¿Cómo especificar los tipos de función para los métodos nulos (no nulos) en Java8?

143

Estoy jugando con Java 8 para descubrir cómo funciona como ciudadanos de primera clase. Tengo el siguiente fragmento:

package test;

import java.util.*;
import java.util.function.*;

public class Test {

    public static void myForEach(List<Integer> list, Function<Integer, Void> myFunction) {
      list.forEach(functionToBlock(myFunction));
    }

    public static void displayInt(Integer i) {
      System.out.println(i);
    }


    public static void main(String[] args) {
      List<Integer> theList = new ArrayList<>();
      theList.add(1);
      theList.add(2);
      theList.add(3);
      theList.add(4);
      theList.add(5);
      theList.add(6);
      myForEach(theList, Test::displayInt);
    }
}

Lo que intento hacer es pasar método displayInta método myForEachutilizando una referencia de método. Al compilador produce el siguiente error:

src/test/Test.java:9: error: cannot find symbol
      list.forEach(functionToBlock(myFunction));
                   ^
  symbol:   method functionToBlock(Function<Integer,Void>)
  location: class Test
src/test/Test.java:25: error: method myForEach in class Test cannot be applied to given ty
pes;
      myForEach(theList, Test::displayInt);
      ^
  required: List<Integer>,Function<Integer,Void>
  found: List<Integer>,Test::displayInt
  reason: argument mismatch; bad return type in method reference
      void cannot be converted to Void

El compilador se queja de eso void cannot be converted to Void. No sé cómo especificar el tipo de interfaz de función en la firma de myForEachtal manera que el código se compila. Sé que podría simplemente cambiar el tipo de retorno de displayInta Voidy luego regresar null. Sin embargo, puede haber situaciones en las que no sea posible alterar el método que quiero pasar a otro lugar. ¿Hay una manera fácil de reutilizar displayIntcomo es?

Valentin
fuente

Respuestas:

244

Está intentando utilizar el tipo de interfaz incorrecto. El tipo Función no es apropiado en este caso porque recibe un parámetro y tiene un valor de retorno. En su lugar, debe usar Consumidor (anteriormente conocido como Bloque)

El tipo de función se declara como

interface Function<T,R> {
  R apply(T t);
}

Sin embargo, el tipo de consumidor es compatible con el que está buscando:

interface Consumer<T> {
   void accept(T t);
}

Como tal, el consumidor es compatible con los métodos que reciben una T y no devuelven nada (nulo). Y esto es lo que quieres.

Por ejemplo, si quisiera mostrar todos los elementos en una lista, simplemente podría crear un consumidor para eso con una expresión lambda:

List<String> allJedi = asList("Luke","Obiwan","Quigon");
allJedi.forEach( jedi -> System.out.println(jedi) );

Puede ver arriba que en este caso, la expresión lambda recibe un parámetro y no tiene valor de retorno.

Ahora, si quisiera usar una referencia de método en lugar de una expresión lambda para crear un consumo de este tipo, entonces necesito un método que reciba una Cadena y devuelva vacío, ¿no?

Podría usar diferentes tipos de referencias de métodos, pero en este caso aprovechemos una referencia de método de objeto usando el printlnmétodo en el System.outobjeto, así:

Consumer<String> block = System.out::println

O simplemente podría hacer

allJedi.forEach(System.out::println);

El printlnmétodo es apropiado porque recibe un valor y tiene un tipo de retorno nulo, al igual que el acceptmétodo en Consumer.

Entonces, en su código, debe cambiar la firma de su método para algo así:

public static void myForEach(List<Integer> list, Consumer<Integer> myBlock) {
   list.forEach(myBlock);
}

Y luego debería poder crear un consumidor, utilizando una referencia de método estático, en su caso haciendo:

myForEach(theList, Test::displayInt);

En última instancia, incluso podría deshacerse de su myForEachmétodo por completo y simplemente hacer:

theList.forEach(Test::displayInt);

Acerca de las funciones como ciudadanos de primera clase

Todo dicho, la verdad es que Java 8 no tendrá funciones como ciudadanos de primera clase ya que no se agregará un tipo de función estructural al lenguaje. Java simplemente ofrecerá una forma alternativa de crear implementaciones de interfaces funcionales a partir de expresiones lambda y referencias de métodos. En última instancia, las expresiones lambda y las referencias de métodos estarán vinculadas a referencias de objetos, por lo tanto, todo lo que tenemos son objetos como ciudadanos de primera clase. Lo importante es que la funcionalidad está ahí, ya que podemos pasar objetos como parámetros, vincularlos a referencias variables y devolverlos como valores de otros métodos, luego sirven para un propósito similar.

Edwin Dalorzo
fuente
1
Por cierto, Blockse cambió para las Consumerúltimas versiones de la API JDK 8
Edwin Dalorzo
44
El último párrafo pasa por alto algunos hechos importantes: las expresiones Lambda y las referencias a métodos son más que una adición sintáctica. La propia JVM proporciona nuevos tipos para manejar referencias de métodos de manera más eficiente que con las clases anónimas (lo más importante MethodHandle), y al usar esto, el compilador de Java puede producir código más eficiente, por ejemplo, implementando lambdas sin cierre como métodos estáticos, evitando así la generación de clases adicionales
Feuermurmel
77
Y la versión correcta de Function<Void, Void>es Runnable.
OrangeDog
2
@OrangeDog Esto no es totalmente cierto. En los comentarios Runnablee Callableinterfaces se escribe que se supone que deben usarse junto con hilos . La molesta consecuencia de eso es que algunas herramientas de análisis estático (como Sonar) se quejarán si llama al run()método directamente.
Denis
Viniendo únicamente de un entorno Java, me pregunto cuáles son los casos en particular en los que Java no puede tratar las funciones como ciudadanos de primera clase incluso después de la introducción de interfaces funcionales y lambdas desde Java8.
Vivek Sethi
21

Cuando necesita aceptar una función como argumento que no toma argumentos y no devuelve ningún resultado (nulo), en mi opinión, es mejor tener algo como

  public interface Thunk { void apply(); }

en algún lugar de tu código. En mis cursos de programación funcional, la palabra 'thunk' se usaba para describir tales funciones. Por qué no está en java.util.function está más allá de mi comprensión.

En otros casos, encuentro que incluso cuando java.util.function tiene algo que coincide con la firma que quiero, todavía no siempre se siente bien cuando el nombre de la interfaz no coincide con el uso de la función en mi código. Supongo que es un punto similar que se hace en otra parte aquí con respecto a 'Runnable', que es un término asociado con la clase Thread, por lo que si bien puede tener la firma que necesito, es probable que confunda al lector.

LOAS
fuente
En particular Runnable, no permite excepciones marcadas, lo que lo hace inútil para muchas aplicaciones. Como Callable<Void>tampoco es útil, debe definir el suyo propio. Feo.
Jesse Glick
En referencia al problema de excepción comprobado, las nuevas funciones de Java en general luchan con eso. Es un gran bloqueador para algunas cosas como la API Stream. Muy mal diseño por su parte.
user2223059
0

Establezca el tipo de retorno en Voidlugar de voidyreturn null

// Modify existing method
public static Void displayInt(Integer i) {
    System.out.println(i);
    return null;
}

O

// Or use Lambda
myForEach(theList, i -> {System.out.println(i);return null;});
Dojo
fuente
-2

Siento que deberías estar usando la interfaz de consumidor en lugar de Function<T, R>.

Un consumidor es básicamente una interfaz funcional diseñada para aceptar un valor y no devolver nada (es decir, nulo)

En su caso, puede crear un consumidor en otra parte de su código como este:

Consumer<Integer> myFunction = x -> {
    System.out.println("processing value: " + x);    
    .... do some more things with "x" which returns nothing...
}

Luego puede reemplazar su myForEachcódigo con el siguiente fragmento:

public static void myForEach(List<Integer> list, Consumer<Integer> myFunction) 
{
  list.forEach(x->myFunction.accept(x));
}

Tratas myFunction como un objeto de primera clase.

Anthony Anyanwu
fuente
2
¿Qué agrega esto más allá de las respuestas existentes?
Matthew leyó el