Mixins With Pure Java
Join the DZone community and get the full member experience.
Join For Freeimplementation of mixins using aop (aspectj) or source-code modification (jamopp)
in object-oriented programming languages, a mixin refers to a defined amount of functionality which can be added to a class. an important aspect of this is that it makes it possible to concentrate more on the properties of a particular behaviour than on the inheritance structures during development.
in scala for example, a variant of mixins can be found under the name of “traits”. although java does not provide direct support for mixins, these can easily be added on with a few annotations, interfaces and some tool support.
occasionally you read in a few online articles that mixins are incorporated into java version 8. unfortunately, this is not the case. a feature of the lambda project ( jsr-335 ) are the so-called “virtual extension methods” (vem).
whilst these are similar to mixins, they do have a different background and are significantly more limited in functionality. the motivation for the introduction of vems is the problem of backward compatibility in the introduction of new methods in interfaces .
as “real” mixins are not expected in the java language in the near future, this article intends to demonstrate how it is already possible to create mixin support in java projects now, using simple methods. to do this, we will discuss two approaches: using aop with aspectj and using source-code modification with jamopp .
why not just inheritance?
when asked at an event “ what would you change about java if you could reinvent it? ” james gosling , the inventor of java is said to have answered “ i would get rid of the classes “.
after the laughter had died down, he explained what he meant by that: inheritance in java, which is expressed with the “extends” relationship, should – wherever possible – be replaced by interfaces [ why extends is evil ].
any experienced developer knows what he meant here: inheritance should be used sparingly. it is very easy to misuse it as a technical construct to reuse code, and not to model a technically motivated parent-child relationship with it.
but even if one considers such a technically motivated code reuse as legitimate, one quickly reaches its limits, as java does not allow multiple inheritance.
mixins are always useful if several classes have similar properties or define a similar behaviour, but these cannot be reasonably modelled simply via slim relationship hierarchies.
in english, terms which end in “able” (e.g. “sortable”, “comparable” or “commentable”) are often an indicator for applications of mixins. also, when starting to write “utility” methods in order to avoid a code duplication in the implementation of interfaces, this can be an indication of a meaningful case of application.
mixins with aop
so-called inter-type declarations are an extremely simple possibility for implementing mixins, offered by the aspectj eclipse project. with these, it is possible – among other things – to add new instance variables and methods to any target class.
this will be shown in the following, based on a small example in listing 1. for this, we will use the following terms:
- basis-interface describes the desired behaviour. classes which the mixin should not use can use this interface.
- mixin-interface intermediate interface used in the aspect and implemented by classes which the mixin is to use.
- mixin-provider aspect which provides the implementation for the mixin.
- mixin-user class which uses (implements) one or more mixin interfaces.
// === listing 1 === /** base-interface */ public interface named { public string getname(); } /** mixin-interface */ public interface namedmixin extends named { } /** mixin-provider */ public aspect namedaspect { private string namedmixin.name; public final void namedmixin.setname(string name) { this.name = name; } public final string namedmixin.getname() { return name; } } /** mixin-user */ public class myclass implements namedmixin { // could have more methods or use different mixins }
listing 1 shows a complete aop-based mixin example. if aspectj is set up correctly, the following source text should compile and run without errors:
myclass myobj = new myclass(); myobj.setname("abc"); system.out.println(myobj.getname());
it is possible to work quite comfortably with aop variants, but there are also a few disadvantages which will be explored here.
first of all, inter-type declarations cannot deal with generic types in the target class. this is not absolutely necessary in many cases, but can be very practical. for example, it is possible to define the “named” interface just as well with a generic type instead of “string”. it would then define the behaviour for any name types. the class used could then determine how the type of name should look.
a further disadvantage is that the methods generated by aspectj follow their own naming conventions. this makes it difficult to search the classes using reflection, as you would have to reckon with method names such as “ajc$intermethoddispatch …”
last but not least, without the support of the development environment, you cannot see the source code in the target class and are dependent on the interface declaration alone. this could, however, be seen as an advantage, since the using classes contain less code.
appearance: java model parser and printer (jamopp)
an alternative to the implementation of mixins with aspektj is offered by java model parser and printer (jamopp). simply put, jamopp can read java source code, present it as an object graph in the memory and transform (i.e. write) it back into text.
with jamopp, it is therefore possible to programmatically process java code and thus automate refactoring or implement your own code analyses, for example. technologically, jamopp is based on the eclipse modeling framework (emf) and emftext . jamopp is jointly developed by the technical university of dresden and devboost gmbh and is freely available on github as an open-source project.
mixins with jamopp
in the following, we would like to take up the example from the aop mixins and expand this slightly. for this, we will first define a few annotations:
- @mixinintf indicates a mixin interface.
- @mixinprovider indicates a class which provides the implementation for a mixin. the implemented mixin interface is specified as the only parameter.
- @mixingenerated marks methods and instance variables which have been generated by the mixin. the only parameter is the class of the mixin
- provider.
in the following, we will also be expanding the interfaces and classes from listing 1 with a generic type for the name. only the class using the mixin defines which concrete type the name should actually have.
// === listing 2 === /** base-interface (extended with generic parameter) */ public interface named<t> { public t getname(); } /** mixin-interface */ @mixinintf public interface namedmixin<t> extends named<t> { } /** mixin-provider */ @mixinprovider(namedmixin.class) public final class namedmixinprovider<t> implements named<t> { @mixingenerated(namedmixinprovider.class) private t name; @mixingenerated(namedmixinprovider.class) public void setname(t name) { this.name = name; } @override @mixingenerated(namedmixinprovider.class) public t getname() { return name; } } /** special name type (alternative to string) */ public final class myname { private final string name; public myname(string name) { super(); if (name == null) { throw new illegalargumentexception("name == null"); } if (name.trim().length() == 0) { throw new illegalargumentexception("name is empty"); } this.name = name; } @override public string tostring() { return name; } }
in the class which the mixin is to use, the mixin interface is now implemented again as shown in listing 3. in order to “blend” the fields and methods defined by the mixin provider into the myclass class, a code generator is used.
with the help of jamopp, this modifies the myclass class and adds the instance variables and methods provided by the mixin provider.
// === listing 3 === /** mixin-user */ public class myclass implements namedmixin<myname> { // could have more methods or use different mixins }
in doing this, the code generator does the following. it reads the source code of every class, similarly to the normal java compiler, and, in doing so, examines the amount of implemented interfaces.
if a mixin interface is present, i.e. an interface with the annotation @mixinintf, the corresponding provider is found and the instance variables and methods are copied into the class which is implementing the mixin.
in order to initiate the generation of mixin codes, there are currently two options: using an eclipse plug-in directly when saving or as a maven plug-in as part of the build.
installation instructions and the source code of both plug-ins can be found on github in the small srcmixins4j project. there is also an on-screen video available there, which demonstrates the use of the eclipse plug-in. listing 4 shows the how the modified target class then looks.
// === listing 4 === /** mixin-user */ public class myclass implements namedmixin<myname> { @mixingenerated(namedmixinprovider.class) private myname name; @mixingenerated(namedmixinprovider.class) public void setname(myname name) { this.name = name; } @override @mixingenerated(namedmixinprovider.class) public myname getname() { return name; } }
if the mixin interface is removed from the “implements” section, all of the provider’s fields and methods annotated with “@mixingenerated” will be deleted automatically. generated code can be overridden at any time by removing the “@mixingenerated” annotation.
click on the following image to open a flash video that demonstrates the eclipse plugin:
conclusion
as native support of mixins in the java language standard is not expected in the foreseeable future, it is currently possible to make do with just some aop or source-code generation. which of the two options you choose depends essentially on whether you prefer to keep the mixin code separate from your own application code or whether you want them directly in the respective classes.
in any case, the speed of development is significantly increased and you will concentrate less on inheritance hierarchies and more on the definition of functional behaviour.
neither approach is perfect. in particular, conflicts are not automatically resolved. methods with the same signature from different interfaces which are provided by different mixin providers will, for example, lead to an error in a class which uses both mixins.
those seeking anything more would have to transfer to another language with native mixin support, such as scala.
Published at DZone with permission of Michael Schnell, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments