Pun intended… Let’s discuss Java
final
.
Recently, our popular blog post
“10 Subtle Best Practices when Coding Java” had a significant revival and a new set of comments as it was
summarised and linked from JavaWorld. In particular, the JavaWorld editors challenged our opinion about the Java keyword “
final
“:
More controversially, Eder takes on the question of whether it’s ever safe to make methods final by default:
“If you’re in full control of all source code, there’s absolutely nothing wrong with making methods final by default, because:”
- “If you do need to override a method (do you really?), you can still remove the final keyword”
- “You will never accidentally override any method anymore”
Yes, indeed. All classes, methods, fields and local variables should be final by default and mutable via keyword.
Here are fields and local variables:
int finalInt = 1;
val int finalInt = 2;
var int mutableInt = 3;
Whether the Scala/C#-style
val
keyword is really necessary is debatable. But clearly, in order to modify a field / variable ever again, we should have a keyword explicitly allowing for it. The same for methods – and I’m using Java 8’s
default
keyword for improved consistency and regularity:
class FinalClass {
void finalMethod() {}
}
default class ExtendableClass {
void finalMethod () {}
default void overridableMethod() {}
}
That would be the perfect world in our opinion, but Java goes the other way round making
default
(overridable, mutable) the default and
final
(non-overridable, immutable) the explicit option.
Fair enough, we’ll live with that
… and as API designers (from the
jOOQ API, of course), we’ll just happily put
final
all over the place to at least pretend that Java had the more sensible defaults mentioned above.
But many people disagree with this assessment, mostly for the same reason:
As someone who works mostly in osgi environments, I could not agree more, but can you guarantee that another api designer felt the same way? I think it’s better to preempt the mistakes of api designers rather than preempt the mistakes of users by putting limits on what they can extend by default. – eliasv on reddit
Or…
Strongly disagree. I would much rather ban final and private from public libraries. Such a pain when I really need to extend something and it cannot be done.
Intentionally locking the code can mean two things, it either sucks, or it is perfect. But if it is perfect, then nobody needs to extend it, so why do you care about that.
Of course there exists valid reasons to use final, but fear of breaking someone with a new version of a library is not one of them. – meotau on reddit
Or also…
I know we’ve had a very useful conversation about this already, but just to remind other folks on this thread: much of the debate around ‘final’ depends on the context: is this a public API, or is this internal code? In the former context, I agree there are some good arguments for final. In the latter case, final is almost always a BAD idea. – Charles Roth on our blog
All of these arguments tend to go into one direction: “We’re working on crappy code so we need at least some workaround to ease the pain.”
But why not think about it this way:
The API designers that all of the above people have in mind will create precisely that horrible API that you’d like to patch through extension. Coincidentally, the same API designer will not reflect on the usefulness and communicativeness of the keyword
final
, and thus will never use it, unless required by the Java language. Win-win (albeit crappy API, shaky workarounds and patches).
The API designers that want to use final for their API will reflect a lot on how to properly design APIs (and well-defined extension points / SPIs), such that you will never worry about something being
final
. Again, win-win (and an awesome API).
Plus, in the latter case, the odd hacker will be kept from hacking and breaking your API in a way that will only lead to pain and suffering, but that’s not really a loss.
Final interface methods
For the aforementioned reasons, I still deeply regret that
final
is not possible in Java 8 interfaces.
Brian Goetz has given an excellent explanation why this has been decideed upon like that. In fact, the usual explanation. The one about this not being the main design goal for the change ;-)
But think about the consistency, the regularity of the language if we had:
default interface ImplementableInterface {
void abstractMethod () ;
void finalMethod () {}
default void overridableMethod() {}
}
(Ducks and runs…)
Or, more realistically with our status quo of defaulting to
default
:
interface ImplementableInterface {
void abstractMethod () ;
final void finalMethod () {}
void overridableMethod() {}
}
Finally
So again, what are your (final) thoughts on this discussion?
If you haven’t heard enough,
consider also reading this excellent post by
Dr. David Pearce, author of the
whiley programming languageLike this:
Like Loading...
Hope I’m not repeating myself too much… but just be wary of conflicts between ‘final’ and testability.
IF (emphasis *if*) I (a) have to mock out parts of *your* API in order to test *my* code,
AND (b) you don’t have interfaces for most of your API,
THEN (c) ‘final’ is a bad choice (because I can’t test).
There are many cases where (a) is not true. Certainly in good APIs, (b) will not be true.
Lack of final can cause “bad things”. Final used to ‘finalize’ a bad decision casts it in concrete, and causes pain and suffering for developers, little old ladies crossing the street, and innocent kittens. Or something like that. :-)
Nice to see you back, Charles. I appreciate your insistence (and you’re still right, of course) :-)
I essentially repeated this topic to generate a new discussion only about that (as the other post was really about 9 other things as well). This topic is now syndicated to our partners (e.g. DZone) and it’s possibly going to be discussed on reddit. If you want to join those, it would be fun! :-)
Hi Lukas,
I strongly agree with Charles. I’ve bookmarked this post to say exactly same thing, so let me just emphasize:
if you’re going to botch the API you won’t get a chance to fix it in the future.
If you’re going to be final all over the place, I won’t be able to fix it for myself/tests out some of the things.
Final does not give you a lot on methods and classes, and you could always mention to people that they can harm themselves by overriding something.
By default all fields should be final and everything Open to extension (if you believe in SOLID).
That is point of balance IMO.
I’m removing all the custom extensions from project we’re working on. Previous team just added something to ALL libraries they’ve used (from Jboss through log4j up to Apache commons and about dozen of others). It’s hard, true, but if you’ve got team that does that kind a thing, then you have a more serious issue to resolve.
Bad design can’t be solved by 3rd party API provider. Sorry, but regardless of how hard you will try, your API won’t make my code better (at least not in such extreme examples).
But if you’ll close you’re API for extension, strong teams won’t have chance to work with it, and will have to put about billion of wrappers between you’re API and theirs app.
Yeah, that may be good, but that’s a different discussion.
That team you’ve mentioned (patching JBoss, log4j, etc.): They might as well have actually patched those libraries, given that they’re all Open Source. So in those cases, patching and extending are really the same thing: Patching (which can also be done with closed source. It’s just a bit harder).
So, in a well-designed API, we should really discuss whether we should allow users to patch pretty much everything. This blog is obviously very biased towards our experience with maintaining the jOOQ API. From an Open/Closed principle’s perspective, extension is very easily possible as we provide a lot of specific APIs to implement custom elements. We would like our users to only ever extend those APIs, not random internal stuff. Which is why everything internal is package-private and as final as possible. If that doesn’t work out for our users / customers, we prefer them to reach out to us to find a long-term solution, instead of everyone quietly patching stuff at their “homes”, without us ever getting feedback about what we did wrong (either in API design or in documentation).
So, in the end, we also see
final
(and other means) as a means of enforcing tighter customer relations. It works great for us. We’ve had a couple of users complain about specific things being package-private or final. In each case, we could help them with a much better solution, teaching them how to make better use of our API – or we could factor out an awesome new feature request for a new SPI, where they could thoroughly extend our API. If we made it easy for them to patch things, they would’ve never said anything and they would’ve gone quite frustrated in the end – with their own patches, and possibly also with our product.When the APIs are used?
1. It’s internal company tool – final is not a problem, because someone reachable should be maintaining it.
2. It’s a product bought by company – in case of painful API bug/feature, shouldn’t there be customer support up and ready to take care of such issue?
3. It’s a integration thingy with client’s API – if it’s theirs, they can rework it a bit? If not fix the issue, then open it up to mutability. They do want to receive working product, aren’t they?
4. It’s something free, open source found on the web. Web is vast. Why use crappy API and complain about it? There certainly is a replacement. It would be hard to believe there is only one OOS API doing some necessary work of commercial counterparts and it’s actually crappy. How in the world no one would be rewriting it or submitting a lot of pull request to patch the-one-and-only-API that whole world is using while suffering immense pain.
Bonus question: How often do you unit test your api?
I’m not 100% convinced I understand the point you’re trying to convey. You probably have a very strong opinion about X (testing, maybe?) and now you’ve found that in your environment, approach Y benefits X (e.g. subtype polymorphism). And since that is your experience, you’re thinking Y is actually a prerequisite for X, when in reality it is only an advantage coming at certain costs.
However, instead of telling us about X or Y, you projected your particular experience (apparently, from how you put it, working with crappy software) on all software. Did I get this right? :)
Bonus answer: Our design allows us to “inject” all sorts of behaviour at a central extension point, which is also the extension point we’re offering to users: https://blog.jooq.org/2019/06/06/how-to-write-a-simple-yet-extensible-api/ If we do unit test (mostly, we’re integration testing, as we want to make sure the backing RDBMS runs our queries just fine), then that approach has hardly ever failed us.
Never was there any need for mocking stuff through any subclassing trickery.