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

时间:2009-12-19 08:56:17

标签: java static methods final

我理解在这段代码中:

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中   在酒吧

method()中将final重新定义为Foo将禁用Bar隐藏它的权限,并重新运行main()将输出:< / p>

  在Foo中   在Foo

修改:将方法标记为final时编译失败,仅在我删除Bar.method()

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

this很好地解释了使用final的行为是什么......)

10 个答案:

答案 0 :(得分:36)

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

正如您所知,final将阻止该方法被子类隐藏,这是非常好的消息imho

我对你的陈述感到非常惊讶:

  

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

     在Foo中   在Foo

不,在final中将方法标记为Foo会阻止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
  }
}

答案 1 :(得分:31)

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

静态方法的问题在于

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

因此你应该

  • 总是用他们的班级作为接收者来称呼他们
  • 总是只将声明类称为接收器
  • 来调用它们
  • 总是让它们(或声明类)final

你应该

  • 从不使用实例作为接收器
  • 来调用它们
  • 从不使用其声明类的子类作为接收器
  • 来调用它们
  • 从不在子类中重新定义它们

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

答案 2 :(得分:6)

如果我有public static方法,那么它通常已经位于只有static方法的所谓实用程序类中。自我解释的示例包括StringUtilSqlUtilIOUtil等。这些实用程序类本身已经声明为final并提供了private构造函数。 E.g。

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是完全有意义的,您不希望具体实现能够覆盖该方法。

答案 3 :(得分:4)

通常使用实用程序类 - 只使用静态方法的类 - 不希望使用继承。因此,您可能希望将类定义为final以防止其他类扩展它。这将否定在实用程序类方法上放置最终修饰符。

答案 4 :(得分:3)

代码无法编译:

  

Test.java:8:Bar中的method()不能   在Foo中覆盖方法();覆盖   方法是静态的最终       public static void method(){

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

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

(第一组“规则”是针对大多数事情完成的 - 一些特殊情况在之后被覆盖)

  1. 创建界面
  2. 创建一个实现接口的抽象类
  3. 创建扩展抽象类的具体类
  4. 创建实现接口的具体类,但不扩展抽象类
  5. 总是,如果可能的话,制作接口的所有变量/常量/参数
  6. 由于界面不能使用静态方法,因此您无法解决问题。如果要在抽象类或具体类中创建静态方法,它们必须是私有的,那么就无法尝试覆盖它们。

    特殊情况:

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

    1. 将该类声明为最终
    2. 给它一个私人构造函数以防止意外创建
    3. 如果要在非私有的具体或抽象类中使用静态方法,则可能需要改为创建实用程序类。

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

      1. 无需创建界面
      2. 无需创建抽象类
      3. 课程应该是最终的
      4. 非私有静态方法是可以的,特别是对于构造,因为您可能希望执行缓存。
      5. 如果您遵循上述建议,您将获得非常灵活的代码,这些代码也具有相当清晰的职责分离。

        示例值类是此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);
            }
        }
        

答案 5 :(得分:1)

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

答案 6 :(得分:1)

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

猜测 ,Idea / Netbeans检查警告您,因为它认为您要使用final关键字进行优化,他们认为您是不知道现代虚拟机不需要它。

只是我的两分钱......

答案 7 :(得分:0)

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

答案 8 :(得分:0)

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

话虽如此,我不记得遇到过你的类有公共静态方法且可以/应该扩展的情况。但是,根据这里报道的内容,我会声明其静态方法是最终的。

答案 9 :(得分:0)

因为静态方法是类的属性,所以使用类的名称而不是对象来调用它们。如果我们将父类方法设为final也不会重载,因为最终方法不允许更改其内存位置,但我们可以在同一内存位置更新最终数据成员...

相关问题