I wonder if it thanks to some people blindly following Effective Java book that made a sin by saying "final all the things". So now we cannot easily mock final classes in tests. And mocking tools have to resort to bytecode manipulation to mock the final classes.
E.g. Effective Java is a requirement inside Google, so even public GDrive APIs have final classes. External APIs is exactly the thing you'd want to mock.
I would say less overuse of final, more underuse of interfaces. If everything takes/returns/stores values by interfaces (excluding data containers with no behavior) then you don't need to "jailbreak" any class to mock it.
Of course you get code bloat defining interfaces for everything you intend to implement once, and you have to enforce these rules, but this is something that could be made easier. Not in Java, but imagine a language where:
- Concrete classes can only be used in new, or in some platform provided DI container.
- Methods can only accept interface types and return interface types.
- Fields are private only, all public/protected is via properties (or getters/setters, it just has to be declarable in an interface)
- You have a ".interface" syntax (akin to ".class" but for types) that refers to the public members of a class without tying you to the concrete class itself. You can use this as a shorthand instead of declaring separate interfaces for everything.
Eg.
```
final class GDrive { ... }
public Download file(GDrive.interface drive) { ... }
class MockDrive implements GDrive.interface { ... }
```
The closest I can think of is a hypothetical typed variant of NewSpeak, but maybe something like this exists already?
> I would say less overuse of final, more underuse of interfaces.
Interfaces with one implementation are terrible. They just clutter everything and make the code navigation a pain, so it's good that people are avoiding them.
Perhaps a special "test-only" mode that allows to patch finals is a better idea.
Custom classloaders and Java agents allow to modify bytecode before it loads into JVM, so it's possible to remove `final`, modify visibility scope and perform basically anything.
I always wonder what is the motivation when people do interfaces with
just one implementation.
I mean, using this logic, every single function can be hidden behind an
interface. Even the sole implementation of the interface can be hidden
behind a yet another interface.
If there's just one implementation, then the interface is not necessary!
An interface, esp. when returned from a method, is the best way the define a limited contract. If you return the concrete class, you have the following potential issues:
- Very broad, unspecific contract that may even obscure the methods purpose
- You cannot modify the contract without modifying the class AND vice versa
- Shrinking a contract (taking away elements) is far harder and more likely to cause breakages in other code than growing a contract
- Mocks become more cumbersome because the contract is so broad
- Changes to the concrete class cause ripple effects in code that doesn't care about the change
I think you've navigated away from the scope of my remark; I've
specifically asked what's the point of using interfaces when there's
just one implementation, not "what is the point of interfaces" in
general.
One motivation is to separate interface and implementation. Using interface allows one to easily observe available methods in a one place. With class, one must use IDEs to filter out hundreds irrelevant lines just to find out class contract. If you ever used Delphi or C++, it provides much better experience by clearly separating class interface and class implementation.
I do not like Bloch, and see him as the architect of some of Java’s woes. Generics didn’t have to be so stupid. Gilad Bracha (who did a lot of the work on generics) quit the moment they were done to go try something very different - gradual typing. I hope he’s keeping an eye on what Elixir is trying, because Set Theoretic Typing has the potential to be big, and it can be applied gradually.
I can no longer recall exactly what Bloch said, I may have to search through some of by old writing to find it, but at one point he admitted he didn’t really understand type theory when he designed the collections API. And while I appreciate the honesty, and the reason (he was trying to illustrate that this stuff is still too hard if “even he” didn’t get it), I think it paints him rather worse.
But I already knew that about him from working with that code for years and understanding LSP, which he clearly did not.
I don’t know why they thought he should be the one writing about how to use Java effectively when he was materially responsible for it being harder to use, but I’m not going to give him any money to reward him. And there are other places to get the same education. “Refactoring” should be a cornerstone of every education, for much the same reason learning to fall without hurting yourself is the first thing some martial arts teach you. Learn to clean up before you learn to make messes.
He said at one point that he had thought of a different way to decompose the interfaces for collections that had less need for variance, with read and write separated, but he thought there were too many interfaces and they would confuse people. But when I tried the same experiment (I spent years thinking about writing my own language)… the thing is when you’re only consuming a collection, a lot of the types have the same semantics, so they don’t need separate read interfaces, and the variance declarations are much simpler. It’s only when you manipulate them that you run into trouble with Liskov, and things that are structurally similar have different contracts. The difference in type count to achieve parity with Collections was maybe 20% more, not double. So to this day I don’t know what he’s talking about.
Most APIs should only consume collections from callers, so friction against mutation in your interface is actually a good thing.
> he had thought of a different way to decompose the interfaces for collections that had less need for variance, with read and write separated, but he thought there were too many interfaces and they would confuse people.
So Josh Bloch opted against separate read/write interfaces.
> the thing is when you’re only consuming a collection, a lot of the types have the same semantics, so they don’t need separate read interfaces, and the variance declarations are much simpler.
And you opted against separate read/write interfaces.
> the thing is when you’re only consuming a collection,
> a lot of the types have the same semantics, so they
> don’t need separate read interfaces
> Most APIs should only consume collections from callers
I'm having trouble understanding what you mean by "consuming a collection." Can you expand?
The trouble is final is not final in Java, private is not private. I mean you can lock it down but you usually won’t. With reflective access, breaking the rules is one method call away. Common Java libraries such as meta factories (Spring, Guice), serializers (Jackson, GSON) and test frameworks regularly cheated in the JDK 8 era and a lot of us are running with protections for the stdlib turned off.
Normally a JDK 21 would let you get private/final reflective access to your own “module” but not to the stdlib modules but so many libraries want private access to stdlib objects such as all the various date and time objects.
I haven’t really run into any libraries wanting reflective access at all. All of our applications are on Java 21, except for one that relies on 8 because it uses jdk internals.
What ones have you ran into? Jackson doesn’t even require you to —add-opens on anything.
Yes and they do that by calling the public constructors, which is permitted by the module system because they are public. There is no cracking into private methods there.
> So now we cannot easily mock final classes in tests
> And mocking tools have to resort to bytecode manipulation to mock the final classes
Well which is it? Presumably you use said mocking tool anyway, so it's not your effort that's being expended.
"Final all the things" really doesn't go far enough. There is little point substituting a mutable hashmap for a "final" mutable hashmap, when the actual solution is for the standard library to ship proper immutable collection classes.
In any case, I prefer to avoid mockito anyway, so it's a non-issue for me. Just do plain ol' dependency injection by passing in dependencies into constructors.
As I mentioned in another reply, Josh Bloch experimented with a different type structure for the Collections API that could have yielded read only collections but he thought it was too confusing and went back to… this.
I would say that anything that requires monkey patching in a strong typed language is a sign of bad architecture and application design in first place.
>So now we cannot easily mock final classes in tests
Mocking final classes is a blunder on its own. Most classes should be package private not public, so being final would have close to zero relevance. Personally I do not use mocking tools/frameworks at all.
OTOH, there is very little benefit of having final classes performance wise. Java performs CHA (class hierarchy analysis), anyways.
The only final classes I can remember are stuff like java.lang.String, which needed to be immutable so a SecurityManager could consume them for policy decisions.
E.g. Effective Java is a requirement inside Google, so even public GDrive APIs have final classes. External APIs is exactly the thing you'd want to mock.