41

我明白在这段代码中:

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();
    }
}

...将输出:

in Foo
in Bar

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

在 Foo
在 Foo

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

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

是对使用行为的一个很好的解释final..)

4

10 回答 10

40

我不认为将static方法标记为final.

正如您所发现的,final这将防止该方法被子类隐藏,这是非常好的消息恕我直言

我对你的说法感到非常惊讶:

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

在 Foo
在 Foo

不,将方法标记为finalinFoo将阻止Bar编译。至少在 Eclipse 中我得到:

线程“main”java.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
  }
}
于 2009-12-19T09:03:57.333 回答
35

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

静态方法的问题在于

  • 它们不是类方法,而是以类名为前缀的全局函数
  • 奇怪的是它们被“继承”给子类
  • 令人惊讶的是它们不能被覆盖而是隐藏
  • 可以用实例作为接收者来调用它们,这完全被打破了

因此你应该

  • 总是用他们的班级作为接收者打电话给他们
  • 始终使用声明类仅作为接收者调用它们
  • 总是让他们(或声明类)final

你应该

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

 

注意:您的程序的第二个版本应该会出现编译错误。我想你的 IDE 对你隐瞒了这个事实!

于 2009-12-19T21:36:10.333 回答
7

如果我有一个方法,那么它通常已经位于一个只有方法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方法返回self的具体实现。在这种情况下,标记方法非常有意义final,您不希望具体实现能够覆盖该方法。

于 2009-12-19T21:11:11.417 回答
4

通常对于实用程序类——只有静态方法的类——使用继承是不可取的。出于这个原因,您可能希望将类定义为 final 以防止其他类扩展它。这将否定将 final 修饰符放在您的实用程序类方法上。

于 2009-12-19T15:28:55.400 回答
3

代码无法编译:

Test.java:8:Bar 中的 method() 不能覆盖 Foo 中的 method();被覆盖的方法是 static final public static void method() {

该消息具有误导性,因为根据定义,静态方法永远不会被覆盖。

我在编码时执行以下操作(不是一直 100%,但这里没有什么是“错误的”:

(第一组“规则”适用于大多数事情——之后会涉及一些特殊情况)

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

由于接口不能具有静态方法,因此您不会遇到问题。如果要在抽象类或具体类中创建静态方法,它们必须是私有的,那么就无法尝试覆盖它们。

特别案例:

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

  1. 将类声明为 final
  2. 给它一个私有构造函数以防止意外创建

如果您想在非私有的具体或抽象类中拥有一个静态方法,您可能希望改为创建一个实用程序类。

值类(一个非常专门用于保存数据的类,例如 java.awt.Point,它几乎保存了 x 和 y 值):

  1. 无需创建接口
  2. 无需创建抽象类
  3. 类应该是最终的
  4. 非私有静态方法是可以的,特别是对于您可能想要执行缓存的构造。

如果您遵循上述建议,您将获得非常灵活的代码,并且具有相当清晰的职责分离。

一个示例值类是这个 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);
    }
}
于 2009-12-19T18:03:40.873 回答
1

将静态方法标记为 final 可能是一件好事,特别是如果您正在开发一个您希望其他人扩展的框架。这样,您的用户就不会无意中将您的静态方法隐藏在他们的类中。但是,如果您正在开发一个框架,您可能希望从一开始就避免使用静态方法。

于 2009-12-19T17:04:26.237 回答
1

这个问题的大部分final可以追溯到 VM-s 相当愚蠢/保守的时候。那时,如果您标记了一个方法final,它意味着(除其他外)VM 可以内联它,避免方法调用。自从 long-long(或 long double :P )时间以来,情况并非如此:http: //java.sun.com/developer/technicalArticles/Networking/HotSpot/inlining.html

Idea/Netbeans 检查会警告您,因为它认为您想使用final关键字进行优化,并且他们认为您不知道现代 VM 不需要它。

就我的两分钱...

于 2012-07-02T22:38:12.100 回答
0

在使用 Spring 的 AOP 和 MVC 使用 final 方法时,我遇到了一个不利因素。我试图使用 spring 的 AOP 围绕 AbstractFormController 中声明为 final 的方法之一放置安全钩子。我认为 spring 在类中使用 bcel 库进行注入,并且存在一些限制。

于 2009-12-19T18:21:08.280 回答
0

当我创建纯实用程序类时,我使用私有构造函数声明它们,因此它们不能被扩展。创建普通类时,如果我的方法不使用任何类实例变量,我将它们声明为静态(或者,在某些情况下,即使它们是,我也会在方法中传递参数并将其设为静态,这样更容易查看该方法在做什么)。这些方法被声明为静态但也是私有的——它们只是为了避免代码重复或使代码更易于理解。

话虽如此,我不记得遇到过这样的情况,即您有一个具有公共静态方法并且可以/应该扩展的类。但是,根据此处报告的内容,我将其静态方法声明为最终方法。

于 2009-12-19T20:34:55.000 回答
0

Because static methods are the properties of the class and they are called with the name of the class rather than of object. If we make the parent class method final as well it will not be overloaded as final methods does not allow to change its memory location but we can update the final data member at the same memory location...

于 2013-07-21T03:55:09.600 回答