How not to use Java 8 default methods
Warning: you can not make this unseen once you have read
I was talking about the multiple inheritance of default methods in the last blog article and how they behave during compilation and run time. This week I look at how to use default methods to do real inheritance, which actually, default methods were not designed for. For this very reason, please read these lines at your own risk, and do not imply that this is a pattern to be followed, just as well do not imply the opposite. What I write here are some coding technics that can be made using Java 8 but their usability is questionable at least for me. I am also a bit afraid to let some ifrit out of the bottle , but on the other hands those ifrits just do not stay there anyway. Some day somebody would let it out. At least I attach the warning sign.
Sample problem
A few years ago I worked on an application that used a lot of different types of objects that each had a name. After many classes started to contain
public String getName(){...} public void setName(String name){...}
methods that were just setters and getters the copy paste code smell just filled the room unbearable. Therefore we created a class
class HasName { public String getName(){...} public void setName(String name){...} }
and each of the classes that had name were just extending this class. Actually it was not working for a long time. There were classes that extended already other classes. In that case we just tried to move the HasName
upward in the inheritance line, but in some cases it just did not work. As we went up the line reaching for the top we realized that those classes and their some other descendant do not have a name, why to force them? To be honest, in real life it was bit more complex than just having name. If it were only names, we could live with it having other classes having names. It was something more complex that would just make the topic even more complicated and believe me: it is going to be complex enough.
Summary: we could not implement having the name for some of the objects implemented in some spare classes. But now we could do that using default methods.
HasName interface with default implementation
Default methods just deliver default functionality. A default method can access the this
variable, which is always the object that is implementing the interface and on which behalf the method was invoked. If there is an interface I
and class C implements the interface, when a method on a C c
object is invoked the variable this
is actually the object c
. How would you implement getName()
and setName()
?
These are setters and getters that accessing a String variable that is in the object. You can not access that from the interface. But it is not absolutely necessary that the value is stored IN the object. The only requirement is that whatever is set for an object the same is get. We can store the value somewhere else, one for each object instance. So we need some value that can be paired to an object and the lifetime of the value has to be the same as the lifetime of the object. Does it ring the bell?
It is a weak hash map! Yes, it is. And using that you can easily implement the HasName
interface.
public interface HasName { class Extensions { private static final WeakHashMap<HasName, String> map = new WeakHashMap<>(); } default void setName(String name) { Extensions.map.put(this, name); } default String getName() { return Extensions.map.get(this); } }
All you have to do is write at the end of the list of interfaces the class implements: ,HasName
and it magically has.
In this example the only value stored is a String
. However you can have instead of String
any class and you can implement not only setters and getters but any methods that do something with that class. Presumably these implementations will be implemented in the class and the default methods will only delegate. You can have the class somewhere else, or as an inner class inside the interface. Matter of taste and style.
Conclusion
Interfaces can not have instance fields. Why? Because in that case they were not interfaces but classes. Java does not have multiple implementation inheritance. Perhaps it has but “please don’t use it” kind of. The default method is a technological mistake. You can call it compromise. Something that was needed to retain backward compatibility of JDK libraries when extended with functional methods. Still you can mimic the fields in interfaces using weak hash maps to get access the inherited class “vtable” of fields and methods to delegate to. With this you can do real multiple inheritance. The type that your mother always warned you about. I told you mate!
Another warning: the above implementation is NOT thread safe. If you try to use it in multithread environment you may get ConcurrentModificationException or it may even happen that calling get()
on a weak hash map gets into infinite loop and never returns. I do not tell how to fix the usage of weak hash maps in this scenario. Or, well, I changed my mind, and I do: use default methods only the they were designed for.
Reference: | How not to use Java 8 default methods from our JCG partner Peter Verhas at the Java Deep blog. |
good post, I was a little bit confused about default methods, excellent example. Thanks a lot.
To put it simply, for default methods they should only call other methods on the interface hierarchy or call methods on any of the parameters (since the idea of default methods was to allow functions as parameters). If a default method calls anything else than it goes against what they’re designed for, and more so if what they’re calling is storing state.