Java方法关键字“final”及其使用

时间:2011-01-15 14:12:02

标签: java final overloading keyword

当我创建复杂类型层次结构(多个级别,每个级别有几种类型)时,我喜欢在实现某些接口声明的方法上使用final关键字。一个例子:

interface Garble {
  int zork();
}

interface Gnarf extends Garble {
  /**
   * This is the same as calling {@link #zblah(0)}
   */
  int zblah();
  int zblah(int defaultZblah);
}

然后

abstract class AbstractGarble implements Garble {
  @Override
  public final int zork() { ... }
}

abstract class AbstractGnarf extends AbstractGarble implements Gnarf {
  // Here I absolutely want to fix the default behaviour of zblah
  // No Gnarf shouldn't be allowed to set 1 as the default, for instance
  @Override
  public final int zblah() { 
    return zblah(0);
  }

  // This method is not implemented here, but in a subclass
  @Override
  public abstract int zblah(int defaultZblah);
}

我这样做有几个原因:

  1. 它帮助我开发类型层次结构。当我向层次结构中添加一个类时,非常清楚,我必须实现哪些方法,以及我可能不会覆盖哪些方法(如果我忘记了层次结构的详细信息)
  2. 根据设计原则和模式,例如template method模式,我认为重写具体的东西是不好的。我不希望其他开发人员或我的用户这样做。
  3. 因此final关键字对我来说非常适合。我的问题是:

    为什么它在野外很少使用?你能告诉我一些例子/原因final(与我的情况类似)会非常糟糕吗?

5 个答案:

答案 0 :(得分:4)

  

为什么它在野外很少使用?

因为你应该再写一个单词来使变量/方法最终

  

你能告诉我一些例子/理由,最终(在类似的情况下)会非常糟糕吗?

通常我会在3d零件库中看到这样的例子。在某些情况下,我想扩展一些类并改变一些行为。特别是在没有接口/实现分离的非开源库中它是危险的。

答案 1 :(得分:3)

当我编写一个抽象类时,我总是使用final,并希望明确哪些方法是固定的。我认为这是此关键字最重要的功能。

但是当你还没想到要扩展课程时,为什么大惊小怪呢?当然,如果你正在为别人写一个图书馆,你会尽可能地保护它,但是当你写“最终用户代码”时,有一点是试图让你的代码万无一失只能用于惹恼维护开发人员,他们会试图弄清楚如何解决你构建的迷宫。

同样的课程也是最终的。虽然有些类本质上应该是最终的,但是短视的开发人员通常只会将inheirance树中的所有叶子类标记为final

毕竟,编码有两个不同的目的:向计算机发出指令并将信息传递给阅读代码的其他开发人员。第二个在大多数时候被忽略,尽管它几乎和使代码一样重要。添加不必要的final关键字就是一个很好的例子:它不会改变代码的行为方式,因此它的唯一目的应该是通信。但是你在沟通什么?如果您将方法标记为final,维护者将假设您有一个很好的读取方法。如果事实证明你没有,那么你所取得的就是混淆他人。

我的方法是(显然我可能完全错了):除非改变代码的工作方式或传达有用信息,否则不要写下任何内容。

答案 2 :(得分:2)

我认为它并不常用,原因有两个:

  1. 人们不知道它存在
  2. 人们在构建方法时没有习惯于思考它。
  3. 我通常属于第二个原因。我在一些常见的基础上覆盖了具体的方法。在某些情况下这很糟糕,但有很多时候它与设计原则没有冲突,事实上可能是最好的解决方案。因此,当我实现一个接口时,我通常不会在每个方法上深入思考,以确定最终关键字是否有用。特别是因为我在很多经常变化的业务应用程序上工作。

答案 3 :(得分:2)

  

为什么它在野外很少使用?

这与我的经历不符。我看到它在各种库中经常使用。只有一个(随机)示例:查看以下的抽象类:

http://code.google.com/p/guava-libraries/

,例如com.google.common.collect.AbstractIterator。 peek()hasNext()next()endOfData()是最终的,只留下computeNext()给实施者。这是IMO非常常见的例子。

反对使用final的主要原因是允许实现者更改算法 - 您提到了“模板方法”模式:修改模板方法仍然有意义,或者通过一些预先增强它来增强它/ post动作(没有用几十个预挂钩/挂钩后的垃圾邮件发送给整个班级)。

使用final的主要原因是为了避免意外的实现错误,或者当方法依赖于未指定的类的内部时(因此将来可能会改变)。

答案 4 :(得分:1)

  

为什么它在野外很少使用?

因为没有必要。它也没有完全关闭实现,所以实际上它可能会给你一种虚假的安全感。

由于Liskov substitution principle,因此没有必要。该方法具有契约,并且在正确设计的继承图中,合同已满(否则它是一个错误)。示例:

interface Animal {
    void bark();
}

abstract class AbstractAnimal implements Animal{
   final void bark() {
       playSound("whoof.wav"); // you were thinking about a dog, weren't you?
   }
}

class Dog extends AbstractAnimal {
  // ok
}

class Cat extends AbstractAnimal() {
  // oops - no barking allowed!
}

通过不允许子类做正确的事(对它来说),你可能会引入一个bug。或者您可能需要其他开发人员将Garble接口的继承树放在您的旁边,因为您的最终方法不允许它执行应该执行的操作。

虚假的安全感是非静态最终方法的典型特征。静态方法不应该使用实例中的状态(它不能)。非静态方法可能会这样做。您的最终(非静态)方法可能也会这样做,但它不拥有实例变量 - 它们可能与预期不同。因此,您继承了继承表单AbstractGarble的类的开发人员的负担 - 以确保实例字段处于您的实现在任何时间点所期望的状态。在调用方法之前,不给开发人员一个准备状态的方法,如:

int zblah() {
    prepareState();
    return super.zblah();
}

在我看来,除非你有充分的理由,否则你不应该以这种方式关闭实现。如果您记录方法合同并提供junit测试,那么您应该能够信任其他开发人员。使用Junit测试,他们可以实际验证Liskov substitution principle

作为旁注,我偶尔会关闭一种方法。特别是如果它在框架的边界部分。我的方法做了一些簿记,然后继续使用其他人实现的抽象方法:

final boolean login() {
    bookkeeping();
    return doLogin();
}
abstract boolean doLogin();

这样就没有人忘记进行簿记,但他们可以提供自定义登录。您是否喜欢这样的设置当然取决于您:)