Referencias de métodos en Java 8

    Introducción

    El mas dulce azúcar sintáctica agregado a Java hasta ahora son definitivamente Lambda Expressions.

    Java es un lenguaje detallado y eso puede obstaculizar la productividad y la legibilidad. Reducir el código repetitivo y repetitivo siempre ha sido una tarea popular entre los desarrolladores de Java, y generalmente se busca un código limpio, legible y conciso.

    Lambda Expressions eliminó la necesidad de escribir código repetitivo engorroso cuando se trata de algunas tareas comunes al permitir a los desarrolladores llamarlas sin que pertenezcan a una clase y pasarlas como si fueran objetos.

    Estas expresiones han tenido un uso importante con la API de Java Streams y Spring’s WebFlux marco para la creación de aplicaciones dinámicas reactivas.

    Otra característica realmente útil agregada a Java 8 son las referencias a métodos, que hacen que las expresiones Lambda sean mucho más concisas y simples, al invocar (hacer referencia) a los métodos usando un nombre de método cuando la expresión Lambda se hubiera usado simplemente para llamar a un método.

    Referencias de métodos

    Las referencias a métodos son esencialmente expresiones Lambda abreviadas, que se utilizan para invocar métodos.

    Constan de dos partes:

    Class::method;
    

    Y un ejemplo común sería imprimir los resultados de, por ejemplo, suscribirse a un servicio de publicación o Java Stream:

    someCodeChain.subscribe(System.out::println);
    

    Repasemos un ejemplo de código imperativo, que luego pasaremos a código funcional a través de Expresiones Lambda y finalmente lo acortaremos mediante Referencias de método.

    Haremos una clase simple:

    public class Employee {
        private int id;
        private String name;
        private int wage;
        private String position;
    
        // Constructor, getters and setters
    
        @Override
        public String toString() {
            return "Name: " + name + ", Wage: " + wage + ", Position: " + position;
        }
    
        public int compareTo(Employee employee) {
            if (this.wage <= employee.wage) {
                return 1;
            } else {
                return -1;
            }
        }
    }
    

    Si formamos esta clase en una colección, como un ArrayList, no pudimos clasificarlo con el método de utilidad .sort() ya que no implementa el Comparable interfaz.

    Lo que podemos hacer es definir un new Comparator para estos objetos mientras llama al .sort() método:

    Employee emp1 = new Employee(1, "David", 1200, "Developer");
    Employee emp2 = new Employee(2, "Tim", 1500, "Developer");
    Employee emp3 = new Employee(3, "Martha", 1300, "Developer");
    
    ArrayList<Employee> employeeList = new ArrayList<>();
    employeeList.add(emp1);
    employeeList.add(emp2);
    employeeList.add(emp3);
    
    Collections.sort(employeeList, new Comparator<Employee>() {
        public int compare(Employee emp1, Employee emp2) {
            return emp1.compareTo(emp2);
        }
    });
    
    System.out.println(employeeList);
    

    Ejecutar este código producirá:

    [Name: Tim, Wage: 1500, Position: Developer, Name: Martha, Wage: 1300, Position: Developer, Name: David, Wage: 1200, Position: Developer]
    

    Aquí, la clase anónima (Comparator) está definiendo los criterios de comparación. Podemos hacerlo mucho más simple y más corto usando una expresión Lambda:

    Collections.sort(employeeList, (e1, e2) -> e1.compareTo(e2));
    

    Ejecutar este fragmento de código producirá:

    [Name: Tim, Wage: 1500, Position: Developer, Name: Martha, Wage: 1300, Position: Developer, Name: David, Wage: 1200, Position: Developer]
    

    Por otra parte, dado que todo lo que estamos haciendo con esta expresión Lambda es llamar a un solo método, podemos hacer referencia solo a ese método:

    Collections.sort(employeeList, Employee::compareTo);
    

    Y esto también producirá:

    [Name: Tim, Wage: 1500, Position: Developer, Name: Martha, Wage: 1300, Position: Developer, Name: David, Wage: 1200, Position: Developer]
    

    Tipos de referencia de método

    Las referencias de métodos se pueden utilizar en un par de escenarios diferentes:

    • Métodos estáticos: Class::staticMethodName
    • Métodos de instancia de objetos particulares: object::instanceMethodName
    • Métodos de instancia de objetos de arbitraje: Class::methodName
    • Referencia del constructor: Class::new

    Repasemos todos estos tipos a través de algunos ejemplos simples.

    Referencias de métodos estáticos

    Puede hacer referencia a cualquier static método de una clase simplemente llamando a su clase contenedora con el nombre del método.

    Definamos una clase con un static y luego referenciarlo desde otra clase:

    public class ClassA {
        public static void raiseToThePowerOfTwo(double num) {
            double result = Math.pow(num, 2);
            System.out.println(result);
        }
    }
    

    Y ahora, de otra clase, usemos el static método de utilidad:

    public class ClassB {
        public static void main(String[] args) {
            List<Double> integerList = new ArrayList<>();
            integerList.add(new Double(5));
            integerList.add(new Double(2));
            integerList.add(new Double(6));
            integerList.add(new Double(1));
            integerList.add(new Double(8));
            integerList.add(new Double(9));
    
            integerList.forEach(ClassA::raiseToThePowerOfTwo);
        }
    }
    

    Ejecutar este fragmento de código producirá:

    25.0
    4.0
    36.0
    1.0
    64.0
    81.0
    

    Hay muchas clases de Java que ofrecen static métodos de utilidad que se pueden utilizar aquí. En nuestro ejemplo hemos utilizado un método personalizado, aunque no muy útil en este caso.

    Métodos de instancia de objetos particulares

    Puede llamar a un método desde un objeto instanciado particular haciendo referencia al método utilizando la variable de referencia del objeto.

    Esto se ilustra con mayor frecuencia mediante un comparador personalizado. Usaremos lo mismo Employee class de antes y la misma lista para resaltar la diferencia entre estos dos:

    public class Employee {
        private int id;
        private String name;
        private int wage;
        private String position;
    
        // Constructor, getters and setters
    
        @Override
        public String toString() {
            return "Name: " + name + ", Wage: " + wage + ", Position: " + position;
        }
    
        public int compareTo(Employee employee) {
            if (this.wage <= employee.wage) {
                return 1;
            } else {
                return -1;
            }
        }
    }
    

    Ahora, definamos un CustomComparator:

    public class CustomComparator {
        public int compareEntities(Employee emp1, Employee emp2) {
            return emp1.compareTo(emp2);
        }
    }
    

    Y finalmente, completemos una lista y ordénela:

    Employee emp1 = new Employee(1, "David", 1200, "Developer");
    Employee emp2 = new Employee(2, "Tim", 1500, "Developer");
    Employee emp3 = new Employee(3, "Martha", 1300, "Developer");
    
    ArrayList<Employee> employeeList = new ArrayList<>();
    employeeList.add(emp1);
    employeeList.add(emp2);
    employeeList.add(emp3);
    
    // Initializing our CustomComparator
    CustomComparator customComparator = new CustomComparator();
    
    // Instead of making a call to an arbitrary Employee
    // we're now providing an instance and its method
    Collections.sort(employeeList, customComparator::compareEntities);
    
    System.out.println(employeeList);
    

    Ejecutar este código también producirá:

    [Name: Tim, Wage: 1500, Position: Developer, Name: Martha, Wage: 1300, Position: Developer, Name: David, Wage: 1200, Position: Developer]
    

    La principal diferencia es que al agregar otra capa, a través del CustomComparator, podemos agregar más funcionalidad para comparar y quitarla de la clase en sí. Una clase como Employee no debe cargarse con una lógica de comparación compleja y esto da como resultado un código más limpio y legible.

    Por otro lado, a veces no deseamos definir comparadores personalizados e introducir uno es simplemente una molestia. En tales casos, llamaríamos a un método desde un objeto arbitrario de un tipo particular, que se muestra en la siguiente sección.

    Métodos de instancia de objetos arbitrarios

    Este ejemplo ya se mostró al comienzo del artículo cuando redujimos el enfoque imperativo a un enfoque funcional a través de Lambda Expressions.

    Aunque, en buena medida, dado que este enfoque se usa con mucha frecuencia, echemos un vistazo a otro ejemplo:

    List<Integer> integerList = new ArrayList<>();
    integerList.add(new Integer(5));
    integerList.add(new Integer(2));
    integerList.add(new Integer(6));
    integerList.add(new Integer(1));
    integerList.add(new Integer(8));
    integerList.add(new Integer(9));
    
    // Referencing the non-static compareTo method from the Integer class
    Collections.sort(integerList, Integer::compareTo);
    
    // Referencing static method
    integerList.forEach(System.out::print);
    

    Ejecutar este fragmento de código produciría:

    125689
    

    Si bien esto puede parecer lo mismo que una llamada a un método estático, no lo es. Esto es equivalente a llamar a la expresión Lambda:

    Collections.sort(integerList, (Integer a, Integer b) -> a.compareTo(b));
    

    Aquí, la distinción es más obvia. Si tuviéramos que llamar a un static método, se vería así:

    Collections.sort(integerList, (Integer a, Integer b) -> SomeClass.compare(a, b));
    

    Constructores de referencia

    Puede hacer referencia al constructor de una clase de la misma manera que haría referencia a un static método.

    Podría usar una referencia a un constructor en lugar de la instanciación de clase clásica:

    // Classic instantiation
    Employee employee = new Employee();
    
    // Constructor reference
    Employee employee2 = Employe::new;
    

    Según el contexto, si hay varios constructores presentes, se utilizará el adecuado si se hace referencia a él:

    Stream<Employee> stream = names.stream().map(Employee::new);
    

    Debido a una serie de nombres, si un Employee(String name) constructor está presente, se utilizará.

    Otra forma en que podría usar referencias de constructor es cuando quiera mapear una secuencia en una matriz, manteniendo el tipo particular. Si simplemente lo mapea y luego llama toArray(), obtendrías una variedad de Objects en lugar de tu tipo particular.

    Si lo intentamos, di:

    Employee[] employeeArray = employeeList.toArray();
    

    Por supuesto, recibiríamos un error del compilador ya que .toArray() devuelve una matriz de Objects. Lanzarlo tampoco ayudará:

    Employee[] employeeArray = (Employee[]) employeeList.toArray();
    

    Pero esta vez, será una excepción de tiempo de ejecución: ClassCastException.

    Podemos evitar eso con:

    // Making a list of employees
    List<String> employeeList = Arrays.asList("David", "Scott");
    
    // Mapping a list to Employee objects and returning them as an array
    Employee[] employeeArray = employeeList.stream().map(Employee::new).toArray(Employee[]::new);
    
    // Iterating through the array and printing information
    for (int i = 0; i < employeeArray.length; i++) {
        System.out.println(employeeArray[i].toString());
    }
    

    Y con eso, obtenemos el resultado:

    Name: David, Wage: 0, Position: null
    Name: Scott, Wage: 0, Position: null
    

    Conclusión

    Las referencias a métodos son un tipo de expresiones Lambda que se utilizan para hacer referencia simplemente a un método en su llamada. Con ellos, escribir código puede ser mucho más conciso y legible.

    Lambda Expressions ha presentado a los desarrolladores de Java un enfoque más funcional en la programación que les permite evitar escribir código detallado para operaciones simples.

     

    Etiquetas:

    Deja una respuesta

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