NB: DO NOT use this in production code yet, its far too immature.
What? Why?
The Java programming language allows classes to inherit from multiple interfaces. That is to say that they can be multiple different types of things. What this project does is allow you to inherit from multiple implementations. This allows code to be more easily shared throughout a different class hierarchy. Historically some OOP programming language, such as C++, have had multiple implementation inheritance but the Java language designers considered it too complex a feature with many downsides. More recently, Scala has renewed interest in multiple implementation inheritance, through its traits system. I was somewhat interested in scratching an itch, and seeing how far one can change a fundamental language design choice, like multiple inheritance, in a mature language, like Java, and so I wrote a simple multiple inheritance in Java implementation. If other people can have fun with it - great! If you don't like or want multiple inheritance in Java then thats cool - just don't use this project.
How do I use it?
The basic design idea in multi-inherit is that Java has multi inheritance for interfaces, but not for classes - ie the implementation. Many modern Java programs use dependency injection, such as through Guice or Spring, in order to create specific implementations for interfaces. Multi-Inherit allows programmers to request an instance of an interface, for which a concrete class is generated at runtime, that implements the parents of the interface. Thats a lot to take in at once, so here's a simple example. You have two classes called A and B that your class Combined wishes to inherit from. In order to implement this in multi inherit we declare A and B as interfaces, with concrete implementations:
@ImplementedBy(AImpl.class) interface A { public String a(); } class AImpl implements A { public String a() { return "a"; } } @ImplementedBy(BImpl.class) interface B { public String b(); } class BImpl implements B { public String b() { return "b"; } }
Now in order to generate a class that inherits from both of these, you simply need to write an interface, that extends A and B:
interface Combined extends A,B {}
You can simply inject instances of Combined, using Guice, and a concrete implementation will be generated that proxies A and B. For example:
Combined c = injector.getInstance(Combined.class); Assert.assertEquals("a",c.a()); Assert.assertEquals("b",c.b());
Of course, if you're using it in practice, you will probably use annotation based injection, for example:
@Inject Combined c; ... System.out.println(c.a());
Traits
Of course, this form of multiple inheritance is quite error prone, one of the reasons why it was left out of Java to begin with. The Scala Programming Language has an approach called traits, in which its possible to add some implementation to an interface, and still inherit from multiple traits. In our system a trait can look like:
@TraitWith(SimilarityImpl.class) public interface Similarity { public boolean isSimilar(Object other); public boolean isNotSimilar(Object other); } public abstract class SimilarityImpl implements Similarity { @Override public boolean isNotSimilar(final Object other) { return !isSimilar(other); } }
You can then combine a trait with a class:
public abstract class Number implements Similarity { ... @Override public boolean isSimilar(final Object other) { if (other instanceof Number) { final Number num = (Number) other; return Math.abs(val - num.val) < 5; } return false; } }
Multiple Overrides
One of the problems of multiple inheritance involves what choice should be made if you have two methods with the same signature, in different classes. Multi-inherit uses the ordering of interfaces in order to disambiguate this, so for example in "interface Combined extends A,B, C {}" methods in A would be preferred to B or C. You can also use an @Prefer annotation in order to specify which parent to inherit a method from, for example:
@Prefer(C.class) public String b();
Will pick the implementation of the method b() in class C to either A or B.
How it works
The module registers itself as a Producer with Guice for relevant classes, and is called when getInstance is called. This then either uses a Proxy and reflection or code generation using the ASM Library in order to construct an adapter class. The adapter class then simply proxies all the methods of the parent classes or traits. Some of the more advanced features, such as traits, only work with the code generation backend, which you're advised to use.
Alternative Approaches
If you wish to avoid Guice, you could keep the same ideas, but implement against another DI framework, such as Spring. You can also implement this kind of rewrite/transformation based system using some kind of Aspect Oriented Programming, or Java-Agents. You'd probably have to use a Factory Class explicitly in order to undertake that approach. Not using a DI framework also prevents some of the cool extensions that you could do, such as Parent Injection.
Caveats
- No Parent Injection! A really cool feature would be to be able to use DI to specify which trait implementation to inherit from at runtime. You could do this by allowing @TraitWith to take some kind of Guice Key, and then specifying that Key in a Module. That way you could directly change your class hierarchy for testing purposes, whilst still maintaining the same interface.
- You have to explicitly specify a list of classes to register your Guice module with, instead of them being auto-detected on the classpath.
- The project has some junit tests, but is far from exhaustively tested. You can probably break it.
- You can get a class hierarchy with type checking errors to compile. You will then get a Guice ProvisionException at runtime. These type checking errors can be identified statically, and its relatively easy to do so, but I simply haven't had time to implement this feature yet.
- The approach is really abstract of Guice but the current implementation is Guice-Oriented. It would be nice if there was Spring integration.
- You can't refer to the fields inside a trait from combined implementing classes.
- The reflection based backend is limited in terms of what it can do, and probably should just be deprecated in favour of the code generation backend.
- No performance testing or optimisation has been done.