声明最终的静态方法是一个坏主意吗?
我明白在这个代码中:
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
方法。 自我解释的例子是StringUtil
, SqlUtil
, IOUtil
等。 这些工具类自己已经声明了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%,但这里没有什么是“错误的”:
(第一套“规则”是为大多数事情做的 – 一些特殊情况在后面被覆盖)
- 创build一个界面
- 创build一个实现接口的抽象类
- 创build扩展抽象类的具体类
- 创build实现接口但不扩展抽象类的具体类
- 总是,如果可能的话,使接口的所有variables/常数/参数
由于一个接口不能有静态方法,所以你不能解决这个问题。 如果您要在抽象类或具体类中创build静态方法,那么它们必须是私有的,那么就没有办法去覆盖它们。
特别案例:
实用程序类(具有所有静态方法的类):
- 宣布这个class是最后的
- 给它一个私人的构造函数来防止意外创build
如果你想在一个非私有的具体或抽象类中有一个静态方法,你可能想改为创build一个实用类。
值类(一个非常专门用来存储数据的类,比如java.awt.Point,它几乎包含x和y值):
- 不需要创build一个接口
- 不需要创build一个抽象类
- 上课应该是最后的
- 非私有的静态方法是可以的,特别是对于你可能想要执行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(或者,在某些情况下,即使它们是,我将在方法中传递参数并使其成为静态的,更容易看到这个方法在做什么)。 这些方法被声明为静态的,但也是私有的 – 它们只是为了避免代码重复或使代码更易于理解。
话虽如此,我不记得你有一个类有公共静态方法,可以/应该扩展的情况。 但是,根据这里所报告的内容,我将最终声明其静态方法。
因为静态方法是类的属性,并且它们被称为类的名称而不是对象。 如果我们使父类的方法也是最终的,它不会被重载,因为最终的方法不允许改变它的内存位置,但是我们可以在同一个内存位置更新最终的数据成员。