声明最终的静态方法是一个坏主意吗?

我明白在这个代码中:

class Foo { public static void method() { System.out.println("in Foo"); } } class Bar extends Foo { public static void method() { System.out.println("in Bar"); } } 

Bar的静态方法隐藏了Foo声明的静态方法,而不是以多态的方式覆盖它。

 class Test { public static void main(String[] args) { Foo.method(); Bar.method(); } } 

…会输出:

在Foo
在酒吧

Foo中将method()重新定义为final将禁用Bar隐藏它的能力,并重新运行main()将输出:

在Foo
在Foo

编辑 :编译失败,当您将方法标记为final ,并且只有当我删除Bar.method() )时再次运行

将静态方法声明为final是否被认为是不好的做法,如果它阻止了有意或无意地重新定义方法的子类?

( 这是对使用final的行为的一个很好的解释。)

我不认为将static方法标记为final是不好的做法。

当你发现, final会阻止这个方法被非常好消息imho的子类隐藏。

我很惊讶你的发言:

在Foo中将方法()重新定义为final将禁用Bar隐藏它的能力,并重新运行main()将输出:

在Foo
在Foo

不,将该方法标记为Foo final方法将阻止Bar编译。 至less在Eclipse中,我得到:

线程“main”中的exceptionjava.lang.Error:未解决的编译问题:无法覆盖Foo的最终方法

另外,我认为人们应该总是调用static方法,即使是在类本身中,也可以用类名来限定它们:

 class Foo { private static final void foo() { System.out.println("hollywood!"); } public Foo() { foo(); // both compile Foo.foo(); // but I prefer this one } } 

静态方法是Java最令人困惑的特性之一。 最好的做法是解决这个问题,并使所有静态方法final是这些最佳实践之一!

静态方法的问题在于

  • 它们不是类方法,而是以一个类名为前缀的全局函数
  • 奇怪的是它们被“inheritance”到子类中
  • 令人惊讶的是,它们不能被忽略而是隐藏起来
  • 它被完全打破,他们可以被称为接收者的实例

所以你应该

  • 总是打电话给他们的class级作为接收
  • 总是用声明类作为接收者来调用它们
  • 总是让他们(或声明的类) final

你应该

  • 从来不会以实例作为接收者来调用它们
  • 永远不要把他们的声明类的子类作为接收者来调用它们
  • 不要在子类中重新定义它们

注意: 你的程序的第二个版本应该会失败一个编译错误。 我认为你的IDE隐藏了你的这个事实!

如果我有一个public static方法,那么它通常已经位于所谓的工具类中 ,只有static方法。 自我解释的例子是StringUtilSqlUtilIOUtil等。 这些工具类自己已经声明了final并提供了一个private构造函数。 例如

 public final class SomeUtil { private SomeUtil() { // Hide c'tor. } public static SomeObject doSomething(SomeObject argument1) { // ... } public static SomeObject doSomethingElse(SomeObject argument1) { // ... } } 

这样你就不能覆盖它们。

如果你的位置不是一种工具类,那么我会质疑public修饰符的值。 它不应该是private吗? 否则就把它移到一些实用程序类。 不要使用public static方法混淆“正常”类。 这样你也不需要final标记它们。

另一种情况是一种抽象工厂类,它通过public static方法返回自我的具体实现。 在这种情况下,将方法标记为final是完全有意义的,您不希望具体实现能够重写该方法。

通常对于实用程序类 – 只有静态方法的类 – 不希望使用inheritance。 因此,您可能希望将该类定义为final,以防止其他类对其进行扩展。 这将否定最终修饰符在您的实用程序类的方法。

代码不能编译:

Test.java:8:bar中的method()不能重写Foo中的method(); 重写的方法是static final public static void method(){

该消息是误导性的,因为根据定义,静态方法可以不被覆盖。

编码时我做了以下事情(不是所有的时间都是100%,但这里没有什么是“错误的”:

(第一套“规则”是为大多数事情做的 – 一些特殊情况在后面被覆盖)

  1. 创build一个界面
  2. 创build一个实现接口的抽象类
  3. 创build扩展抽象类的具体类
  4. 创build实现接口但不扩展抽象类的具体类
  5. 总是,如果可能的话,使接口的所有variables/常数/参数

由于一个接口不能有静态方法,所以你不能解决这个问题。 如果您要在抽象类或具体类中创build静态方法,那么它们必须是私有的,那么就没有办法去覆盖它们。

特别案例:

实用程序类(具有所有静态方法的类):

  1. 宣布这个class是最后的
  2. 给它一个私人的构造函数来防止意外创build

如果你想在一个非私有的具体或抽象类中有一个静态方法,你可能想改为创build一个实用类。

值类(一个非常专门用来存储数据的类,比如java.awt.Point,它几乎包含x和y值):

  1. 不需要创build一个接口
  2. 不需要创build一个抽象类
  3. 上课应该是最后的
  4. 非私有的静态方法是可以的,特别是对于你可能想要执行caching的构造。

如果你遵循上面的build议,你会得到相当灵活的代码,也有相当干净的职责分离。

一个示例值类是这个Location类:

 import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public final class Location implements Comparable<Location> { // should really use weak references here to help out with garbage collection private static final Map<Integer, Map<Integer, Location>> locations; private final int row; private final int col; static { locations = new HashMap<Integer, Map<Integer, Location>>(); } private Location(final int r, final int c) { if(r < 0) { throw new IllegalArgumentException("r must be >= 0, was: " + r); } if(c < 0) { throw new IllegalArgumentException("c must be >= 0, was: " + c); } row = r; col = c; } public int getRow() { return (row); } public int getCol() { return (col); } // this ensures that only one location is created for each row/col pair... could not // do that if the constructor was not private. public static Location fromRowCol(final int row, final int col) { Location location; Map<Integer, Location> forRow; if(row < 0) { throw new IllegalArgumentException("row must be >= 0, was: " + row); } if(col < 0) { throw new IllegalArgumentException("col must be >= 0, was: " + col); } forRow = locations.get(row); if(forRow == null) { forRow = new HashMap<Integer, Location>(col); locations.put(row, forRow); } location = forRow.get(col); if(location == null) { location = new Location(row, col); forRow.put(col, location); } return (location); } private static void ensureCapacity(final List<?> list, final int size) { while(list.size() <= size) { list.add(null); } } @Override public int hashCode() { // should think up a better way to do this... return (row * col); } @Override public boolean equals(final Object obj) { final Location other; if(obj == null) { return false; } if(getClass() != obj.getClass()) { return false; } other = (Location)obj; if(row != other.row) { return false; } if(col != other.col) { return false; } return true; } @Override public String toString() { return ("[" + row + ", " + col + "]"); } public int compareTo(final Location other) { final int val; if(row == other.row) { val = col - other.col; } else { val = row - other.row; } return (val); } } 

把静态方法标记为final是一件好事,特别是如果你正在开发一个你期待别人扩展的框架。 这样,你的用户不会无意中最终隐藏你的静态方法在他们的类中。 但是,如果你正在开发一个框架,你可能想避免使用静态方法开始。

这个final问题大部分可以追溯到虚拟机非常愚蠢/保守的时候。 那么如果你标记了一个方法final这意味着(除其他外),VM可以内联它,避免方法调用。 那是因为长久以来(或长时间的双倍:P)时间: http : //java.sun.com/developer/technicalArticles/Networking/HotSpot/inlining.html 。

Idea / Netbeans的检查会提醒你,因为它认为你想使用final关键字进行优化,他们认为你并不知道现在的虚拟机是不需要的。

只是我的两分钱…

我遇到了使用Spring的AOP和MVC使用final方法的一个不利之处。 我试图使用Spring的AOP放置在被声明为最终的AbstractFormController中的某个方法的安全钩子周围。 我觉得spring在课堂上使用bcel库进行注射,那里有一些限制。

当我创build纯粹的实用工具类时,我用一个私有的构造函数声明,所以它们不能被扩展。 在创build普通类时,如果我的方法没有使用任何类实例variables(或者,在某些情况下,即使它们是,我将在方法中传递参数并使其成为静态的,更容易看到这个方法在做什么)。 这些方法被声明为静态的,但也是私有的 – 它们只是为了避免代码重复或使代码更易于理解。

话虽如此,我不记得你有一个类有公共静态方法,可以/应该扩展的情况。 但是,根据这里所报告的内容,我将最终声明其静态方法。

因为静态方法是类的属性,并且它们被称为类的名称而不是对象。 如果我们使父类的方法也是最终的,它不会被重载,因为最终的方法不允许改变它的内存位置,但是我们可以在同一个内存位置更新最终的数据成员。