Hacker News new | past | comments | ask | show | jobs | submit login

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


The problem is that all the items you've listed are basically "just in case we'll need to do something later".

And this basically never happens, but you still have to carry that extra overhead of interfaces.

Java also supports private/public method visibility, and this can be used to clearly show the contract. No need for interfaces.


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.


I think parent means that the one implementation may be changed in the future, so everything applies.

If you 100% sure the one implementation will never change, then I'd say you're right. But it requires future-telling.


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 always wonder what is the motivation when people do interfaces with just one implementation.

They intend to write a test double which implements the interface. But then they don't get around to writing tests?


You just invented Java AutoValue and Kotlin Data Classes.

https://github.com/google/auto/blob/main/value/userguide/ind...


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?


Read-only use of an externally provided collection, presumably.


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.


Spring, Guice? They need to instantiate classes, and call factory methods.


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.

And I’ve never forgiven him for it


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.


Yep, that's why I prefer static (code generation) dependency injection (i.e. not Guice).


If you are rigorous as to make classes final, you should also be rigorous to never provide a non-interface as an Application Programming Interface.

Google uses mocks and fakes implementations of interfaces, and provides dependency injection frameworks for managing these (Guice and Dagger).


I once worked with a guy who obsessively made interfaces for every java class. Even domain objects. He was extremely proud of this.

It was garbage.


Was this in Amazon? If so, it might have been me. Sorry about that. I have learnt my lesson now.

I don’t recall doing it for domain objects though.


>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.


In my experience I don’t see many people use final classes. Mostly just final fields.


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.


Good thing the security manager is deprecated for removal!




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: