Thursday, February 20, 2020

Lombok @Cleanup Example


Automatic resource management: Call your close() methods safely with no hassle.

Overview

You can use @Cleanup to ensure a given resource is automatically cleaned up before the code execution path exits your current scope. You do this by annotating any local variable declaration with the @Cleanup annotation like so:
@Cleanup InputStream in = new FileInputStream("some/file");
As a result, at the end of the scope you're in, in.close() is called. This call is guaranteed to run by way of a try/finally construct. Look at the example below to see how this works.
If the type of object you'd like to cleanup does not have a close() method, but some other no-argument method, you can specify the name of this method like so:
@Cleanup("dispose") org.eclipse.swt.widgets.CoolBar bar = new CoolBar(parent, 0);
By default, the cleanup method is presumed to be close(). A cleanup method that takes 1 or more arguments cannot be called via @Cleanup.

With Lombok

import lombok.Cleanup;
import java.io.*;

public class CleanupExample {
  public static void main(String[] argsthrows IOException {
    @Cleanup InputStream in = new FileInputStream(args[0]);
    @Cleanup OutputStream out = new FileOutputStream(args[1]);
    byte[] b = new byte[10000];
    while (true) {
      int r = in.read(b);
      if (r == -1break;
      out.write(b, 0, r);
    }
  }
}

Vanilla Java

import java.io.*;

public class CleanupExample {
  public static void main(String[] argsthrows IOException {
    InputStream in = new FileInputStream(args[0]);
    try {
      OutputStream out = new FileOutputStream(args[1]);
      try {
        byte[] b = new byte[10000];
        while (true) {
          int r = in.read(b);
          if (r == -1break;
          out.write(b, 0, r);
        }
      finally {
        if (out != null) {
          out.close();
        }
      }
    finally {
      if (in != null) {
        in.close();
      }
    }
  }
}

Supported configuration keys:

lombok.cleanup.flagUsage = [warning | error] (default: not set)
Lombok will flag any usage of @Cleanup as a warning or error if configured.

Small print

In the finally block, the cleanup method is only called if the given resource is not null. However, if you use delombok on the code, a call to lombok.Lombok.preventNullAnalysis(Object o) is inserted to prevent warnings if static code analysis could determine that a null-check would not be needed. Compilation with lombok.jar on the classpath removes that method call, so there is no runtime dependency.
If your code throws an exception, and the cleanup method call that is then triggered also throws an exception, then the original exception is hidden by the exception thrown by the cleanup call. You should not rely on this 'feature'. Preferably, lombok would like to generate code so that, if the main body has thrown an exception, any exception thrown by the close call is silently swallowed (but if the main body exited in any other way, exceptions by the close call will not be swallowed). The authors of lombok do not currently know of a feasible way to implement this scheme, but if java updates allow it, or we find a way, we'll fix it.
You do still need to handle any exception that the cleanup method can generate!

Reference:

Lombok Constructor Example - @NoArgsConstructor / @RequiredArgsConstructor / @AllArgsConstructor


Constructors made to order: Generates constructors that take no arguments, one argument per final / non-null field, or one argument for every field.

Overview

This set of 3 annotations generate a constructor that will accept 1 parameter for certain fields, and simply assigns this parameter to the field.
@NoArgsConstructor will generate a constructor with no parameters. If this is not possible (because of final fields), a compiler error will result instead, unless @NoArgsConstructor(force = true) is used, then all final fields are initialized with 0 / false / null. For fields with constraints, such as @NonNull fields, no check is generated,so be aware that these constraints will generally not be fulfilled until those fields are properly initialized later. Certain java constructs, such as hibernate and the Service Provider Interface require a no-args constructor. This annotation is useful primarily in combination with either @Data or one of the other constructor generating annotations.
@RequiredArgsConstructor generates a constructor with 1 parameter for each field that requires special handling. All non-initialized final fields get a parameter, as well as any fields that are marked as @NonNull that aren't initialized where they are declared. For those fields marked with @NonNull, an explicit null check is also generated. The constructor will throw a NullPointerException if any of the parameters intended for the fields marked with @NonNull contain null. The order of the parameters match the order in which the fields appear in your class.
@AllArgsConstructor generates a constructor with 1 parameter for each field in your class. Fields marked with @NonNull result in null checks on those parameters.
Each of these annotations allows an alternate form, where the generated constructor is always private, and an additional static factory method that wraps around the private constructor is generated. This mode is enabled by supplying the staticName value for the annotation, like so: @RequiredArgsConstructor(staticName="of"). Such a static factory method will infer generics, unlike a normal constructor. This means your API users get write MapEntry.of("foo", 5) instead of the much longer new MapEntry<String, Integer>("foo", 5).
To put annotations on the generated constructor, you can use onConstructor=@__({@AnnotationsHere}), but be careful; this is an experimental feature. For more details see the documentation on the onX feature.
Static fields are skipped by these annotations.
Unlike most other lombok annotations, the existence of an explicit constructor does not stop these annotations from generating their own constructor. This means you can write your own specialized constructor, and let lombok generate the boilerplate ones as well. If a conflict arises (one of your constructors ends up with the same signature as one that lombok generates), a compiler error will occur.

With Lombok

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.NonNull;

@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class ConstructorExample<T> {
  private int x, y;
  @NonNull private T description;
  
  @NoArgsConstructor
  public static class NoArgsExample {
    @NonNull private String field;
  }
}

Vanilla Java

public class ConstructorExample<T> {
  private int x, y;
  @NonNull private T description;
  
  private ConstructorExample(T description) {
    if (description == nullthrow new NullPointerException("description");
    this.description = description;
  }
  
  public static <T> ConstructorExample<T> of(T description) {
    return new ConstructorExample<T>(description);
  }
  
  @java.beans.ConstructorProperties({"x""y""description"})
  protected ConstructorExample(int x, int y, T description) {
    if (description == nullthrow new NullPointerException("description");
    this.x = x;
    this.y = y;
    this.description = description;
  }
  
  public static class NoArgsExample {
    @NonNull private String field;
    
    public NoArgsExample() {
    }
  }
}

Supported configuration keys:

lombok.anyConstructor.addConstructorProperties = [true | false] (default: false)
If set to true, then lombok will add a @java.beans.ConstructorProperties to generated constructors.
lombok.[allArgsConstructor|requiredArgsConstructor|noArgsConstructor].flagUsage = [warning | error] (default: not set)
Lombok will flag any usage of the relevant annotation (@AllArgsConstructor@RequiredArgsConstructor or @NoArgsConstructor) as a warning or error if configured.
lombok.anyConstructor.flagUsage = [warning | error] (default: not set)
Lombok will flag any usage of any of the 3 constructor-generating annotations as a warning or error if configured.
lombok.copyableAnnotations = [A list of fully qualified types] (default: empty list)
Lombok will copy any of these annotations from the field to the constructor parameter, the setter parameter, and the getter method. Note that lombok ships with a bunch of annotations 'out of the box' which are known to be copyable: All popular nullable/nonnull annotations.

Small print

Even if a field is explicitly initialized with null, lombok will consider the requirement to avoid null as fulfilled, and will NOT consider the field as a 'required' argument. The assumption is that if you explicitly assign null to a field that you've also marked as @NonNull signals you must know what you're doing.
The @java.beans.ConstructorProperties annotation is never generated for a constructor with no arguments. This also explains why @NoArgsConstructor lacks the suppressConstructorProperties annotation method. The generated static factory methods also do not get @ConstructorProperties, as this annotation can only be added to real constructors.
@XArgsConstructor can also be used on an enum definition. The generated constructor will always be private, because non-private constructors aren't legal in enums. You don't have to specify AccessLevel.PRIVATE.
Various well known annotations about nullity cause null checks to be inserted and will be copied to the parameter. See Getter/Setter documentation's small print for more information.
The flagUsage configuration keys do not trigger when a constructor is generated by @Data@Value or any other lombok annotation.

Reference:

Lombok @Data Example


All together now: A shortcut for @ToString@EqualsAndHashCode@Getter on all fields, @Setter on all non-final fields, and @RequiredArgsConstructor!

Overview

@Data is a convenient shortcut annotation that bundles the features of @ToString@EqualsAndHashCode@Getter / @Setter and @RequiredArgsConstructor together: In other words, @Data generates all the boilerplate that is normally associated with simple POJOs (Plain Old Java Objects) and beans: getters for all fields, setters for all non-final fields, and appropriate toStringequals and hashCode implementations that involve the fields of the class, and a constructor that initializes all final fields, as well as all non-final fields with no initializer that have been marked with @NonNull, in order to ensure the field is never null.
@Data is like having implicit @Getter@Setter@ToString@EqualsAndHashCode and @RequiredArgsConstructor annotations on the class (except that no constructor will be generated if any explicitly written constructors already exist). However, the parameters of these annotations (such as callSuperincludeFieldNames and exclude) cannot be set with @Data. If you need to set non-default values for any of these parameters, just add those annotations explicitly; @Data is smart enough to defer to those annotations.
All generated getters and setters will be public. To override the access level, annotate the field or class with an explicit @Setter and/or @Getter annotation. You can also use this annotation (by combining it with AccessLevel.NONE) to suppress generating a getter and/or setter altogether.
All fields marked as transient will not be considered for hashCode and equals. All static fields will be skipped entirely (not considered for any of the generated methods, and no setter/getter will be made for them).
If the class already contains a method with the same name and parameter count as any method that would normally be generated, that method is not generated, and no warning or error is emitted. For example, if you already have a method with signature equals(AnyType param), no equals method will be generated, even though technically it might be an entirely different method due to having different parameter types. The same rule applies to the constructor (any explicit constructor will prevent @Data from generating one), as well as toStringequals, and all getters and setters. You can mark any constructor or method with @lombok.experimental.Tolerate to hide them from lombok.
@Data can handle generics parameters for fields just fine. In order to reduce the boilerplate when constructing objects for classes with generics, you can use the staticConstructor parameter to generate a private constructor, as well as a static method that returns a new instance. This way, javac will infer the variable name. Thus, by declaring like so: @Data(staticConstructor="of") class Foo<T> { private T x;} you can create new instances of Foo by writing: Foo.of(5); instead of having to write: new Foo<Integer>(5);.

With Lombok

import lombok.AccessLevel;
import lombok.Setter;
import lombok.Data;
import lombok.ToString;

@Data public class DataExample {
  private final String name;
  @Setter(AccessLevel.PACKAGEprivate int age;
  private double score;
  private String[] tags;
  
  @ToString(includeFieldNames=true)
  @Data(staticConstructor="of")
  public static class Exercise<T> {
    private final String name;
    private final T value;
  }
}

Vanilla Java

import java.util.Arrays;

public class DataExample {
  private final String name;
  private int age;
  private double score;
  private String[] tags;
  
  public DataExample(String name) {
    this.name = name;
  }
  
  public String getName() {
    return this.name;
  }
  
  void setAge(int age) {
    this.age = age;
  }
  
  public int getAge() {
    return this.age;
  }
  
  public void setScore(double score) {
    this.score = score;
  }
  
  public double getScore() {
    return this.score;
  }
  
  public String[] getTags() {
    return this.tags;
  }
  
  public void setTags(String[] tags) {
    this.tags = tags;
  }
  
  @Override public String toString() {
    return "DataExample(" this.getName() ", " this.getAge() ", " this.getScore() ", " + Arrays.deepToString(this.getTags()) ")";
  }
  
  protected boolean canEqual(Object other) {
    return other instanceof DataExample;
  }
  
  @Override public boolean equals(Object o) {
    if (o == thisreturn true;
    if (!(instanceof DataExample)) return false;
    DataExample other = (DataExampleo;
    if (!other.canEqual((Object)this)) return false;
    if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
    if (this.getAge() != other.getAge()) return false;
    if (Double.compare(this.getScore(), other.getScore()) != 0return false;
    if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
    return true;
  }
  
  @Override public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final long temp1 = Double.doubleToLongBits(this.getScore());
    result = (result*PRIME(this.getName() == null 43 this.getName().hashCode());
    result = (result*PRIMEthis.getAge();
    result = (result*PRIME(int)(temp1 ^ (temp1 >>> 32));
    result = (result*PRIME+ Arrays.deepHashCode(this.getTags());
    return result;
  }
  
  public static class Exercise<T> {
    private final String name;
    private final T value;
    
    private Exercise(String name, T value) {
      this.name = name;
      this.value = value;
    }
    
    public static <T> Exercise<T> of(String name, T value) {
      return new Exercise<T>(name, value);
    }
    
    public String getName() {
      return this.name;
    }
    
    public T getValue() {
      return this.value;
    }
    
    @Override public String toString() {
      return "Exercise(name=" this.getName() ", value=" this.getValue() ")";
    }
    
    protected boolean canEqual(Object other) {
      return other instanceof Exercise;
    }
    
    @Override public boolean equals(Object o) {
      if (o == thisreturn true;
      if (!(instanceof Exercise)) return false;
      Exercise<?> other = (Exercise<?>o;
      if (!other.canEqual((Object)this)) return false;
      if (this.getName() == null ? other.getValue() != null : !this.getName().equals(other.getName())) return false;
      if (this.getValue() == null ? other.getValue() != null : !this.getValue().equals(other.getValue())) return false;
      return true;
    }
    
    @Override public int hashCode() {
      final int PRIME = 59;
      int result = 1;
      result = (result*PRIME(this.getName() == null 43 this.getName().hashCode());
      result = (result*PRIME(this.getValue() == null 43 this.getValue().hashCode());
      return result;
    }
  }
}

Supported configuration keys:

lombok.data.flagUsage = [warning | error] (default: not set)
Lombok will flag any usage of @Data as a warning or error if configured.

Small print

Various well known annotations about nullity cause null checks to be inserted and will be copied to the relevant places (such as the method for getters, and the parameter for the constructor and setters). See Getter/Setter documentation's small print for more information.
By default, any variables that start with a $ symbol are excluded automatically. You can include them by specifying an explicit annotation (@Getter or @ToString, for example) and using the 'of' parameter.

Reference: