Java方法关键字“final”及其用途
当我创建复杂类型层次结构(多个级别,每个级别多个类型)时,我喜欢在实现某些接口声明的方法上使用 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);
}
我这样做有几个原因:
- 它帮助我开发类型层次结构。当我向层次结构添加一个类时,非常清楚我必须实现哪些方法,以及我不能重写哪些方法(以防我忘记有关层次结构的详细信息)
- 我认为重写具体根据设计原则和模式,例如模板方法模式,东西是不好的。我不希望其他开发人员或我的用户这样做。
所以 final
关键字非常适合我。我的问题是:
为什么它在野外很少被使用?你能给我展示一些 final
(与我的情况类似)会非常糟糕的例子/原因吗?
When I create complex type hierarchies (several levels, several types per level), I like to use the final
keyword on methods implementing some interface declaration. An example:
interface Garble {
int zork();
}
interface Gnarf extends Garble {
/**
* This is the same as calling {@link #zblah(0)}
*/
int zblah();
int zblah(int defaultZblah);
}
And then
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);
}
I do this for several reasons:
- It helps me develop the type hierarchy. When I add a class to the hierarchy, it is very clear, what methods I have to implement, and what methods I may not override (in case I forgot the details about the hierarchy)
- I think overriding concrete stuff is bad according to design principles and patterns, such as the
template method
pattern. I don't want other developers or my users do it.
So the final
keyword works perfectly for me. My question is:
Why is it used so rarely in the wild? Can you show me some examples / reasons where final
(in a similar case to mine) would be very bad?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
因为你应该多写一个词来使变量/方法成为最终的
通常我会在 3D 零件库中看到此类示例。在某些情况下,我想扩展一些类并改变一些行为。尤其是在没有接口/实现分离的非开源库中,这是危险的。
Because you should write one more word to make variable/method final
Usually I see such examples in 3d part libraries. In some cases I want to extend some class and change some behavior. Especially it is dangerous in non open-source libraries without interface/implementation separation.
当我编写抽象类并希望明确哪些方法是固定的时,我总是使用
final
。我认为这是this关键字最重要的功能。但是,当你不希望课程延长时,为什么要大惊小怪呢?当然,如果您正在为其他人编写库,您会尽力保护它,但是当您编写“最终用户代码”时,在某种程度上,尝试使您的代码万无一失只会导致惹恼维护开发人员,他们会试图找出如何绕过您建造的迷宫。
将课程定为期末也是如此。尽管某些类本质上应该是最终类,但目光短浅的开发人员常常会简单地将继承树中的所有叶类标记为
final
。毕竟,编码有两个不同的目的:向计算机发出指令以及将信息传递给阅读代码的其他开发人员。第二个大多数时候都会被忽略,尽管它几乎与让代码正常工作一样重要。放入不必要的
final
关键字就是一个很好的例子:它不会改变代码的行为方式,因此其唯一目的应该是通信。但你们交流什么呢?如果您将一个方法标记为final
,维护者会认为您已经充分阅读了这样做的信息。如果事实证明你没有这样做,那么你所做的只是让别人感到困惑。我的方法是(显然我在这里可能完全错误):不要写下任何内容,除非它改变了代码的工作方式或传达有用的信息。
I always use
final
when I write an abstract class and want to make it clear which methods are fixed. I think this is the most important function of this keyword.But when you're not expecting a class to be extended anyway, why the fuss? Of course if you're writing a library for someone else, you try to safeguard it as much as you can but when you're writing "end user code", there is a point where trying to make your code foolproof will only serve to annoy the maintenance developers who will try to figure out how to work around the maze you had built.
The same goes to making classes final. Although some classes should by their very nature be final, all too often a short-sighted developer will simply mark all the leaf classes in the inheirance tree as
final
.After all, coding serves two distinct purposes: to give instructions to the computer and to pass information to other developers reading the code. The second one is ignored most of the time, even though it's almost as important as making your code work. Putting in unnecessary
final
keywords is a good example of this: it doesn't change the way the code behaves, so its sole purpose should be communication. But what do you communicate? If you mark a method asfinal
, a maintainer will assume you'd had a good readon to do so. If it turns out that you hadn't, all you achieved was to confuse others.My approach is (and I may be utterly wrong here obviously): don't write anything down unless it changes the way your code works or conveys useful information.
这与我的经历不符。我发现它在各种图书馆中使用得非常频繁。仅举一个(随机)示例:查看
http://code.google 中的抽象类。 com/p/guava-libraries/
,例如 com.google.common.collect.AbstractIterator。
peek()
、hasNext()
、next()
和endOfData()
是最终版本,仅留下computeNext()
给实现者。在我看来,这是一个非常常见的例子。反对使用
final
的主要原因是允许实现者更改算法 - 您提到了“模板方法”模式:修改模板方法或通过一些预定义来增强它仍然有意义。 /post 操作(不会用数十个前/后钩子向整个班级发送垃圾邮件)。pro 使用
final
的主要原因是为了避免意外的实现错误,或者当该方法依赖于未指定的类的内部(因此将来可能会发生变化)时。That doesn't match my experience. I see it used very frequently in all kinds of libraries. Just one (random) example: Look at the abstract classes in:
http://code.google.com/p/guava-libraries/
, e.g. com.google.common.collect.AbstractIterator.
peek()
,hasNext()
,next()
andendOfData()
are final, leaving justcomputeNext()
to the implementor. This is a very common example IMO.The main reason against using
final
is to allow implementors to change an algorithm - you mentioned the "template method" pattern: It can still make sense to modify a template method, or to enhance it with some pre-/post actions (without spamming the entire class with dozens of pre-/post-hooks).The main reason pro using
final
is to avoid accidental implementation mistakes, or when the method relies on internals of the class which aren't specified (and thus may change in the future).我认为它不常用的原因有两个:
我通常属于第二个原因。我确实在一些通用的基础上重写了具体的方法。在某些情况下,这很糟糕,但很多时候它并不与设计原则相冲突,事实上可能是最好的解决方案。因此,当我实现一个接口时,我通常不会对每个方法进行足够深入的思考来决定最终关键字是否有用。特别是因为我从事许多经常变化的业务应用程序。
I think it is not commonly used for two reasons:
I typically fall into the second reason. I do override concrete methods on a somewhat common basis. In some cases this is bad, but there are many times it doesn't conflict with design principles and in fact might be the best solution. Therefore when I am implementing an interface, I typically don't think deeply enough at each method to decide if a final keyword would be useful. Especially since I work on a lot of business applications that change frequently.
因为它不应该是必要的。它也不会完全关闭实现,因此实际上它可能会给您一种错误的安全感。
由于里氏替换原则,这应该是不必要的。该方法有一个契约,并且在正确设计的继承图中,该契约已得到满足(否则它是一个错误)。示例:
如果不允许子类(为其)做正确的事情,您可能会引入错误。或者,您可能需要其他开发人员将您的
Garble
接口的继承树放在您的接口旁边,因为您的最终方法不允许它执行其应该执行的操作。错误的安全感是非静态最终方法的典型特征。静态方法不应该使用实例中的状态(它不能)。非静态方法可能可以。您的最终(非静态)方法可能也会这样做,但它不拥有实例变量 - 它们可能与预期不同。因此,您给继承
AbstractGarble
形式的类的开发人员增加了负担 - 确保实例字段在任何时间点都处于您的实现所期望的状态。在调用您的方法之前,没有为开发人员提供一种准备状态的方法,如下所示:在我看来,除非您有充分的理由,否则您不应该以这种方式关闭实现。如果您记录了您的方法契约并提供了 junit 测试,您应该能够信任其他开发人员。使用 Junit 测试,他们实际上可以验证里氏替换原则。
附带说明一下,我偶尔会关闭一个方法。特别是当它位于框架的边界部分时。我的方法做了一些簿记,然后继续到由其他人实现的抽象方法:
这样就没有人忘记做簿记,但他们可以提供自定义登录。您是否喜欢这样的设置当然取决于您:)
Because it should not be necessary. It also does not fully close down the implementation, so in effect it might give you a false sense of security.
It should not be necessary due to the Liskov substitution principle. The method has a contract and in a correctly designed inheritance diagram that contract is fullfilled (otherwise it's a bug). Example:
By not allowing a subclass to do the right thing (for it) you might introduce a bug. Or you might require another developer to put an inheritance tree of your
Garble
interface right beside yours because your final method does not allow it to do what it should do.The false sense of security is typical of a non-static final method. A static method should not use state from the instance (it cannot). A non-static method probably does. Your final (non-static) method probably does too, but it does not own the instance variables - they can be different than expected. So you add a burden on the developer of the class inheriting form
AbstractGarble
- to ensure instance fields are in a state expected by your implementation at any point in time. Without giving the developer a way to prepare the state before calling your method as in:In my opinion you should not close an implementation in such a fashion unless you have a very good reason. If you document your method contract and provide a junit test you should be able to trust other developers. Using the Junit test they can actually verify the Liskov substitution principle.
As a side note, I do occasionally close a method. Especially if it's on the boundary part of a framework. My method does some bookkeeping and then continues to an abstract method to be implemented by someone else:
That way no-one forgets to do the bookkeeping but they can provide a custom login. Whether you like such a setup is of course up to you :)