util predefined interfaz interfaces functional funcionalinterface funcionales funcional example ejemplo java lambda java-8 classcastexception functional-interface

interfaz - java lambda predefined interfaces



Casting de interfaces funcionales de Java (3)

Porque la clase implementadora podría implementar ambas interfaces.

Es legal convertir cualquier tipo en cualquier tipo de interfaz, siempre que el objeto que se está pasando pueda implementar la interfaz de destino . En el momento de la compilación, se sabe que esto es falso cuando el tipo de origen es una clase final que no implementa la interfaz, o cuando se puede probar que tiene una parametrización de tipo diferente que resulta en el mismo borrado. En tiempo de ejecución , si el objeto no implementa la interfaz, obtendrá una ClassCastException . La verificación con instanceof antes de intentar emitir le permite evitar la excepción.

De la especificación del lenguaje Java, 5.5.1: conversión de tipos de referencia :

5.5.1 Conversión de tipos de referencia Dado un tipo de referencia de compilación S (fuente) y un tipo de referencia de compilación T (destino), existe una conversión de conversión de S a T si no se producen errores de compilación debido a las siguientes reglas.

...

• Si T es un tipo de interfaz: - Si S no es una clase final (§8.1.1), entonces, si existe un supertipo X de T y un supertipo Y de S, tal que tanto X como Y son claramente distintos tipos parametrizados, y que los borrados de X e Y son los mismos, se produce un error en tiempo de compilación.

De lo contrario, la conversión siempre es legal en tiempo de compilación (porque incluso si S no implementa T, una subclase de S podría).

Como siempre, revisé las fuentes de JDK 8 y encontré un código muy interesante:

@Override default void forEachRemaining(Consumer<? super Integer> action) { if (action instanceof IntConsumer) { forEachRemaining((IntConsumer) action); } }

La pregunta es: ¿cómo Consumer<? super Integer> Consumer<? super Integer> podría ser una instancia de IntConsumer ? Porque están en jerarquías diferentes.

He hecho un fragmento de código similar para probar el casting:

public class InterfaceExample { public static void main(String[] args) { IntConsumer intConsumer = i -> { }; Consumer<Integer> a = (Consumer<Integer>) intConsumer; a.accept(123); } }

Pero arroja ClassCastException :

Exception in thread "main" java.lang.ClassCastException: com.example.InterfaceExample$$Lambda$1/764977973 cannot be cast to java.util.function.Consumer

Puede encontrar este código en java.util.Spliterator.OfInt#forEachRemaining(java.util.function.Consumer)


Tenga en cuenta que podría haber encontrado este comportamiento sin mirar el código fuente, solo mirando java.util.Spliterator.OfInt#forEachRemaining(java.util.function.Consumer) , se ha vinculado usted mismo:

Requisitos de implementación:

Si la acción es una instancia de IntConsumer , se IntConsumer en IntConsumer y se pasa a forEachRemaining(java.util.function.IntConsumer) ; de lo contrario, la acción se adapta a una instancia de IntConsumer , encajonando el argumento de IntConsumer y luego se pasa a forEachRemaining(java.util.function.IntConsumer) .

Entonces, en cualquier caso, se forEachRemaining(IntConsumer) , que es el método de implementación real. Pero cuando sea posible, se omitirá la creación de un adaptador de boxeo. La razón es que un Spliterator.OfInt también es un Spliterator<Integer> , que solo ofrece el forEachRemaining(Consumer<Integer>) . El comportamiento especial permite tratar las instancias genéricas de Spliterator y sus homólogos primitivos ( Spliterator.OfPrimitive ) por igual, con una selección automática del método más eficiente.

Como han dicho otros, puede implementar múltiples interfaces con una clase ordinaria. Además, puede implementar múltiples interfaces con una expresión lambda, si crea un tipo de ayuda, por ejemplo,

interface UnboxingConsumer extends IntConsumer, Consumer<Integer> { public default void accept(Integer t) { System.out.println("unboxing "+t); accept(t.intValue()); } } public static void printAll(BaseStream<Integer,?> stream) { stream.spliterator().forEachRemaining((UnboxingConsumer)System.out::println); } public static void main(String[] args) { System.out.println("Stream.of(1, 2, 3):"); printAll(Stream.of(1, 2, 3)); System.out.println("IntStream.range(0, 3)"); printAll(IntStream.range(0, 3)); }

Stream.of(1, 2, 3): unboxing 1 1 unboxing 2 2 unboxing 3 3 IntStream.range(0, 3) 0 1 2


Veamos el código de abajo, entonces puedes ver por qué?

class IntegerConsumer implements Consumer<Integer>, IntConsumer { ... }

Cualquier clase puede implementar interfaces múltiples, una es Consumer<Integer> tal vez implementa otra es IntConsumer . A veces ocurre cuando queremos adaptar IntConsumer a Consumer<Integer> y guardar su tipo de origen ( IntConsumer ), luego el código se ve como sigue:

class IntConsumerAdapter implements Consumer<Integer>, IntConsumer { @Override public void accept(Integer value) { accept(value.intValue()); } @Override public void accept(int value) { // todo } }

Nota : es el uso del patrón de diseño de adaptador de clase .

ENTONCES , puede usar IntConsumerAdapter como Consumer<Integer> e IntConsumer , por ejemplo:

Consumer<? extends Integer> consumer1 = new IntConsumerAdapter(); IntConsumer consumer2 = new IntConsumerAdapter();

Sink.OfInt es un uso concreto del patrón de diseño del adaptador de clase en jdk-8. La desventaja de Sink.OfInt#accept(Integer) es que JVM lanzará una NullPointerException cuando acepte un valor null , por lo que esa es la razón por la que Sink es el paquete visible.

189 interface OfInt extends Sink<Integer>, IntConsumer {
190 @Override
191 void accept(int value);
193 @Override
194 default void accept(Integer i) {
195 if (Tripwire.ENABLED)
196 Tripwire.trip(getClass(), "{0} calling Sink.OfInt.accept(Integer)");
197 accept(i.intValue());
198 }
199 }

Encontré por qué necesito IntConsumer un Consumer<Integer> a un IntConsumer si se pasa un consumidor como IntConsumerAdapter ?

Una razón es cuando usamos un Consumer para aceptar un int el compilador necesita para encasillarlo automáticamente en un Integer . Y en el método accept(Integer) necesitas desempaquetar un Integer a un int manualmente. En otras palabras, cada accept(Integer) realiza 2 operaciones adicionales para el boxeo / desempaquetado. Necesita mejorar el rendimiento, por lo que realiza algunas comprobaciones especiales en la biblioteca de algoritmos.

Otra razón es reutilizar un trozo de código. El cuerpo de java.util.Spliterator.OfInt#forEachRemaining(java.util.function.Consumer) es un buen ejemplo de la aplicación del Patrón de diseño del adaptador para reutilizar OfInt#forEachRenaming(IntConsumer) .

default void forEachRemaining(Consumer<? super Integer> action) { if (action instanceof IntConsumer) { // action''s implementation is an example of Class Adapter Design Pattern // | forEachRemaining((IntConsumer) action); } else { // method reference expression is an example of Object Adapter Design Pattern // | forEachRemaining((IntConsumer) action::accept); } }