Métodos de Referencia y Constructores (Expresión lambda)

Avanzado

Todo sobre Métodos de Referencia y Constructores de Referenciados.

1. Métodos de Referencia

Hay una característica importante relacionada con las expresiones lambda llamada Métodos de Referencia. Una referencia a métodos o métodos referenciados proporciona una forma de referirse a un método sin ejecutarlo. Se relaciona con expresiones lambda porque también requiere un contexto de tipo de objetivo que consiste en una interfaz funcional compatible. Cuando se evalúa, una referencia de método también crea una instancia de una interfaz funcional. Hay diferentes tipos de referencias de métodos. Comenzaremos con referencias de métodos a métodos estáticos.

1.1. Métodos de Referencias a métodos estáticos

Se crea una referencia de método a un método estático especificando el nombre del método precedido por su nombre de clase, usando esta sintaxis general:

NombreClase::nombreMetodo

Observe que el nombre de la clase está separado del nombre del método por dos puntos dobles. El :: es un separador que JDK 8 agregó a Java expresamente para este propósito. Esta referencia de método se puede usar en cualquier lugar en el que sea compatible con su tipo de objetivo.

El siguiente programa demuestra la referencia de método estático. Lo hace al primero declarar una interfaz funcional llamada DemoInt que tiene un método llamado prueba(). Este método tiene un parámetro int y devuelve un resultado boolean. Por lo tanto, se puede usar para probar un valor entero contra alguna condición. El programa crea una clase llamada MiDemoInt, que define tres métodos estáticos, y cada uno verifica si un valor satisface alguna condición. Los métodos se llaman esPrimo(), esPar() y esPositivo(), y cada método realiza la prueba indicada por su nombre.

Dentro de MetodoRefDemo, se crea un método llamado pruebaNum() que tiene como primer parámetro, una referencia a IntPredicate. Su segundo parámetro especifica el entero que se prueba. Dentro de main(), se realizan tres pruebas diferentes llamando a pruebaNum(), pasando una referencia de método a la prueba a realizar.

// Demuestración de métodos de referencia para un método estático.

// Una interfaz funcional para demostraciones numéricas que operan en valores enteros.
interface DemoInt{
    boolean prueba(int n);
}

// Esta clase define tres métodos estáticos que verifican un entero contra alguna condición.
class MiDemoInt{

    // Un método estático que devuelve true si un número es primo.
    static boolean esPrimo(int n){
        if (n<2) return false;

        for (int i=2;i<=n/i;i++){
            if ((n%i)==0)
                return false;
        }
        return true;
    }

    // Un método estático que devuelve verdadero si un número es par.
    static boolean esPar(int n){
        return (n%2)==0;
    }

    // Un método estático que devuelve true si un número es positivo.
    static boolean esPositivo(int n){
        return n>0;
    }
}
class MetodoRefDemo {

    // Este método tiene una interfaz funcional como el tipo de su primer parámetro.
    // Por lo tanto, se puede pasar una referencia a cualquier instancia de esa interfaz,
    // incluida una creada por una referencia de método.
    static boolean pruebaNum(DemoInt p, int v){
        return p.prueba(v);
    }

    public static void main(String[] args) {
        boolean resultado;

        // Aquí, una referencia de método a esPrimo se pasa a pruebaNum().
        resultado=pruebaNum(MiDemoInt::esPrimo,19);
        if (resultado) System.out.println("19 es primo");

        // A continuación, se utiliza una referencia de método a esPar().
        resultado=pruebaNum(MiDemoInt::esPar,10);
        if (resultado) System.out.println("10 es par");

        // Ahora, se pasa una referencia de método a esPositivo.
        resultado=pruebaNum(MiDemoInt::esPositivo,28);
        if (resultado) System.out.println("28 es positivo");
    }
}

Salida:

19 es primo
10 es par
28 es positivo

En el programa, preste especial atención a esta línea:

resultado=pruebaNum(MiDemoInt::esPrimo,19);

Aquí, una referencia al método estático esPrimo() se pasa como primer argumento a pruebaNum(). Esto funciona porque esPrimo es compatible con la interfaz funcional de DemoInt. Por lo tanto, la expresión MiDemoInt::esPrimo se evalúa como una referencia a un objeto en el que esPrimo() proporciona la implementación de prueba() en DemoInt. Las otras dos llamadas a pruebaNum() funcionan de la misma manera.

1.2. Método Referencias a los métodos de instancia

La sintaxis básica crea una referencia a un método de instancia en un objeto específico:

refObj::nombreMetodo

Como puede ver, la sintaxis es similar a la utilizada para un método estático, excepto que se utiliza una referencia de objeto en lugar de un nombre de clase. Por lo tanto, el método al que hace referencia el método de referencia opera en relación con refObj. El siguiente programa ilustra este punto.

Utiliza la misma interfaz DemoInt y el método prueba() que el programa anterior. Sin embargo, crea una clase llamada MiDemoInt, que almacena un valor int y define el método esDivisor(), que determina si el valor pasado es un divisor del valor almacenado por la instancia MiDemoInt. El método main() crea dos instancias MiDemoInt. Luego llama a prueba(), pasando una referencia de método al método esDivisor() y el valor que se va a verificar. En cada caso, la referencia del método opera en relación con el objeto específico.

// Use una referencia de método a un método de instancia.

// Una interfaz funcional para demostraciones numéricas que operan en valores enteros.
interface DemoInt{
    boolean prueba(int n);
}

// Esta clase almacena un valor int y define el método de instancia esDivisor()
// que devuelve true si su argumento es un divisor del valor almacenado.
class MiDemoInt{
    private int v;

    MiDemoInt(int x){v=x;}
    int getNum(){return v;}

    // Devuelve true si n es un divisor de v.
    boolean esDivisor(int n){
        return (v % n)==0;
    }
}
class MetodoRefDemo {

    public static void main(String[] args) {
        boolean resultado;

     MiDemoInt miNum = new MiDemoInt(15);
     MiDemoInt miNum2 = new MiDemoInt(18);

     // Aquí, se crea una referencia del método esDivisor() en miNum.
     DemoInt di=miNum::esDivisor;

     // Ahora, se usa para llamar a esDivisor() a través de prueba().
    resultado=di.prueba(3);
    if (resultado) System.out.println("3 es un divisor de "+miNum.getNum());

    // Esta vez, se crea una referencia del método esDivisor() en miNum2
    // y  se usa para llamar a esDivisor() a través de prueba().
    di=miNum2::esDivisor;
    resultado=di.prueba(3);
    if (resultado) System.out.println("3 es un divisor de "+miNum2.getNum());
    
    }
}

Salida:

3 es un divisor de 15
3 es un divisor de 18

En el programa, preste especial atención a la línea:

DemoInt di=miNum::esDivisor;

Aquí, la referencia de método asignada a di se refiere a un método de instancia esDivisor() en miNum. Por lo tanto, cuando se llama a prueba() a través de esa referencia, como se muestra aquí:

resultado=di.prueba(3);

el método llamará a esDivisor() en miNum, que es el objeto especificado cuando se creó la referencia del método. La misma situación ocurre con el método de referencia miNum2::esDivisor, excepto que se llamará a esDivisor() en miNum2. Esto es confirmado por la salida.

Ejemplo 2

También es posible manejar una situación en la que desee especificar un método de instancia que pueda usarse con cualquier objeto de una clase determinada, no solo un objeto especificado. En este caso, creará una referencia de método como se muestra aquí:

NombreClase::nombreMetodoInstancia

Aquí, el nombre de la clase se usa en lugar de un objeto específico, aunque se especifique un método de instancia. Con este forma, el primer parámetro de la interfaz funcional coincide con el objeto invocado y el segundo parámetro coincide con el parámetro (si lo hay) especificado por el método.

Aquí hay un ejemplo. Rehace el ejemplo anterior. En este caso, el primer parámetro para prueba() es de tipo MiDemoInt. Se usará para recibir el objeto sobre el que se opera. Esto permite que el programa cree una referencia de método al método de instancia esDivisor() que se puede usar con cualquier objeto MiDemoInt.

// Uso de una referencia de método de instancia para referirse a cualquier instancia.

// Una interfaz funcional para demostraciones numéricas que operan en valores enteros.
interface DemoInt{
    boolean prueba(MiDemoInt mv,int n);
}

// Esta clase almacena un valor int y define el método de instancia esDivisor()
// que devuelve true si su argumento es un divisor del valor almacenado.
class MiDemoInt{
    private int v;

    MiDemoInt(int x){v=x;}
    int getNum(){return v;}

    // Devuelve true si n es un divisor de v.
    boolean esDivisor(int n){
        return (v % n)==0;
    }
}
class MetodoRefDemo {

    public static void main(String[] args) {
        boolean resultado;

     MiDemoInt miNum = new MiDemoInt(15);
     MiDemoInt miNum2 = new MiDemoInt(18);

     // Esto hace que di se refiera al método de instancia esDivisor().
     DemoInt di=MiDemoInt::esDivisor;

     // Lo siguiente llama a esDivisor() en miNum.
    resultado=di.prueba(miNum,3);
    if (resultado) System.out.println("3 es un divisor de "+miNum.getNum());

    // Lo siguiente llama a esDivisor() en miNum2.
    resultado=di.prueba(miNum2,3);
    if (resultado) System.out.println("3 es un divisor de "+miNum2.getNum());
    }
}

Salida:

3 es un divisor de 15
3 es un divisor de 18

En el programa, preste especial atención a esta línea:

resultado=di.prueba(miNum,3);

Crea una referencia de método al método de instancia esDivisor() que funcionará con cualquier objeto de tipo MiDemoInt. Por ejemplo, cuando prueba() se llama a través de di, da como resultado una llamada a miNum.esDivisor(3). En otras palabras, miNum se convierte en el objeto sobre el que se llama esDivisor(3).

Una referencia de método puede usar la palabra clave super para referirse a una versión de superclase de un método. Las formas generales de la sintaxis son super::nombreMetodo y nombreTipo.super::nombreMetodo. En la segunda forma, nombreTipo debe hacer referencia a la clase adjunta o a una superinterfaz. 

2. Constructor de Referencia

Similar a la forma en que puede crear referencias a métodos, también puede crear referencias a constructores. Aquí está la forma general de la sintaxis que usará:

NombreClase::new

Esta referencia se puede asignar a cualquier referencia de interfaz funcional que defina un método compatible con el constructor. Aquí hay un ejemplo simple:

// Demostración de un Constructor de Referencia

// MiFunc es una interfaz funcional cuyo método devuelve una referencia de MiClase.
interface MiFunc{
    MiClase func (String s);
}

class MiClase{
    private String str;

    // Este constructor toma un argumento.
    MiClase(String s){str=s; }

    //Este es el constructor predeterminado.
    MiClase(){ str="";}

    String getStr(){return str;}
}
class ConstructorRefDemo {
    public static void main(String[] args) {
        // Crea una referencia al constructor MiClase.
        // Como func() en MiFunc toma un argumento,
        // new hace referencia al constructor parametrizado en MiClase, no al constructor predeterminado.

        MiFunc miFunc= MiClase::new; // Constructor de referencia

        // Crea una instancia de MiClase a través de esa referencia de constructor.
        MiClase mc= miFunc.func("Probando");

        // Use la instancia de MiClase recién creada.
        System.out.println("str en mc es: "+mc.getStr());
    }
}

Salida:

str en mc es: Probando

En el programa, observe que el método func() de MiFunc devuelve una referencia de tipo MiClase y tiene un parámetro String. A continuación, observe que MiClase define dos constructores. El primero especifica un parámetro de tipo String. El segundo es el constructor predeterminado sin parámetros. Ahora, examine la siguiente línea:

MiFunc miFunc= MiClase::new;

Aquí, la expresión MiClase::new crea una referencia de constructor a un constructor MiClase. En este caso, dado que el método func() de MiFunc toma un parámetro String, el constructor al que se hace referencia es MiClase (String s) porque es el que coincide. También observe que la referencia a este constructor está asignada a una referencia de MiFunc llamada miFunc. Después de que se ejecute esta instrucción, miFunc se puede usar para crear una instancia de MiClase, ya que esta línea muestra:

MiClase mc= miFunc.func("Probando");

En esencia, miFunc se ha convertido en otra forma de llamar a MiClase (String s). Si quería que MiClase::new usara el constructor predeterminado de MiClase, entonces necesitaría usar una interfaz funcional que defina un método que no tenga ningún parámetro. Por ejemplo, si define MiFunc2, como se muestra aquí:

interface MiFunc2{
  MiClase func;
}

entonces la siguiente línea asignará a miFunc una referencia al constructor predeterminado de MiClase (es decir, sin parámetros):

MiFunc2 miFunc = MiClase::new;

En general, el constructor que se usará cuando se especifica ::new es aquel cuyos parámetros coinciden con los especificados por la interfaz funcional.

2.1. Constructor para una clase genérica

Un último punto: en el caso de crear una referencia de constructor para una clase genérica, puede especificar el parámetro de tipo de la manera normal, después del nombre de la clase. Por ejemplo, si MiClaseGen se declara así:

MiClaseGen<T>{//...

a continuación, lo siguiente crea una referencia de constructor con un argumento de tipo Integer:

MiClaseGen<Integer>::new;

Debido a la inferencia de tipo, no siempre será necesario especificar el argumento de tipo, pero puede hacerlo cuando sea necesario.

Expresiones Lambda en Java
  • Métodos de Referencia y Constructores

Sobre el Autor:

Hey hola! Yo soy Alex Walton y tengo el placer de compartir conocimientos hacía ti sobre el tema de Programación en Java, desde cero, Online y Gratis.

Deja una Respuesta

*

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.