Lombok uses @With annotation
1. Introduction
Lombok is a library that helps us significantly reduce boilerplate code when writing Java applications.
In this tutorial, we will see how to use this library to make copies of immutable objects that only change a single property.
2. Usage
When using immutable objects that do not allow setters, we may need an object similar to the current object, but with only one attribute different. This can be achieved using Lombok's @With
annotations:
public class User { private final String username; private final String emailAddress; @With private final boolean isAuthenticated; //getters, constructors }
The above comments generate the following in the background:
public class User { private final String username; private final String emailAddress; private final boolean isAuthenticated; //getters, constructors public User withAuthenticated(boolean isAuthenticated) { return this.isAuthenticated == isAuthenticated ? this : new User(this.username, this.emailAddress, isAuthenticated); } }
Then we can use the method generated above to create a mutated copy of the original object:
User immutableUser = new User("testuser", "[email protected]", false); User authenticatedUser = immutableUser.withAuthenticated(true); assertNotSame(immutableUser, authenticatedUser); assertFalse(immutableUser.isAuthenticated()); assertTrue(authenticatedUser.isAuthenticated());
In addition, we can choose to annotate the entire class, which will have all property withX()
methods .
3. Requirements
To @With
comment, we need to provide a full-parameter constructor . From the above example, we can see that the generated method needs this to create a clone of the original object.
We can use Lombok's own @AllArgsConstructor
or @Value
annotations to meet this requirement. Alternatively, we can provide this constructor manually while ensuring that the order of non-static properties in the class matches the order of the constructor.
We should remember that if you use @With
** annotations on static fields , you will do nothing. This is because static properties are not considered part of the object state. In addition, Lombok will skip$
the method generation of the field ** at the beginning of the symbol.
4. Advanced usage
Let's examine some advanced scenarios when using this annotation.
4.1. Abstract class
We can use the abstract field@With
public abstract class Device { private final String serial; @With private final boolean isInspected; //getters, constructor }
However, we need towithInspected()
provide an implementation for the generated method . This is because Lombok doesn't know the concrete implementation of our abstract class to create its clone:
public class KioskDevice extends Device { @Override public Device withInspected(boolean isInspected) { return new KioskDevice(getSerial(), isInspected); } //getters, constructor }
4.2. Naming Convention
As mentioned above, Lombok will skip $
fields that start with a symbol. However, if the field starts with a character, it is the case of the title, and finally, with
the prefix of the generation method.
Or, if the field starts with an underscore, it is with
only used as a prefix for the generation method:
public class Holder { @With private String variableA; @With private String _variableB; @With private String $variableC; //getters, constructor excluding $variableC }
According to the code above, we see that only the first two variables will be generated for themwithX()
Holder value = new Holder("a", "b"); Holder valueModifiedA = value.withVariableA("mod-a"); Holder valueModifiedB = value.with_variableB("mod-b"); // Holder valueModifiedC = value.with$VariableC("mod-c"); not possible
4.3. Exceptions generated by the method
We should note that, except for the fields beginning with $
symbols , if there are already withX()
methods in our class , Lombok will not generate it:
public class Stock { @With private String sku; @With private int stockCount; //prevents another withSku() method from being generated public Stock withSku(String sku) { return new Stock("mod-" + sku, stockCount); } //constructor }
In the above scenario, no new withSku()
methods will be generated .
In addition, Lombok will skip method generation in the following scenarios :
public class Stock { @With private String sku; private int stockCount; //also prevents another withSku() method from being generated public Stock withSKU(String... sku) { return sku == null || sku.length == 0 ? new Stock("unknown", stockCount) : new Stock("mod-" + sku[0], stockCount); } //constructor }
We can notice the abovewithSKU()
Basically, Lombok will skip method generation if:
The same method exists as the generated method name (ignoring case)
The existing method and the generated method have the same number of parameters (including var-args)
4.4. Null verification of the generated method
Similar to other Lombok annotations, we can @With
annotate the generated methodnull
@With @AllArgsConstructor public class ImprovedUser { @NonNull private final String username; @NonNull private final String emailAddress; }
Lombok will generate the following code for us and the required null
checks:
public ImprovedUser withUsername(@NonNull String username) { if (username == null) { throw new NullPointerException("username is marked non-null but is null"); } else { return this.username == username ? this : new ImprovedUser(username, this.emailAddress); } } public ImprovedUser withEmailAddress(@NonNull String emailAddress) { if (emailAddress == null) { throw new NullPointerException("emailAddress is marked non-null but is null"); } else { return this.emailAddress == emailAddress ? this : new ImprovedUser(this.username, emailAddress); } }
5 Conclusion
In this article, we have seen how to use Lombok's @With
annotations to generate clones of specific objects with individual field changes.
We also learned how and when this method actually works, and how null
to enhance it with other verifications (such as inspections).
0 Comments