Finding a Diamond in the Eclipse Collections Repo

Robin Zheng
4 min readFeb 1, 2023

--

Inheritance is a feature of Object-Oriented languages that I’ve come across many times, but never critically thought about. At its surface, it’s a simple concept; children can inherit the fields and methods defined in its parents. Used with classes and interfaces, it is a way to hierarchically organize code and promote code reuse.

Interface inheritance in Eclipse Collections

Despite using Java for many years now, I’ve never encountered an issue with inheritance; with most cases being simple. It was only because of my interest in open source and my first contribution to Eclipse Collections that I truly had to explore the complications of inheritance. For those unaware, Eclipse Collections is a collections library that provides efficient and more functional data types and methods. Since the organization of data structures is naturally hierarchical (in general we want our data structures to have a set of core invariants with custom implementations and optimizations as additions), this was the perfect exercise in testing my knowledge of inheritance. The complex inheritance tree resulting from Eclipse Collection classes and interfaces inheriting from the standard JDK collections as well as custom implementations resulted in an issue that I’ve never practically encountered. This was the multiple inheritance or diamond inheritance problem.

An example of the multiple inheritance problem I encountered

The issue that I addressed in my first commit was a performance optimization necessitated by the upgrade of Eclipse Collections to Java 8. One of the features introduced in this version of Java is default method implementations for interfaces. Unfortunately, this caused conflicts with Eclipse Collections’ implementations of certain methods as they also extend the standard JDK map interface. Prior to JDK 8, interfaces could only be abstract, thus the multiple inheritance problem was impossible (each class can only extend one class and interfaces cannot have implemented methods; no conflicts here!).

The default forEach implementation for java.util.mapwas inefficient for our custom maps (details can be found here). Therefore, we wanted to override this default implementation and delegate it to a previously optimized method. Instead of overriding for every map, we can leverage inheritance to only implement it once in the MapIterableinterface which is inherited by all the maps. However, once I did this, I got an “… inherits unrelated defaults” compilation error.

/**
* This interface has a multiple inheritance problem!
* There are default implementations for the method forEach
* in both the Map and MapIterable interfaces.
*/
public interface MutableMapIterable<K, V> extends MapIterable<K, V>, Map<K, V>
{
...
/**
* The forEach method does not know which one default
* implementation to know and thus does not compile.
*/
default void forEach(BiConsumer<? super K, ? super V> action)
{
...
}
}

Having never encountered this error before, I was curious as to how to resolve it. With feedback from Eclipse Collections maintainer Craig Motlin, I was reminded to use the .super to call the inherited method of a parent interface.

public interface MutableMapIterable<K, V> extends MapIterable<K, V>, Map<K, V>
{
...
@Override
default void forEach(BiConsumer<? super K, ? super V> action)
{
MapIterable.super.forEach(action);
}
}

As I was fixing the errors in the various files where the conflicts exist, something caught my eye. I noticed that some files required explicit declaration of the parent interfaces, while others were marked as redundant by my IDE. This made me curious about the rules that the compiler resolves with. Revisiting some of the notes from my university studies, I found this page that illustrates the nuances of Java.

One interesting case is extension of a class with a public method and an interface with a default implementation of the method. In this case, the child class would inherit the method from the abstract class and would override the default implementation specified by the interface. Thus, superclasses supersede interfaces. I came across this behavior when I my IDE resolved which method is called without explicit declaration.

Through open source, I was able to discover something I previously overlooked when studying Java. There is tremendous value in learning through trial and compilation error. Open source provides a sandbox and community where I can gain experience and knowledge for free. I am excited about what I will come across next in my journey and am eager to explore!

--

--

Robin Zheng

CS and Math Grad. Socializing my work and interesting things I find. Aspiring pastry chef and enjoyer of good food (https://www.instagram.com/robuns.buns/)