code generating beans in java
TRANSCRIPT
Bean generationStop writing getters and settersStephen Colebourne, @jodastephenEngineering Lead, OpenGammaSeptember 2016http://blog.joda.org
Stephen Colebourne● Java Champion, regular conference speaker● Best known for date & time - Joda-Time and JSR-310● More Joda projects - http://www.joda.org ● Major contributions in Apache Commons● Blog - http://blog.joda.org ● Worked at OpenGamma for 6 years
Strata, from OpenGamma● Open Source market risk library● Valuation and risk calcs for finance
○ interest rate swap, FRA, CDS
● Great example of Java SE 8 coding stylehttp://strata.opengamma.io/
Why use beans?● Beans are used in most applications● Common denominator between applications & libraries● ORMs (Hibernate, JPA, etc.)● Serialization (Binary, JSON, XML, etc.)● Mappers/configuration (Spring, Dozer, etc.)
What is a bean?● JavaBean specification v1.01, from 1997● Focus on software components, COM/DCOM● Manipulated visually in GUIs● Java components within MS Word/Excel !!!● References to floppy disks !!!
What is a bean?● JavaBeans must extend java.awt.Component● Created via standard factories● No use of casts of instanceof checks● Checked exceptions, not unchecked● Communication via events● Very specific rules around capitalization● Use of BeanInfo and PropertyEditor
What is a mutable bean?● Each tool has subtly different definition● Most agree on
○ no-args constructor○ getters match getXxx()○ setters match setXxx()○ equals() / hashCode() / toString()
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Pattern for mutable bean/** Represents a person. */
public class Person {
/** The forename of the person. */
private String forename;
/** The surname of the person. */
private String surname;
/** The birth date of the person. */
private LocalDate birthDate;
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Pattern for mutable bean /** Creates an empty instance of Person. */
public Person() {}
/** Gets the forename of the person. */
public String getForename() { … }
/** Sets the forename of the person. */
public void setForename(String forename) { … }
// same for surname/birthDate
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Pattern for mutable bean /** Compares this person to another. */
public boolean equals(Object obj) { … }
/** Returns a suitable hash code. */
public int hashCode() { … }
/** Returns a string summary of this object. */
public String toString() { … }
What is an immutable bean?● "Beans" has traditionally implied mutability● Many tools can't handle immutable beans● No setters, may have "withers"● Class must be final, with final fields● Factory or builder instead of constructor
Why immutable?● Thread-safe● Java Memory Model guarantees● No need to trust other methods not to modify● State checked and valid on construction● Nulls can be eliminated
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Pattern for immutable bean/** Represents a person. */
public final class Person {
/** The forename of the person. */
private final String forename;
/** The surname of the person. */
private final String surname;
/** The birth date of the person. */
private final LocalDate birthDate;
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Pattern for immutable bean /** Obtains an instance of Person. */
public Person of(
String surname, String forename, LocalDate date) { … }
/** Gets the forename of the person. */
public String getForename() { … }
/** Returns a copy with the specified forename. */
public Person withForename(String forename) { … }
// same for surname/birthDate
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Pattern for immutable bean /** Compares this person to another. */
public boolean equals(Object obj) { … }
/** Returns a suitable hash code. */
public int hashCode() { … }
/** Returns a string summary of this object. */
public String toString() { … }
Pattern for immutable bean● May prefer to have a builder instead of a factory
// factory
Person person = Person.of("Stephen", "Colebourne", date);
// or builder
Person person = Person.builder()
.forename("Stephen")
.surname("Colebourne")
.birthDate(date)
.build();
What is a VALJO?● POJO - Plain Old Java Object● VALJO - Value Java Object
○ http://blog.joda.org/2014/03/valjos-value-java-objects.html
● No use of identity - equal is interchangeable● Immutable bean with extra restrictions
○ may be suitable for conversion to value types in Java 10/11○ logical state clearly defined and used in equals()/hashCode()○ comparable consistent with equals()○ constructor must be private○ formal string representation that can be parsed
How to create beans● Lots of different ways to create beans● Best option depends on use case● Mutable vs Immutable
Option 1 - Manual● Write each bean manually● Deathly boring● Double deathly boring for immutable beans● Error-prone● Probably not tested or code reviewed
Option 2 - Another JVM language● Kotlin code much shorter
// Kotlin immutable bean
data class Person(
val forename: String,
val surname: String,
val birthDate: LocalDate)
Option 2 - Another JVM language● Groovy code much shorter
// Groovy mutable bean
class Customer {
String forename
String surname
LocalDate birthDate
Option 3 - Language change● New language feature in Java● Perhaps like Kotlin/Groovy● Doesn't help us now● Likely to be restrictive
Option 4 - IDE generation● Eclipse / IntelliJ / NetBeans can generate the code
○ Eclipse uses Ctrl+Alt+S followed by R / O / H / S○ IntelliJ uses Alt+Insert
● One time generation, doesn't handle change● Can you express field is not null?● Can you generate immutable builders?● Still not tested or code reviewed
AutoValue● Annotation processor● Open Source, from Google
○ https://github.com/google/auto/tree/master/value
Annotation processing● Additional step during compilation● Compiler calls annotation processor● Processor generates additional files● If generated file is a .java file then it is compiled
Annotation processing
Person(abstract)
PersonImpl(concrete)
Compilation findsannotations andcode generates
You writeabstract class
Annotation processing● Maven
○ just add the dependency
● Eclipse with Maven○ install m2e-apt plugin○ turn it on in the preferences
● IntelliJ with Maven○ turn it on in the preferences
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
AutoValue: your code@AutoValuepublic abstract class Person {
public static Person of(String name, LocalDate date) {
return new AutoValue_Person(name, date); }
public abstract String getName();
public abstract LocalDate getBirthDate();
}
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
AutoValue: generated@Generated("com.google.auto.value.processor.AutoValueProcessor")
final class AutoValue_Person extends Person {
private final String name;
private final LocalDate birthDate;
// constructor, getters, equals, hashCode, toString
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
AutoValue builder: your code@AutoValuepublic abstract class Person {
public static Builder builder() {
return new AutoValue_Person.Builder(); }
@AutoValue.Builder
public static abstract class Builder {
public abstract Builder name(String name);
public abstract Builder birthDate(LocalDate date);
public abstract Person build(); } public abstract Builder toBuilder();
AutoValue options● Handles name() or getName() convention● Can override (underride) equals/hashCode/toString● Can add any other method● Can change fields to nullable using @Nullable● Supports memoized fields● Builder can have sub-builders for collections● Pattern to handle builder validation● Extensions API, but undocumented
AutoValue pros/cons● Callers use Person like a normal bean
○ but they can see class is abstract
● Generated code is package scoped○ you must write outline builder and/or static factory method○ quite a lot of code still to write
● Simple, does sensible thing for most use cases○ not that many options
● Only for immutable beans
Immutables● Annotation processor● Open Source
○ https://immutables.github.io/
● Reacts to use Guava if available
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Immutables: your [email protected] interface Person {
String getName();
LocalDate getBirthDate();
}
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Immutables: generated@SuppressWarnings("all")
@Generated({"Immutables.generator", "ImmutablePerson"})
public final class ImmutablePerson implements Person {
private final String name;
private final LocalDate birthDate;
private ImmutablePerson(String name, LocalDate birthDate) {
this.name = name;
this.birthDate = birthDate;
}
// getters, withers, equals, hashCode, toString, builder
Immutables options● Can generate from abstract class or interface● Handles name() or getName() convention● Can override (underride) equals/hashCode/toString● Can add any other method● Pre-computed hash code● Instance interning● and lots more!
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Immutables: hide [email protected]@Value.Style(visibility = PACKAGE)public abstract class Person extends WithPerson {
public String getName();
public LocalDate getBirthDate();
public static class Builder
implements ImmutablePerson.Builder {}
}
Immutables pros/cons● Many options and ways to generate
○ takes time to choose best option
● Callers see and use generated class (by default)○ hiding generated class possible if you write more code
● Mutable beans supported○ more like builders, do not follow JavaBeans spec
Lombok● Internal APIs● Open Source
○ https://projectlombok.org/
● Uses agents and annotation processors● Works best with Eclipse, but requires installing
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Lombok: your code - mutable@Datapublic class Person {
private String name;
private LocalDate birthDate;
}
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Lombok: generated- mutable@Datapublic class Person {
private String name;
private LocalDate birthDate;
// constructor, getters, setters, equals, hashCode, toString
}
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Lombok: your code - immutable@Valuepublic class Person {
private String name;
private LocalDate birthDate;
}
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Lombok: generated - immutable@Valuepublic final class Person {
private final String name;
private final LocalDate birthDate;
// constructor, getters, equals, hashCode, toString, builder
}
Lombok options● Over 15 annotations and tweaks● Use other annotations for more control● Can add any other method
Lombok pros/cons● No second class, proper immutable bean
○ resulting bean is exactly the same as manually written
● Works best with Maven and Eclipse○ installation in Eclipse not via a standard plugin
● Uses internal API hackery○ higher risk option
● Generated code is invisible○ cannot step in when debugging○ can "delombok" to code generated code
Joda-Beans● Same-file regenerator● Open Source, by me @jodastephen
○ http://www.joda.org/joda-beans/
● Adds autogenerated block to your code● Generation using Maven/Gradle, on-save in Eclipse
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Joda-Beans: your code - mutable@BeanDefinitionpublic class Person implements Bean {
@PropertyDefinition
private String name;
@PropertyDefinition
private LocalDate birthDate;
}
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Joda-Beans: generated - mutable@BeanDefinitionpublic class Person implements Bean {
@PropertyDefinition
private String name;
@PropertyDefinition
private LocalDate birthDate; // ---- AUTOGENERATED START ----
// getters, setters, equals, hashCode, toString, properties
// ----- AUTOGENERATED END -----
}
Properties● C# and most other languages have properties● Higher level than a field● Bean is a set of properties● Can list, get and set properties
○ like reflection
● Very useful abstraction for frameworks
Joda-Beans properties● Bean provides abstraction for properties● MetaBean acts like Class● Can loop over MetaProperty for each property
○ meta bean acts as a hash-map of property name to property○ no reflection
● BeanBuilder allows a bean to be created○ this allows immutable beans to be created one property at a time
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Joda-Beans: your code immutable@BeanDefinitionpublic class Person implements ImmutableBean {
@PropertyDefinition(validate = "notNull")
private String name;
@PropertyDefinition(validate = "notNull")
private LocalDate birthDate;
}
// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});
Joda-Beans: generated immutable@BeanDefinitionpublic class Person implements ImmutableBean {
@PropertyDefinition(validate = "notNull")
private String name;
@PropertyDefinition(validate = "notNull")
private LocalDate birthDate; // ---- AUTOGENERATED START ----
// getters, equals, hashCode, toString, properties, builder
// ----- AUTOGENERATED END -----
}
Joda-Beans options● Annotations allow field-level and bean-level control● Control scope/style for getters/setters● Supports pragmatic optional usage● Can add any validation code● Defaults/cross-validation for immutable bean builders● Can default one property from another in builder● Can fully override constructor
Joda-Beans pros/cons● No second class, proper immutable bean
○ resulting bean is same as manually written
● Adds abstraction for properties without reflection○ key reason to use Joda-Beans, but requires runtime dependency
● Built in XML, JSON, Binary serialization● Generated code is visible for debugging
○ more code to checkin, but easy to ignore in review○ proper Javadoc
● Plugin for Maven, Gradle and Eclipse with M2E○ anyone want to volunteer to write an IntelliJ one?
Comparisons● Annotation processor
○ AutoValue○ Immutables
● Internal APIs○ Lombok
● Same file regenerator○ Joda-Beans
Comparisons● All can generate a lot of useful code
○ getters, factories, builders, equals, hashCode, toString
● Some can generate mutable beans● Only Joda-Beans generates C# style properties● Each project has a different trade-off
Same class vs Generated class● Same class (Joda-Beans, Lombok)
○ your code involves writing fields○ caller sees concrete class○ properly immutable
● Generated class (AutoValue, Immutables)○ your code involves writing methods (more code)○ caller sees abstract class or interface○ is it really immutable?○ IDE rename class typically breaks code
Collections● Immutable beans should use Guava ImmutableList● Builder needs to take List and convert internally● All except Lombok do this correctly
Installation requirements● AutoValue & Immutables use annotation processor
○ must be configured in IDE, extra plugin for Eclipse
● Lombok uses internal API hackery○ requires specific Eclipse installation
● Joda-Beans runs as separate code regenerator○ for Eclipse, just needs standard M2E
AutoValue vs Immutables● AutoValue
○ you write abstract class○ generated class is package-scoped ○ cannot generate static factory○ must write builder outline manually
● Immutables○ you write interface or abstract class○ generated class is public (can be made private or package-scoped)○ static factory and builder in generated class○ lots of flexibility
Valid on checkout● AutoValue, Immutables, Lombok
○ code invalid in IDE on checkout unless configured○ only your code checked in
● Joda-Beans○ code valid in IDE on checkout, even if not configured○ your code and generated code checked in
Evaluate yourself● GitHub project with everything setup for comparison
○ https://github.com/jodastephen/compare-beangen
● Includes 4 tools discussed○ plus VALJOGen, POJOmatic, Eclipse wizards and IntelliJ wizards
● Good project to play with each tool
Summary● All four tools have their sweet spot and trade off
○ AutoValue - simplicity, abstract class○ Immutables - comprehensive, abstract class or interface○ Lombok - almost a language extension, hacky implementation○ Joda-Beans - C# style properties, runtime dependency
Summary - personal view● Use Joda-Beans if you like properties
○ Code should be valid on checkout○ Immutable beans should be final○ Want C# style properties○ Hence I wrote and use Joda-Beans
● Otherwise, use Immutables
@jodastephenhttp://blog.joda.org