java - not - stream supplier
Cast java.util.function.Function a la interfaz (3)
Ejemplo
En este ejemplo (simplificado) puedo crear mi objeto MyInterface
utilizando una referencia de método para apply
, pero la conversión no funciona directamente.
@Test
public void testInterfaceCast(){
Function<String, Integer> func = Integer::parseInt;
MyInterface legal = func::apply; // works
MyInterface illegal = func; // error
}
public interface MyInterface extends Function<String, Integer>{}
La segunda asignación da el error del compilador:
incompatible types: Function<String,Integer> cannot be converted to MyInterface
La pregunta
¿Puedo hacer algo de magia genérica para poder lanzar una Function<T, R>
a una interfaz?
En el fondo de esto está el hecho de que, a pesar de toda la sintaxis bonita que da tal apariencia superficialmente, Java no ha adoptado tipos estructurales; Las reglas de la sustituibilidad de Liskov de los tipos nombrados se mantienen tan estrictamente como siempre. En tu código de la vida real, llamas Function.andThen
, que devuelve alguna instancia de Function
, y definitivamente no es una instancia de MyInterface
. Así que cuando escribes
MyInterface legal = func::apply;
obtendrás otro objeto, que es una instancia de MyInterface
. Por el contrario, cuando escribes
MyInterface illegal = func;
está intentando asignar una referencia a la instancia existente de Function
directamente como illegal
, lo que violaría la posibilidad de sustitución de Liskov aunque coincida estructuralmente con el tipo. Ningún truco puede existir para solucionar esto fundamentalmente; incluso si Java introdujera algo de azúcar sintáctico nuevo que lo hiciera parecer un casting, las semánticas reales seguirían siendo las de conversión (evaluando a otra instancia).
La razón MyInterface illegal = func;
no funciona, es porque func
se declara como una variable de tipo Function<String,Integer>
, que no es un subtipo de MyInterface
.
La razón por la que MyInterface legal = func::apply;
El trabajo es el siguiente. Podría esperar que el tipo fun::apply
sea también Function<String,Integer>
, pero este no es el caso. El tipo de fun::apply
depende en parte de qué tipo espera el compilador. Como lo usa en un contexto de asignación, el compilador espera una expresión de tipo MyInterface
y, por lo tanto, en ese contexto, func::apply
es de tipo MyInterface
. Es por este motivo que la expresión de referencia del método solo puede aparecer en contextos de asignación, contextos de invocación y contextos de conversión (consulte la Especificación del lenguaje Java ).
Como la Function<String,Integer>
no es un subtipo de MyInterface
, la func
conversión a MyInterface
lanza una MyInterface
ClassCastException
. Por lo tanto, la forma más clara de convertir una Function<String,Integer>
a un MyInterface
es usar una referencia de método, como ya hizo:
MyInterface legal = func::apply;
No se compila por la misma razón que este fragmento de código no se compila:
Animal animal = new Animal();
Cat x = animal; //illegal assignment
es decir, estás insinuando que cada animal puede ser un gato. El compilador no tiene pruebas de que la instancia de la superclase sea realmente un gato en tiempo de ejecución y, por lo tanto, genere un error.
MyInterface
asignar Integer::parseInt
a una instancia de MyInterface
:
MyInterface func = Integer::parseInt;
MyInterface illegal = func;
como func
es de tipo Function<String, Integer>
(es decir, el MyInterface
de MyInterface
).
La verdad es que no hay forma de hacer Integer::parseInt
de tipo MyInterface
en tiempo de compilación, a menos que se lo asigne a la variable MyInterface
.