¿Qué son los registros de Java y cómo usarlos junto con constructores y métodos?

Como desarrolladores e ingenieros de software, nuestro objetivo es siempre diseñar formas de obtener la máxima eficiencia y si necesitamos escribir menos código para ello, eso es una bendición. 

En Java, un registro es un tipo especial de declaración de clase destinado a reducir el código repetitivo. Los registros de Java se introdujeron con la intención de ser utilizados como una forma rápida de crear clases de soporte de datos, es decir, las clases cuyo objetivo es simplemente contener datos y transportarlos entre módulos, también conocidos como POJO (Plain Old Java Objects) y DTO (Data Transferir Objetos). Record se introdujo en Java SE 14 como una función de vista previa, que es una función cuyo diseño, implementación y especificación están completos pero no es una adición permanente al lenguaje, lo que significa que la función puede existir o no en las versiones futuras. del idioma Java SE 15 amplía la función de vista previa con capacidades adicionales, como clases de registros locales.

Primero analicemos por qué necesitamos registros antes de implementarlos. Consideremos una ilustración para esto.

Ilustración:

Considere un Empleado de clase simple, cuyo objetivo es contener los datos de un empleado, como su ID y nombre, y actuar como un portador de datos para transferir entre módulos. Para crear una clase tan simple, necesitaría definir sus métodos constructor, getter y setter, y si desea usar el objeto con estructuras de datos como HashMap o imprimir el contenido de sus objetos como una string, tendríamos que invalidar métodos como equals(), hashCode() y toString().

Ejemplo

Java

// Java Program Illustrating Program Without usage of
// Records
 
// A sample Employee class
class Employee {
 
    // Member variables of this class
    private String firstName;
    private String lastName;
    private int Id;
 
    // Constructor of this class
    public Employee(String firstName, String lastName,
                    int Id)
    {
 
        // This keyword refers to current instance itself
        this.firstName = firstName;
        this.lastName = lastName;
        this.Id = Id;
    }
 
    // Setter and Getter methods
 
    // Setter-getter Method 1
    public void setFirstName(String firstName)
    {
        this.firstName = firstName;
    }
 
    // Setter-getter Method 2
    // to get the first name of employee
    public String getFirstName() { return firstName; }
 
    // Setter-getter Method 3
    // To set  the last name of employees
    public void setLastName(String lasstName)
    {
 
        // This keyword refers to current object itself
        this.lastName = lastName;
    }
 
    // Setter-getter Method 3
    // To set  the last name of employees
    public String getLastName() { return lastName; }
 
    // Setter-getter Method 4
    // To set  the last name of employees
    public void setId(int Id) { this.Id = Id; }
 
    // Setter-getter Method 5
    // To set  the last name of employees
    public int getId() { return Id; }
 
    // Setter-getter Method 6
    public String toString()
    {
 
        // Return the attributes
        return "Employee [firstName=" + firstName
            + ", lastName=" + lastName + ", Id=" + Id + "]";
    }
 
    // Method 7
    // Overriding hashCode method
    @Override public int hashCode()
    {
 
        // Final variable
        final int prime = 31;
        int result = 1;
 
        result = prime * result + Id;
        result = prime * result
                 + ((firstName == null)
                        ? 0
                        : firstName.hashCode());
        result
            = prime * result
              + ((lastName == null) ? 0
                                    : lastName.hashCode());
 
        return result;
    }
 
    // Method 8
    // Overriding equals method to
    // implement with data structures
    @Override public boolean equals(Object obj)
    {
 
        // This refers to current instance itself
        if (this == obj)
            return true;
 
        if (obj == null)
            return false;
 
        if (getClass() != obj.getClass())
            return false;
 
        Employee other = (Employee)obj;
 
        if (Id != other.Id)
            return false;
 
        if (firstName == null) {
            if (other.firstName != null)
                return false;
        }
 
        else if (!firstName.equals(other.firstName))
            return false;
 
        if (lastName == null) {
            if (other.lastName != null)
                return false;
        }
 
        else if (!lastName.equals(other.lastName))
            return false;
 
        return true;
    }
}

Nota: Son más de 100 líneas de código solo para crear una clase que contenga algunos datos.

Ahora veamos lo que se necesitaría para crear una clase similar usando Record para obtener su uso antes de discutir las propiedades de los registros que se detallan a continuación:

Algunas propiedades más de los registros

  • Puede usar clases e interfaces anidadas dentro de un registro.
  • También puede tener registros anidados, que implícitamente serán estáticos.
  • Un registro puede implementar interfaces.
  • Puede crear una clase de registro genérica.
  • Es posible utilizar clases de registros locales (desde Java SE 15).
  • Los registros son serializables.

Por muy tentador que sea usar registros para objetos portadores de datos, los registros siguen siendo una función de vista previa en Java. Además, dado que están destinados a ser utilizados solo como portadores de datos, definir nuestros propios métodos de acceso y otros métodos de instancia desafiaría el propósito. Los registros se pueden usar para reducir el trabajo realizado por el desarrollador, pero internamente la diferencia de rendimiento entre un registro y una clase no es tan amplia.

public record Employee(int id, String firstName, String lastName) {}

¡Eso es todo! Solo 2 líneas de código, eso es todo lo que necesita para implementar esas 80 líneas de código usando Record. Para saber cómo implementa Java esta función, primero aprenderemos a configurarla nosotros mismos. Ahora analicemos los pasos con ayudas visuales que demuestran los registros de Java. Dado que Records es una característica de Java SE 14, necesitaríamos JDK 14 en nuestra máquina. Descargue Oracle JDK 14 de este archivo para su máquina. Después de descargar e instalar JDK-14 en la carpeta Java junto con cualquier otra versión de Java, siga los pasos a continuación.

Nota : Para este tutorial se utiliza Eclipse IDE. 

Pasos para configurar registros java 

Paso 1: Cree un nuevo proyecto Java y seleccione JavaSE-14 como entorno de ejecución.

Paso 2: si es la primera vez que usa JDK-14, habrá algunos pasos más que deberá seguir para configurar los registros para que funcionen. Es posible que vea este tipo de marca de excepción en la carpeta de su proyecto.

Paso 3: Para arreglar eso, en la parte superior, ve a Ventana -> Preferencias .

Paso 4: en la ventana Preferencias, haga clic en JRE instalados y luego haga clic en Agregar como se muestra a continuación:

Paso 5: Ahora, en la ventana Agregar JRE que se abre, seleccione VM estándar y haga clic en Siguiente. Verá una nueva ventana abierta para seleccionar un JRE, ahora haga clic en Directorio y navegue hasta donde está instalado su jdk-14 y seleccione esa carpeta. Haga clic en Finalizar .

Paso 6: marque el JDK-14 que acaba de agregar y aplíquelo .

Paso 7: Aún no hemos terminado. Dado que los registros son una función de vista previa, debemos habilitarlos para que la usen. En la ventana del Explorador de proyectos en el lado izquierdo, seleccione su proyecto y haga clic con el botón derecho y vaya a sus Propiedades .

Paso 8: En la ventana que se abre, a la derecha, de las diversas opciones, seleccione Compilador de Java. Después de eso, en el lado izquierdo, desmarque las configuraciones marcadas con flechas rojas en la imagen y marque la configuración resaltada con verde. Hacer eso habilitará las funciones de vista previa.

Paso 9: después de hacer clic en Aplicar y cerrar, verá un mensaje que le preguntará si desea reconstruir el proyecto. Seleccione .

Implementación: 

Después de configurar el entorno, ahora podemos proceder a escribir el código para los registros.

Los registros de codificación se declaran escribiendo registros en lugar de clases en la declaración de clase. Al definir un registro, todos los campos de instancia se escriben como parámetros. El compilador de Java genera el constructor, los métodos getter, toString(), equals() y hashCode() durante el tiempo de compilación. Una cosa a tener en cuenta aquí es que los registros no proporcionan métodos de establecimiento, ya que se espera que el valor de las variables de instancia se proporcione al crear el objeto.

// A simple Employee class to be used as a DTO

public record Employee(int id, String firstName,
                       String lastName) {
}

Ejemplo 1

// Creating Employee object and showcasing its use cases

// Main class
class GFG {

  // Main driver method
  public static void main(String args[]) {

    // Creating object with default constructor
    Employee e1 = new Employee(1001, "Derok", "Dranf");

    // Auto generated getter methods
    System.out.println(e1.id() + " " + e1.firstName()
                       + " " + e1.lastName());

    // Auto-generated toString() method
    System.out.println(e1.toString());
  }
}

Producción:

1001 Derok Dranf
Employee[id=1001, firstName=Derok, lastName=Dranf]

Notaremos que los métodos getter no son similares en la convención de nomenclatura a los métodos getter normales que se crean (p. ej., getFirstName()), sino que simplemente se indican con el nombre del campo (p. ej., firstName()). Ahora, ampliemos nuestro ejemplo anterior para probar estas funcionalidades.

Eso no es todo lo que un disco puede hacer. Los registros también nos brindan la capacidad de:

  • Crear nuestros propios constructores. En los registros, puede crear un constructor parametrizado, que llama al constructor predeterminado con los parámetros proporcionados dentro de su cuerpo. También puede crear constructores compactos que son similares a los constructores predeterminados con la diferencia de que puede agregar alguna funcionalidad adicional, como comprobaciones dentro del cuerpo del constructor.
  • Crear métodos de instancia. Como cualquier otra clase, puede crear y llamar a métodos de instancia para la clase de registro.
  • Crear campos estáticos. Los registros nos restringen a escribir las variables de instancia solo como parámetros, pero permiten el uso de variables estáticas y métodos estáticos.

Ejemplo 1

// Java Program Illustrating a Record class
// defining constructors, instance methods
// and static fields

// Record class
public record Employee(int id, String firstName,
                       String lastName)
{

    // Instance fields need to be present in the record's
    // parameters but record can define static fields.
    static int empToken;

    // Constructor 1 of this class
    // Compact Constructor
    public Employee
    {
        if (id < 100) {
            throw new IllegalArgumentException(
                "Employee Id cannot be below 100.");
        }
        if (firstName.length() < 2) {
            throw new IllegalArgumentException(
                "First name must be 2 characters or more.");
        }
    }

    // Constructor 2 of this class
    // Alternative Constructor
    public Employee(int id, String firstName)
    {
        this(id, firstName, null);
    }

    // Instance methods
    public void getFullName()
    {
        if (lastName == null)
            System.out.println(firstName());

        else
            System.out.println(firstName() + " "
                               + lastName());
    }

    // Static methods
    public static int generateEmployeeToken()
    {
        return ++empToken;
    }
}

Ejemplo 2

Java

// Java Program to Illustrate Record's functionalities
 
// Main class
class GFG {
 
  // Main driver method
  public static void main(String args[]) {
 
    // Creating object with default constructor
    Employee e1 = new Employee(1001, "Derok", "Dranf");
 
    // auto generated getter methods
    System.out.println(e1.id() + " " + e1.firstName()
                       + " " + e1.lastName());
 
    // Auto-generated toString() method
    System.out.println(e1.toString());
 
    // Creating object with parameterized constructor
    Employee e2 = new Employee(1002, "Seren");
 
    // Using instance methods
    e2.getFullName();
 
    // Using static methods
    System.out.println("Employee " + e2.id()
                       + " Token = "
                       + e2.generateEmployeeToken());
 
    // Using the equals() method
    System.out.print("Is e1 equal to e2: "
                     + e1.equals(e2));
  }
}

Producción:

1001 Derok Dranf
Employee[id=1001, firstName=Derok, lastName=Dranf]
Seren
Employee 1002 Token = 1
Is e1 equal to e2: false

Geek, ¿alguna vez te has preguntado qué magia hace el Compilador?

Como se discutió anteriormente, el registro es solo una declaración especial de una clase e internamente el compilador la convierte en una clase normal con algunas restricciones, lo que la hace diferente de las clases típicas. Cuando el compilador de Java compila el archivo Java al código de bytes, el archivo .class producido contiene la declaración extendida de la clase de registro. Al mirar ese archivo, podemos obtener más información sobre los registros. El código de bytes producido para el registro de empleado que creamos anteriormente es el siguiente:

public final class Employee extends java.lang.Record {
   private final int id;
   private final java.lang.String firstName;
   private final java.lang.String lastName;
   static int empToken;

   public Employee(int id, java.lang.String firstName, java.lang.String lastName) { /* compiled code */ }

   public Employee(int id, java.lang.String firstName) { /* compiled code */ }

   public void getFullName() { /* compiled code */ }

   public static int generateEmployeeToken() { /* compiled code */ }

   public int id() { /* compiled code */ }

   public java.lang.String firstName() { /* compiled code */ }

   public java.lang.String lastName() { /* compiled code */ }

   public java.lang.String toString() { /* compiled code */ }

   public final int hashCode() { /* compiled code */ }

   public final boolean equals(java.lang.Object o) { /* compiled code */ }
}

Conclusión: si nos tomamos un tiempo para observar el código de bytes, notará lo siguiente:

  • El registro ha sido reemplazado por clase .
  • La clase y sus miembros de datos se han declarado como finales . Esto implica que esta clase no se puede extender, es decir, no se puede heredar y también es inmutable.
  • La clase extiende java.lang.Record . Esto significa que todos los registros son una subclase de Registro definido en el paquete java.lang.
  • Hay un constructor predeterminado y un constructor parametrizado. Notará que no hay una declaración separada para el constructor compacto que definimos. Esto se debe a que el constructor compacto no genera un constructor independiente sino que agrega su código al comienzo del cuerpo del constructor predeterminado.
  • Los métodos de instancia y estáticos se declaran como estaban.
  • El compilador ha generado automáticamente los métodos toString(), hashCode() y equals(). 

Publicación traducida automáticamente

Artículo escrito por yuvrajjoshi31 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *