May 29, 2016

Project Lombok: Making Java more Groovy (part 2 of 3)


Pages: 1 2 3

@Canonical

Groovy:
@CompileStatic
@Canonical
class CanonicalGroovy {
    String name
    int age = 10
}

new CanonicalGroovy("My name", 20).equals(new CanonicalGroovy("My name", 20));
Lombok:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CanonicalLombok {
    private String name;
    private int age = 10;
}


new CanonicalLombok("My name", 20).equals(new CanonicalLombok("My name", 20)); // true
  • In Groovy, @Canonical combines @ToString, @EqualsAndHashCode and @TupleConstructor (with getter / setter generation enabled by plain Groovy already),
  • In Lombok, @Data combines @ToString, @EqualsAndHashCode, @Getter / @Setter for all fields, and @RequiredArgsConstructor.
In both cases, adding the individual annotations additionally overrides using their default values by @Canonical / @Data.

This really is just a catch-all solution to quickly build POJOs. However, at least in Groovy practice I’ve found that typically, you really want to use the individual annotations anyway for more specific code generation control. For Lombok, the use of @RequiredArgsConstructor is unfortunate because it forces you to still manually apply @AllArgsConstructor + @NoArgsConstructor if you want to replicate the Groovy behavior which seems more sensible.

Hence, unfortunately, this is almost useless in both libraries.

@Immutable

Groovy:
@Immutable
class ImmutableGroovy {
    String name
    int age = 10
}


new ImmutableGroovy();
new ImmutableGroovy("My name", 20).equals(new ImmutableGroovy("My name", 20)); // true
Lombok:
@Value
@AllArgsConstructor
public class ImmutableLombok {
    private String name;
    private int age = 10;

    public ImmutableLombok() {
        this.name = null;
    }
}

new ImmutableLombok();
new ImmutableLombok("My name").equals(new ImmutableLombok("My name"));
In Groovy, @Immutable applies @Canonical with generating getters only and creating “immutable” POJOs. In Lombok, @Value does the same with the @Data equivalent, plus all the fields become final, hence the output can derive from the Groovy version.

Immutable setters

Groovy: (none)

Lombok:
@NoArgsConstructor
@AllArgsConstructor
@Getter @Setter
@Wither
public class WitherLombok {
    private String name = "Default name";
    private int age;
}

WitherLombok lombok = new WitherLombok();
lombok.setAge(20);
WitherLombok lombok2 = lombok.withName("My name");
lombok2.getName(); // My name
lombok2.getAge(); // 20
Lombok’s @Wither generates and “immutable setter” for fields. Although this works similar to @Setter, the setter is invoked on a new object (a clone) previously constructed from calling the constructor with all the instances’ current field values. It’s hence best practice to use this in conjunction with @AllArgsConstructor.

Unfortunately, there is no Groovy equivalent.

@Builder

Groovy:
@Builder
class BuilderGroovy {
    String name
    int age
    List<String> siblings = []
}

BuilderGroovy groovy = BuilderGroovy.builder()
    .name("My name").age(20).siblings(Arrays.asList("First", "Second"))
    .build();
Lombok:
@Builder
public class BuilderLombok {
    String name;
    int age;
    @Singular
    List<String> siblings = new ArrayList<>();
}

BuilderLombok lombok = BuilderLombok.builder()
    .name("My name").age(20).sibling("First").sibling("Second")
    .build();
@Builder auto-generates the otherwise boilerplate-intense builder pattern both in Groovy and Lombok.

With the @Singular annotation, Lombok provides an enhancement to further facilitate dealing with collections. For reasons unknown, the respective feature request for Groovy has been rejected.

In case you’re searching for an equivalent to the Groovy Builder’s more concise SimpleStrategy, you’ll have to use Lombok’s combined @Accessors(fluent=true) @Getter @Setter:

Groovy:
@Builder(builderStrategy=SimpleStrategy, prefix="")
class SimpleBuilderGroovy {
    String name
    int age
    List<String> siblings = []
}

SimpleBuilderGroovy simpleGroovy = new SimpleBuilderGroovy()
    .name("My name").age(20);
Lombok:
@Accessors(fluent=true)
@Getter @Setter
public class SimpleBuilderLombok {
    String name;
    int age;
    List<String> siblings = new ArrayList<>();
}

SimpleBuilderLombok simpleLombok = new SimpleBuilderLombok()
    .name("My name").age(20);

Make exceptions unchecked

Groovy:
class ExceptionsGroovy {
    public static void tryToDoSomething() {
        doSomethingDangerous(); // no try-catch!
    }
    
    private static void doSomethingDangerous() { // no throws!
        throw new UselessException();
    }
    
    public static class UselessException extends Exception {
        
    }
}
Lombok:
public class ExceptionsLombok {
    public static void tryToDoSomething() {
        doSomethingDangerous(); // no try-catch!
    }
    
    @SneakyThrows
    private static void doSomethingDangerous() { // no throws!
        throw new UselessException();
    }
    
    public static class UselessException extends Exception {
        
    }
}
By annotating a method with @SneakyThrows, you replicate Groovy’s exception handling default behavior with Lombok, that is to make exception checking optional, even for checked exceptions.

Although in some situations, checked exceptions may be used effectively as a tool to make your business code more robust, some people will claim that the concept of checked exceptions in Java is all wrong per se (which becomes obvious when working with code which was designed without a clear idea about what checked exceptions are for), and advocate an optional-checked language design like in Groovy, Scala, or C#.

This really is a pinch by Lombok towards creating Java code which feels more “dynamically typed”. You should have a deep understanding about Java exception design, and use it sparingly even then. Note that in contrast to exceptions design with Groovy, this is a very local opt-out hook, and it can easily devolve into exception micro-management.

@Log

Groovy: (none)

Lombok:
@Log
public class LoggingLombok {
    public static void logError() {
        log.severe("An error occurred");
    }
}
In Lombok, @Log and its alternatives add a logger field which can be of any of the major Java logging frameworks classes.

Unfortunately, there is no Groovy equivalent.

Pages: 1 2 3