Setters en un Record en Java

Jordi Ayala - Oct 21 - - Dev Community

Un record es una estructura que se caracteriza por ser inmutable, es decir, una vez que se ha creado un objeto de tipo record, no se pueden modificar sus atributos, es lo equivalente a lo que otros lenguajes de programación denominan data-class o DTO (Data Transfer Object). Sin embargo, si se requiere modificar algún atributo mediante un método setter y considerando que cada atributo dentro del record es de tipo final, ¿cómo se puede lograr esto?

Para mostrar si esto es posible, vamos a crear un record Product que tiene dos atributos name y price, y los respectivos métodos que se crean automáticamente al definir un record en Java:

public record Product(String name, double price) { }
Enter fullscreen mode Exit fullscreen mode

Ahora, si se crea un objeto de tipo Product y se intenta modificar el atributo name, se puede observar que no es posible, y ni siquiera existe un método setter para hacerlo:

Product p = new Product("Bread", 1.0);
p.setName("Water"); // Error: cannot resolve method 'setName' in 'Product'
Enter fullscreen mode Exit fullscreen mode

Pero, si sabemos que un record puede tener métodos adicionales, entonces se puede crear un método setName(String name) que modifique el atributo name y le asigne el nuevo valor, pues la respuesta es que no, no funciona como lo haría en una clase normal, por ejemplo:

public record Product(String name, double price) {
    // Error: cannot asign a value to final variable 'name'
    public void setName(String name) {
        this.name = name;
    }
}
Enter fullscreen mode Exit fullscreen mode

Entonces, ¿cómo se puede modificar un atributo de un record en Java? La respuesta es que se puede si el método set devuelve una nueva instancia del record con cada uno de sus atributos y obviamente con el o los atributos modificados. Este proceso puede ser un poco tedioso dependiendo del número de atributos que tenga el record.

public record Product(String name, double price) {

    public Product setName(String name) {
        return new Product(name, this.price);
    }

    public Product setPrice(double price) {
        return new Product(this.name, price);
    }

}
Enter fullscreen mode Exit fullscreen mode

De esta manera al invocar cualquiera de los métodos setter se obtendrá una nueva instancia del tipo Product con el atributo modificado, por ejemplo:

Product p = new Product("Bread", 1.0);
Product q = p.setName("Milk");
Product r = q.setPrice(2.0);
Enter fullscreen mode Exit fullscreen mode

Para cada objeto p, q y r se pueden invocar de forma normal sus métodos get, equals, hashCode y toString, considerando que ninguno de los objetos es igual a otro, ya que cada uno tiene un valor diferente en sus atributos.

public class Main {
    public static void main(String[] args) {
        Product p = new Product("Bread", 1.0);
        Product q = p.setName("Milk");
        Product r = q.setPrice(2.0);

        System.out.println(p); // Product[name=Bread, price=1.0]
        System.out.println(q); // Product[name=Milk, price=1.0]
        System.out.println(r); // Product[name=Milk, price=2.0]

        System.out.println(p.equals(q)); // false
        System.out.println(q.equals(r)); // false
        System.out.println(r.equals(p)); // false
    }
}
Enter fullscreen mode Exit fullscreen mode

En este punto, es importante considerar si este enfoque es el adecuado para el problema que se está resolviendo, considerando que un record está diseñado para ser una estructura que permite almacenar y transportar información de una manera simple dentro de una aplicación y ser inmutable, o sí se requiere de una estructura con mayor flexibilidad, entonces se debería utilizar una clase. Para mayor información sobre cuándo utilizar un record o una clase, se puede consultar el siguiente post.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .