sentencia - ¿Cómo se especifican los tipos de función para los métodos void(no Void) en Java8?
sentencia return java (2)
Estoy jugando con Java 8 para descubrir cómo funcionan las funciones de 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 trato de hacer es pasar el método displayInt
al método myForEach
usando 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 que void cannot be converted to Void
. No sé cómo especificar el tipo de la interfaz de la función en la firma de myForEach
modo que el código compila. Sé que podría simplemente cambiar el tipo de displayInt
de displayInt
a Void
y luego devolver null
. Sin embargo, puede haber situaciones en las que no sea posible alterar el método que quiero aprobar en otro lugar. ¿Hay alguna manera fácil de reutilizar displayInt
tal como es?
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 así como
private interface Thunk { void apply(); }
en algún lugar de tu código. En mis cursos de programación funcional la palabra ''thunk'' se usó para describir tales funciones. Por qué no está en java.util.function está más allá de mi comprensión.
En otros casos, me parece que incluso cuando java.util.function tiene algo que coincide con la firma que quiero, todavía no me siento 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 otro lugar aquí con respecto a ''Runnable'', que es un término asociado con la clase Thread, así que aunque pueda tener la firma que necesito, es probable que confunda al lector.
Está intentando usar el tipo de interfaz incorrecto. El tipo Function no es apropiado en este caso porque recibe un parámetro y tiene un valor de retorno. En su lugar, debe usar Consumer (anteriormente conocido como Block)
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 lo que está buscando:
interface Consumer<T> {
void accept(T t);
}
Como tal, Consumer es compatible con 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 un método de referencia en lugar de una expresión lambda para crear un consumo de este tipo, entonces necesito un método que reciba un String y devuelva el vacío, ¿verdad ?.
Podría usar diferentes tipos de referencias a métodos, pero en este caso aprovechemos una referencia a métodos de objetos utilizando el método println
en el objeto System.out
, como este:
Consumer<String> block = System.out::println
O simplemente podría hacer
allJedi.forEach(System.out::println);
El método println
es apropiado porque recibe un valor y tiene un tipo de devolución anulado, al igual que el método accept
en Consumer.
Por lo tanto, en su código, debe cambiar la firma de su método de la siguiente manera:
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 método myForEach
completo y simplemente hacer:
theList.forEach(Test::displayInt);
Acerca de las funciones como ciudadanos de primera clase
Dicho todo esto, la verdad es que Java 8 no tendrá funciones como ciudadanos de primera clase ya que un tipo de función estructural no se agregará al idioma. Java simplemente ofrecerá azúcar sintáctico para 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 se vincularán a las referencias de objeto, 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, y luego cumplen una función similar.