Java Record
The https://openjdk.java.net/jeps/359 outlines a new Java feature that may/will be implemented in some future versions of Java. The JEP suggests having a new type of “class”: record. The sample in the JEP reads as follows:
1 2 3 4 5 6 | record Range( int lo, int hi) { public Range { if (lo > hi) /* referring here to the implicit constructor parameters */ throw new IllegalArgumentException(String.format( "(%d,%d)" , lo, hi)); } } |
Essentially a record will be a class that intends to have only final
fields that are set in the constructor. The JEP as of today also allows any other members that a class has, but essentially a record is a record, pure data and perhaps no functionality at its core. The description of a record is short and to the point and eliminates a lot of boilerplate that we would need to encode such a class in Java 13 or less or whichever version record will be implemented. The above code using conventional Java will look like the following:
01 02 03 04 05 06 07 08 09 10 11 12 | public class Range { final int lo; final int hi; public Range( int lo, int hi) { if (lo > hi) /* referring here to the implicit constructor parameters */ throw new IllegalArgumentException(String.format( "(%d,%d)" , lo, hi)); this .lo = lo; this .hi = hi; } } |
Considering my Java::Geci code generation project this was something that was screaming for a code generator to bridge the gap between today and the day when the new feature will be available on all production platforms.
Thus I started to think about how to develop this generator and I faced a few issues. The Java::Geci framework can only convert a compilable project to another compilable project. It cannot work like some other code generators that convert an incomplete source code, which cannot be compiled without the modifications of the code generator, to a complete version. This is because Java::Geci works during the test phase. To get to the test phase the code has to compile first. This is a well-known trade-off and was a design decision. In most of the cases when Java::Geci is useful this is something easy to cope with. On the other hand, we gain the advantage that the generators do not need configuration management like reading and interpreting property or XML files. They only provide an API and the code invoking them from a test configure the generators through it. The most advantage is that you can even provide call-backs in forms of method references, lambdas or object instances that are invoked by the generators so that these generators can have a totally open structure in some aspects of their working.
Why is this important in this case? The record generation is fairly simple and does not need any complex configuration, as a matter of fact, it does not need any configuration at all. On the other hand, the compilable -> compilable
restrictions are affecting it. If you start to create a record using, say Java 8 and Java::Geci then your manual code will look something like this:
1 2 3 4 5 6 | @Geci ( "record" ) public class Range { final int lo; final int hi; } |
This does not compile, because by the time of the first compilation before the code generation starts the default constructor does not initialize the fields. Therefore the fields cannot be final
:
1 2 3 4 5 6 | @Geci ( "record" ) public class Range { int lo; int hi; } |
Running the generator we will get
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | package javax0.geci.tests.record; import javax0.geci.annotations.Geci; @Geci ( "record" ) public final class Range { final int lo; final int hi; //<editor-fold id="record"> public Range( final int lo, final int hi) { this .lo = lo; this .hi = hi; } public int getLo() { return lo; } public int getHi() { return hi; } @Override public int hashCode() { return java.util.Objects.hash(lo, hi); } @Override public boolean equals(Object o) { if ( this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; Range that = (Range) o; return java.util.Objects.equals(that.lo, lo) && java.util.Objects.equals(that.hi, hi); } //</editor-fold> } |
what this generator actually does is that
- it generates the constructor
- converts the class and the fields to
final
as it is a requirement by the JEP - generates the getters for the fields
- generates the
equals()
andhashCode()
methods for the class
If the class has a void
method that has the same (though case insensitive) name as the class, for example:
1 2 3 4 | public void Range( double hi, long lo) { if (lo > hi) /* referring here to the implicit constructor parameters */ throw new IllegalArgumentException(String.format( "(%d,%d)" , lo, hi)); } |
then the generator will
- invoke that method from the generated constructor,
- modify the argument list of the method to match the current list of fields.
01 02 03 04 05 06 07 08 09 10 11 | public void Range( final int lo, final int hi) { if (lo > hi) /* referring here to the implicit constructor parameters */ throw new IllegalArgumentException(String.format( "(%d,%d)" , lo, hi)); } //<editor-fold id="record"> public Range( final int lo, final int hi) { Range(lo, hi); this .lo = lo; this .hi = hi; } |
Note that this generation approach tries to behave the possible closest to the actual record
as proposed in the JEP and generates code that can be converted to the new syntax as soon as it is available. This is the reason why the validator method has to have the same name as the class. When converting to a real record all that has to be done is to remove the void
keyword converting the method to be a constructor, remove the argument list as it will be implicit as defined in the JEP and remove all the generated code between the editor folds (also automatically generated when the generator was executed first).
The modification of the manually entered code is a new feature of Java::Geci that was triggered by the need of the Record generator and was developed to overcome the shortcomings of the compilable -> compilable
restriction. How a generator can use this feature that will be available in the next 1.3.0 release of Java::Geci will be detailed in a subsequent article.
Takeaway
The takeaway of this article is that you can use Java records with Java 8, 9, … even before it becomes available.
Published on Java Code Geeks with permission by Peter Verhas, partner at our JCG program. See the original article here: Java Record Opinions expressed by Java Code Geeks contributors are their own. |