java - thread - Runnable:: nuevo vs nuevo Runnable()
threads en java 8 (4)
¿Por qué no funciona el primero de los siguientes ejemplos?
-
run(R::new);
MétodoR.run
no se llama. -
run(new R());
Método se llamaR.run
.
Ambos ejemplos son compilables.
public class ConstructorRefVsNew {
public static void main(String[] args) {
new ConstructorRefVsNew().run(R::new);
System.out.println("-----------------------");
new ConstructorRefVsNew().run(new R());
}
void run(Runnable r) {
r.run();
}
static class R implements Runnable {
R() {
System.out.println("R constructor runs");
}
@Override
public void run() {
System.out.println("R.run runs");
}
}
}
La salida es:
R constructor runs
-----------------------
R constructor runs
R.run runs
En el primer ejemplo, se llama al constructor
R
, devuelve lambda (que no es un objeto):
Pero entonces, ¿cómo es posible que el ejemplo se compile con éxito?
Compara dos llamadas:
((Runnable)() -> new R()).run();
new R().run();
Por
((Runnable)() -> new R())
o
((Runnable) R::new)
, crea
un nuevo
Runnable
que no hace nada
1
.
Con la
new R()
, creas
una instancia de la clase
R
donde el método de
run
está bien definido.
1
En realidad, crea un objeto de
R
que no tiene impacto en la ejecución.
Estaba pensando en tratar 2 invocaciones de manera idéntica sin modificar el método
main
.
Necesitaríamos sobrecargar la
run(Runnable)
con la
run(Supplier<Runnable>)
.
class ConstructorRefVsNew {
public static void main(String[] args) {
new ConstructorRefVsNew().run(R::new);
System.out.println("-----------------------");
new ConstructorRefVsNew().run(new R());
}
void run(Runnable r) {
r.run();
}
void run(Supplier<Runnable> s) {
run(s.get());
}
static class R implements Runnable { ... }
}
El método de
run
espera un
Runnable
.
El caso fácil es
new R()
.
En este caso, usted sabe que el resultado es un objeto de tipo
R
R
sí es ejecutable, tiene un método de
run
, y así es como lo ve Java.
Pero cuando pasas
R::new
algo
R::new
está sucediendo.
Lo que le dice es que cree un objeto anónimo compatible con un
Runnable
cuyo método de
run
ejecuta la operación que le pasó.
La operación que pasaste no es el método de
run
R
La operación es el costructor de
R
Por lo tanto, es como si le hubieras pasado una clase anónima como:
new Runnable() {
public void run() {
new R();
}
}
(No todos los detalles son iguales, pero esta es la construcción Java "clásica" más cercana).
R::new
, cuando se llama, llama a
new R()
.
Nada más y nada menos.
El primer ejemplo de referencia de método de uso de java 8 :
new ConstructorRefVsNew().run(R::new);
es más o menos equivalente a: ( Java 8 lambda expresión )
new ConstructorRefVsNew().run( () -> {new R();} );
El efecto es que solo creas una instancia de R pero no llamas a su método de
run
.
Su método de
run
toma una instancia de
Runnable
, y eso explica por qué
run(new R())
funciona con la implementación de
R
R::new
no es equivalente a
new R()
.
Puede ajustarse a la firma de un
Supplier<Runnable>
(o interfaces funcionales similares), pero
R::new
no se puede usar como
Runnable
implementado con su clase
R
Una versión de su método de
run
que puede tomar
R::new
podría tener este aspecto (pero esto sería innecesariamente complejo):
void run(Supplier<Runnable> r) {
r.get().run();
}
¿Por qué se compila?
Debido a que el compilador puede hacer un
Runnable
fuera de la llamada del constructor, y eso sería equivalente a esta versión de la expresión lambda:
new ConstructorRefVsNew().run(() -> {
new R(); //discarded result, but this is the run() body
});
Lo mismo se aplica a estas declaraciones:
Runnable runnable = () -> new R();
new ConstructorRefVsNew().run(runnable);
Runnable runnable2 = R::new;
new ConstructorRefVsNew().run(runnable2);
Pero, como puede observar, el
Runnable
creado con
R::new
simplemente llama a
new R()
en su cuerpo del método de
run
.
Un uso válido de una referencia de método para ejecutar
R#run
podría usar una instancia como esta (pero seguramente preferiría usar la instancia
r
directamente, en este caso):
R r = new R();
new ConstructorRefVsNew().run(r::run);