Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Battle of Immutables

Battle of Immutables

AutoValue vs Lombok

DroidconUK 2016 Lightning Talk

Avatar for Evelina Vrabie

Evelina Vrabie

October 27, 2016

Other Decks in Technology

Transcript

  1. Battle Of Immutables AutoValue vs Lombok Evelina Vrabie - Consumer

    Team Carlo Marinangeli - Restaurant Team DELIVEROO
  2. What Annotation processors help you remove boilerplate. • no more

    getters • no more setters • no more hashCode(), equals(), toString() • no more builders
  3. How • How they generate code • Features offered •

    How much code to write • Number of methods generated
  4. How Lombok is not a standard annotation processor. Modifies the

    Abstract Syntax Tree, allowing to generate new methods or inject new code.
 
 It does not generate new classes.
  5. Features Code generation • getters (and setters) • equals, hashCode

    • toString • builder Extensions • Parcelable • Json serialization • more...
  6. public class Card { private final String number; private final

    String expiryDate; private final int cvc; private final String nameOnCard; private Card(Builder builder) {} public String getNumber() {} public String getExpiryDate() {} public int getCvc() {} public String getNameOnCard() {} @Override public boolean equals(Object o) {} @Override public int hashCode() {} @Override public String toString() {} public static Builder builder() {} public Builder copy() {} public static final class Builder { private String number; private String expiryDate; private int cvc; private String nameOnCard; public Builder number(String val) {} public Builder expiryDate(String val) {} public Builder cvc(int val) {} public Builder nameOnCard(String val) {} public Card build() {} } } Some code How we used to write
  7. @AutoValue public abstract class Card { public abstract String number();

    public abstract String expiryDate(); public abstract int cvc(); public abstract String nameOnCard(); public abstract Builder copyBuilder(); public static Builder builder() {...} @AutoValue.Builder public abstract static class Builder { public abstract Builder number(String number); public abstract Builder expiryDate(String expiryDate); public abstract Builder cvc(int cvc); public abstract Builder nameOnCard(String nameOnCard); public abstract Card build(); } } @lombok.Value() @lombok.Builder(toBuilder = true) public class Card { private final String number; private final String expiryDate; private final int cvc; private final String nameOnCard; } Some code How we write now
  8. The cost Method reference count • How many methods does

    the generated code have? • How does the pojo compare to AutoValue and to Lombok?
  9. The cost Method reference count • dx • dexdump •

    dex-method-counts • dex-method-list
  10. The cost Method reference count • ./gradlew compileDebugSources • cd

    app/build/intermediates/classes/debug • dx --dex --output=output.dex com/deliveroo/.../ Order.class • dexdump -f output.dex • look for `method_ids_size`
  11. The cost dexdump Processing 'output.dex'... Opened 'output.dex', DEX version '035'

    DEX file header: magic : 'dex\n035\0' checksum : 60d6b095 signature : c81d...e4a5 file_size : 1420 header_size : 112 link_size : 0 link_off : 0 (0x000000) string_ids_size : 34 string_ids_off : 112 (0x000070) type_ids_size : 10type_ids_off : 248 (0x0000f8) proto_ids_size : 11 proto_ids_off : 288 (0x000120) field_ids_size : 1 field_ids_off : 420 (0x0001a4) method_ids_size : 13 method_ids_off : 428 (0x0001ac) class_defs_size : 1 class_defs_off : 532 (0x000214) data_size : 856 data_off : 564 (0x000234)
  12. The cost dex-method-counts Processing output.dex Read in 13 method IDs.

    <root>: 13 com: 6 deliveroo: 6 battleofimmutables: 6 model: 6 java: 7 lang: 7 Overall method count: 13
  13. The cost dex-method-list com.deliveroo.battleofimmutables.model.pojo.Card <init>(Card$Builder) com.deliveroo.battleofimmutables.model.pojo.Card <init>(Card$Builder, Card$1) com.deliveroo.battleofimmutables.model.pojo.Card builder()

    → Card$Builder com.deliveroo.battleofimmutables.model.pojo.Card builder(Card) → Card$Builder com.deliveroo.battleofimmutables.model.pojo.Card equals(Object) → boolean com.deliveroo.battleofimmutables.model.pojo.Card getCvc() → int com.deliveroo.battleofimmutables.model.pojo.Card getExpiryDate() → String com.deliveroo.battleofimmutables.model.pojo.Card getNameOnCard() → String com.deliveroo.battleofimmutables.model.pojo.Card getNumber() → String com.deliveroo.battleofimmutables.model.pojo.Card hashCode() → int com.deliveroo.battleofimmutables.model.pojo.Card toString() → String com.deliveroo.battleofimmutables.model.pojo.Card$Builder <init>(Card$1) com.deliveroo.battleofimmutables.model.pojo.Card$Builder access$000(Card$Builder) → String com.deliveroo.battleofimmutables.model.pojo.Card$Builder access$002(Card$Builder, String) → String
  14. The cost Method count Pojo AutoValue Lombok 1 field 6

    (+7) = 13 7 (+4) = 11 7 (+5) = 12 4 fields 24 (+12) = 36 22 (+14) = 36 10 (+10) = 20 15 fields 158 (+18) = 176 188 (+26) = 220 101 (+12) = 113
  15. The cost access$ ??? 11: getfield #4 // Field number:Ljava/lang/String;

    14: invokestatic #28 // Method com/deliveroo/battleofimmutables/ model/pojo/Card$Builder.access$002:(Lcom/deliveroo/battleofimmutables/ model/pojo/Card$Builder;Ljava/lang/String;)Ljava/lang/String;
  16. Handwritten code Pojo AutoValue Lombok 15 fields 307 50 19

    AutoValue gives 85% less lines Lombok gives 94% less lines
  17. The benefits • The generated code is tested • The

    generated code is smaller • Forward-compatible code • Human-readable generated code* • Helps prevent null references • Helps eliminate coding mistakes • Easier assertions in tests *with a plugin, for Lombok
  18. AutoValue + Gson apt 'com.ryanharter.auto.value:auto-value-gson:0.4.2' provided 'com.ryanharter.auto.value:auto-value-gson:0.4.2' @GsonTypeAdapterFactory public abstract

    class OrderGsonAdapterFactory implements TypeAdapterFactory { public static OrderGsonAdapterFactory create() { return new AutoValueGson_OrderGsonAdapterFactory(); } } // auto-value-gson extension public static TypeAdapter<Order> typeAdapter(Gson gson) { return new GsonTypeAdapter(gson); }
  19. Features in Lombok • @Getter • @Setter • @EqualsAndHashCode •

    @ToString • @[No|Required|All]ArgsConstructor • @Value => @Getter, @EqualsAndHashCode, @ToString, @AllArgsConstructor • @Data => @Value @Setter • @Synchronized
  20. Features in Lombok • @Getter(lazy=true) • @Builders - @Builder(toBuilder =

    true) • @Singular • @FieldDefault(makeFinal=true, level=PRIVATE) • @Wither • @Cleanup • @NonNull
  21. Links • https://github.com/mihaip/dex-method-counts • https://projectlombok.org/features/ • https://immutables.github.io • https://github.com/google/auto/blob/master/value/userguide/index.md •

    https://github.com/JakeWharton/dex-method-list • https://github.com/square/wire • https://github.com/Microsoft/thrifty • https://github.com/mg6maciej/hrisey • https://github.com/evelyne24/BattleOfImmutables
  22. Q&A • Does it work with Jack toolchain? • Not

    from what we’ve tried… • ...but it works with Retrolambda • Can AutoValue/Lombok implement interfaces? • Yes • … with default methods? • Yes
  23. Pitfalls • AutoValue & Json deserialization: watch out for @Nullable

    annotations • AutoValue & Parcelable: watch out for non-parcelable fields • Lombok: all fields are nullable - must add @NonNull annotations • Immutability: watch out for collections (make a defensive copy in the builder)