Clase de Registros de Java 14

Java 14 introdujo una nueva forma de crear clases llamada Records. En este tutorial, aprenderemos:

  • ¿Por qué necesitamos Java Records?
  • Cómo crear Records y utilizarlos
  • Sobreescritura y extensión de clases Records

Lectura recomendada: Características de Java 14

¿Por qué necesitamos Java Records?

Una de las quejas comunes sobre Java ha sido su verbosidad. Si tienes que crear una clase POJO simple, requiere el siguiente código repetitivo.

  • Campos privados
  • Métodos Getter y Setter
  • Constructores
  • Métodos hashCode(), equals(), y toString().

Esta verbosidad es una de las razones por las que hay un gran interés en Kotlin y Project Lombok.

De hecho, la pura frustración de escribir estos métodos genéricos una y otra vez llevó a atajos para crearlos en los IDEs de Java como Eclipse e IntelliJ IDEA.

Aquí tienes una captura de pantalla que muestra la opción del IDE Eclipse para generar los métodos ceremoniales para una clase.

Eclipse Shortcuts to Generate Ceremonial Methods

Los Java Records están diseñados para eliminar esta verbosidad al proporcionar una estructura compacta para crear las clases POJO.

Cómo crear Registros en Java

Los Registros en Java son una característica previa, desarrollada bajo JEP 359. Por lo tanto, necesitas dos cosas para crear Registros en tus proyectos de Java.

  1. JDK 14 instalado. Si estás utilizando un IDE, este también debe proporcionar soporte para Java 14. Tanto Eclipse como IntelliJ ya proporcionan soporte para Java 14, así que estamos bien aquí.
  2. Habilitar la Característica Prevista: Por defecto, las características previas están deshabilitadas. Puedes habilitarlas en Eclipse desde la configuración del compilador de Java del proyecto.
Java 14 Enable Preview Feature In Eclipse

Puedes habilitar las características previas de Java 14 en la línea de comandos usando la opción --enable-preview -source 14.

Supongamos que quiero crear una clase de modelo de Empleado. Se verá algo así como el siguiente código.

package com.journaldev.java14;

import java.util.Map;

public class Employee {

	private int id;
	private String name;
	private long salary;
	private Map<String, String> addresses;

	public Employee(int id, String name, long salary, Map<String, String> addresses) {
		super();
		this.id = id;
		this.name = name;
		this.salary = salary;
		this.addresses = addresses;
	}

	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public long getSalary() {
		return salary;
	}

	public Map<String, String> getAddresses() {
		return addresses;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((addresses == null) ? 0 : addresses.hashCode());
		result = prime * result + id;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + (int) (salary ^ (salary >>> 32));
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Employee other = (Employee) obj;
		if (addresses == null) {
			if (other.addresses != null)
				return false;
		} else if (!addresses.equals(other.addresses))
			return false;
		if (id != other.id)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (salary != other.salary)
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + ", addresses=" + addresses + "]";
	}

}

Uf, eso son más de 70 líneas de código generado automáticamente. Ahora veamos cómo crear una clase de Registro de Empleado, que básicamente proporciona las mismas características.

package com.journaldev.java14;

import java.util.Map;

public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {
}

Wow, esto no puede ser más corto. Ya me encantan las clases de Registro.

Ahora, usemos el comando javap para averiguar qué está sucediendo detrás de escena cuando se compila un Registro.

# javac --enable-preview -source 14 EmpRecord.java
Note: EmpRecord.java uses preview language features.
Note: Recompile with -Xlint:preview for details.

# javap EmpRecord      
Compiled from "EmpRecord.java"
public final class EmpRecord extends java.lang.Record {
  public EmpRecord(int, java.lang.String, long, java.util.Map<java.lang.String, java.lang.String>);
  public java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public int id();
  public java.lang.String name();
  public long salary();
  public java.util.Map<java.lang.String, java.lang.String> addresses();
}
# 
Java Record Class Details

# javac enablepreview source 14 EmpRecord.java

# javap EmpRecord

Si desea obtener más detalles internos, ejecute el comando javap con la opción -v.

  1. A Record class is final, so we can’t extend it.
  2. # javap -v EmpRecord
  3. Puntos Importantes sobre las Clases de Registros
  4. Las clases de registro se extienden implícitamente de la clase java.lang.Record.
  5. A single constructor is created with all the fields specified in the record definition.
  6. Todos los campos especificados en la declaración del registro son finales.
  7. Los campos de registro son inmutables “superficialmente” y dependen del tipo. Por ejemplo, podemos cambiar el campo de direcciones accediendo a él y luego realizando actualizaciones en él.

La clase de registro proporciona automáticamente métodos de acceso para los campos. El nombre del método es el mismo que el nombre del campo, no como los métodos getter genéricos y convencionales.

La clase de registro también proporciona implementaciones de hashCode(), equals(), y toString().

package com.journaldev.java14;

public class RecordTest {

	public static void main(String[] args) {
		
		EmpRecord empRecord1 = new EmpRecord(10, "Pankaj", 10000, null);
		EmpRecord empRecord2 = new EmpRecord(10, "Pankaj", 10000, null);

		Uso de Registros en Programas Java
		System.out.println(empRecord1);
		
		Vamos a ver un ejemplo simple de cómo usar nuestra clase EmpRecord.
		System.out.println("Name: "+empRecord1.name()); 
		System.out.println("ID: "+empRecord1.id());
		
		// toString()
		System.out.println(empRecord1.equals(empRecord2));
		
		// accediendo a los campos
		System.out.println(empRecord1 == empRecord2);		
	}
}

// equals()

EmpRecord[id=10, name=Pankaj, salary=10000, addresses=null]
Name: Pankaj
ID: 10
true
false

// hashCode()

Salida:

El objeto Record funciona de la misma manera que cualquier clase de modelo, objeto de datos, etc.

public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {
	
	public EmpRecord {
		if (id < 0)
			throw new IllegalArgumentException("employee id can't be negative");

		if (salary < 0)
			throw new IllegalArgumentException("employee salary can't be negative");
	}

}

Extensión del Constructor de Registros

EmpRecord empRecord1 = new EmpRecord(-10, "Pankaj", 10000, null);

A veces, queremos tener algunas validaciones o registro en nuestro constructor. Por ejemplo, el id del empleado y el salario no deben ser negativos. El constructor por defecto no tendrá esta validación. Podemos crear un constructor compacto en la clase de registro. El código de este constructor se colocará al principio del constructor generado automáticamente.

Exception in thread "main" java.lang.IllegalArgumentException: employee id can't be negative
	at com.journaldev.java14.EmpRecord.<init>(EmpRecord.java:9)

Si creamos un EmpRecord como en el siguiente código:

Obtendremos una excepción en tiempo de ejecución como:

public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {

	public int getAddressCount() {
		if (this.addresses != null)
			return this.addresses().size();
		else
			return 0;
	}
}

¿Pueden las clases de registros tener métodos?

Sí, podemos crear métodos en registros.

Sin embargo, los registros están destinados a ser transportadores de datos. Deberíamos evitar tener métodos de utilidad en una clase de registro. Por ejemplo, el método anterior puede crearse en una clase de utilidad.

Si crees que tener un método es necesario para tu clase de Registro, piensa cuidadosamente si realmente necesitas una clase de Registro.

Source:
https://www.digitalocean.com/community/tutorials/java-records-class