Lombok Immutable: Constructor to make deep copy of parameters

Viewed 5279

How can I get a constructor that assigns a copy of constructor arguments to the fields, to make the instance truly immutable? The below class has @Value annotation but this isn't immutable as its the properties are not: (The question is not specific to immutable Collections, I just used it to demo a mutable DS. I am looking for any mutable bean in place of List here, which, say is coming from a library and is not in my control)

@Value
class ImmutableBean {
    List<String> props;
}
    @Test
    void checkImmutability() {
        var props = new ArrayList<String>();
        props.add("abc");
        var immutableBean = new ImmutableBean(props);
        System.out.println(immutableBean.getProps()); // abc
        props.add("pqr");
        System.out.println(immutableBean.getProps()); // abc pqr
    }
2 Answers

For Singleton class you need to use @Singular and mark your Collection fields as private final

import lombok.*;

import java.util.ArrayList;
import java.util.List;

@Value
@AllArgsConstructor
@Getter
@Builder
class ImmutableBean {

    @Singular
    private final List<String> props;

    public static void main(String[] args) {
        List<String> props = new ArrayList<>();
        props.add("abc");
        ImmutableBean immutableBean = ImmutableBean.builder().props(props).build();
        System.out.println(immutableBean.getProps()); // abc
        props.add("pqr");
        System.out.println(immutableBean.getProps()); // abc
    }
}

A class that is annotated with lombook @Value makes all fields are made private and final by default, and setters are not generated. Which means once the object is created, it is immutable and you cannot override properties of that class using setters method, for example

@Test
void checkImmutability() {
    var props = new ArrayList<String>();
    props.add("abc");
    var immutableBean = new ImmutableBean(props);
    System.out.println(immutableBean.getProps()); // abc
    immutableBeansetProps(new ArrayList<>());  //compile time error

}

So lombook @Value annotation will make property as immutable, so you cannot override that object with another object like my above example, But you can update the same object

var props = new ArrayList<String>();
props.add("abc");
var immutableBean = new ImmutableBean(props);
System.out.println(immutableBean.getProps().hashCode()); // object1
props.add("pqr");
System.out.println(immutableBean.hashCode()); // object1 (same object)

The only way to avoid this is you need to create immutable objects of those properties, for example you can create immutable list

var immutableBean = new ImmutableBean(Collections.unmodifiableList(props));

@Value is the immutable variant of @Data; all fields are made private and final by default, and setters are not generated. The class itself is also made final by default, because immutability is not something that can be forced onto a subclass. Like @Data, useful toString(), equals() and hashCode() methods are also generated, each field gets a getter method, and a constructor that covers every argument (except final fields that are initialized in the field declaration) is also generated.

Related