Vino, vio, generalizó: inmerso en Java Generics

Java Generics es uno de los cambios más significativos en la historia del lenguaje Java. Los genéricos disponibles con Java 5 han hecho que usar Java Collection Framework sea más fácil, más conveniente y más seguro. Los errores asociados con el uso incorrecto de los tipos ahora se detectan en la etapa de compilación. Sí, y el lenguaje Java en sí se ha vuelto aún más seguro. A pesar de la aparente simplicidad de los tipos genéricos, muchos desarrolladores tienen dificultades para usarlos. En esta publicación hablaré sobre las características de trabajar con Java Generics, para que tenga menos de estas dificultades. Es útil si no es un gurú genérico y ayudará a evitar muchas dificultades al sumergirse en el tema.



Trabaja con colecciones


Supongamos que un banco necesita calcular la cantidad de ahorro en las cuentas de los clientes. Antes del advenimiento de los "genéricos", el método de cálculo de la suma se veía así:

public long getSum(List accounts) {   long sum = 0;   for (int i = 0, n = accounts.size(); i < n; i++) {       Object account = accounts.get(i);       if (account instanceof Account) {           sum += ((Account) account).getAmount();       }   }   return sum; } 

Repetimos, revisamos la lista de cuentas y verificamos si el elemento de esta lista es realmente una instancia de la clase Account , es decir, la cuenta del usuario. El tipo de nuestro objeto de la clase Account y el método getAmount se getAmount , lo que devolvió el monto en esta cuenta. Luego lo resumieron todo y devolvieron la cantidad total. Se requieren dos pasos:
 if (account instanceof Account) { // (1) 

 sum += ((Account) account).getAmount(); // (2) 

Si no marca ( instanceof ) para pertenecer a la clase Account , entonces en la segunda etapa es posible una ClassCastException es decir, un bloqueo del programa. Por lo tanto, tal verificación era obligatoria.

Con el advenimiento de los genéricos, la necesidad de verificación de tipo y conversión ha desaparecido:
 public long getSum2(List<Account> accounts) {  long sum = 0;  for (Account account : accounts) {      sum += account.getAmount();  }  return sum; } 

Ahora método
 getSum2(List<Account> accounts) 
acepta como argumentos solo una lista de objetos de la clase Account . Esta restricción se indica en el método en sí, en su firma, el programador simplemente no puede transferir ninguna otra lista, solo la lista de cuentas de clientes.

No necesitamos verificar el tipo de elementos de esta lista: está implícito en la descripción del tipo del parámetro del método
 List<Account> accounts 
(puede leerse como una Account ). Y el compilador arrojará un error si algo sale mal, es decir, si alguien intenta pasar una lista de objetos distintos de la clase Account a este método.

En la segunda línea del cheque, la necesidad también desapareció. Si es necesario, el casting se realizará en la etapa de compilación.

Principio de sustitución


El principio de sustitución de Barbara Liskov es una definición específica de un subtipo en la programación orientada a objetos. La idea de Liskov de un "subtipo" define el concepto de sustitución: si S es un subtipo de T , entonces los objetos de tipo T en un programa pueden ser reemplazados por objetos de tipo S sin ningún cambio en las propiedades deseadas de este programa.

Tipo
Subtipo
Numero
Entero
Lista <E>
ArrayList <E>
Colección <E>
Lista <E>
Iterable <E>
Colección <E>

Ejemplos de relación de tipo / subtipo

Aquí hay un ejemplo del uso del principio de sustitución en Java:
 Number n = Integer.valueOf(42); List<Number> aList = new ArrayList<>(); Collection<Number> aCollection = aList; Iterable<Number> iterable = aCollection; 

Integer es un subtipo de Number , por lo tanto, a la variable n tipo Number se le puede asignar el valor que devuelve el método Integer.valueOf(42) .

Covarianza, contravarianza e invariancia


Primero, una pequeña teoría. La covarianza es la preservación de la jerarquía de herencia de los tipos de origen en tipos derivados en el mismo orden. Por ejemplo, si el Gato es un subtipo de Animales , entonces el Conjunto de <Gatos> es un subtipo del Conjunto de <Animales> . Por lo tanto, teniendo en cuenta el principio de sustitución, se puede realizar la siguiente asignación:

Many <Animals> = Many <Cats>

La contravarianza es la inversión de la jerarquía de los tipos de origen en los tipos derivados. Por ejemplo, si el Gato es un subtipo de los , entonces el Conjunto <Animales> es un subtipo del Conjunto de <Gatos> . Por lo tanto, teniendo en cuenta el principio de sustitución, se puede realizar la siguiente asignación:

Many <Cats> = Many <Animals>

Invarianza: falta de herencia entre los tipos derivados. Si el Gato es un subtipo de Animales , entonces el Conjunto de <Gatos> no es un subtipo del Conjunto de <Animales> y el Conjunto de <Animales> no es un subtipo del Conjunto de <Gatos> .

Las matrices en Java son covariantes . El tipo S[] es un subtipo de T[] si S es un subtipo de T Ejemplo de asignación:
 String[] strings = new String[] {"a", "b", "c"}; Object[] arr = strings; 

Asignamos un enlace a una matriz de cadenas a la variable arr , cuyo tipo es « » . Si las matrices no fueran covariantes, no podríamos hacer esto. Java le permite hacer esto, el programa compila y se ejecuta sin errores.

 arr[0] = 42; // ArrayStoreException.       

Pero si tratamos de cambiar el contenido de la matriz a través de la variable arr y escribimos el número 42 allí, obtendremos una ArrayStoreException en la etapa de ejecución del programa, ya que 42 no es una cadena, sino un número. Este es el inconveniente de la covarianza de las matrices Java: no podemos realizar comprobaciones en la etapa de compilación, y algo puede romperse ya en tiempo de ejecución.

Los "genéricos" son invariables. Aquí hay un ejemplo:
 List<Integer> ints = Arrays.asList(1,2,3); List<Number> nums = ints; // compile-time error.      nums.set(2, 3.14); assert ints.toString().equals("[1, 2, 3.14]"); 

Si toma una lista de enteros, entonces no será un subtipo de tipo Number , ni ningún otro subtipo. Él es solo un subtipo de sí mismo. Es decir, List <Integer> es una List<Integer> y nada más. El compilador se asegurará de que la variable ints declarada como una lista de objetos de la clase Integer contenga solo objetos de la clase Integer y nada más. En la etapa de compilación, se realiza una verificación y nada caerá en nuestro tiempo de ejecución.

Comodines


¿Los genéricos son siempre invariantes? No Daré ejemplos:
 List<Integer> ints = new ArrayList<Integer>(); List<? extends Number> nums = ints; 

Esto es covarianza. List<Integer> - subtipo de List<? extends Number> List<? extends Number>

 List<Number> nums = new ArrayList<Number>(); List<? super Integer> ints = nums; 

Esto es contravarianza. List<Number> es un subtipo de List<? super Integer> List<? super Integer> .

Un registro como "? extends ..." o "? super ..." se llama comodín o comodín, con un límite superior (se extends ) o un límite inferior ( super ). List<? extends Number> List<? extends Number> puede contener objetos cuya clase es Number o hereda de Number . List<? super Number> List<? super Number> puede contener objetos cuya clase es Number o cuyo Number es un heredero (supertipo de Number ).


extiende B - comodín con límite superior
super B - comodín con un límite inferior
donde B - representa el borde

Un registro de la forma T 2 <= T 1 significa que el conjunto de tipos descritos por T 2 es un subconjunto del conjunto de tipos descritos por T 1

es decir
Número <=? extiende objeto
? extiende Número <=? extiende objeto
y
? super objeto <=? super numero


Más interpretación matemática del tema.

Un par de tareas para probar el conocimiento:

1. ¿Por qué aparece el error en tiempo de compilación en el siguiente ejemplo? ¿Qué valor puedo agregar a la lista de nums ?
 List<Integer> ints = new ArrayList<Integer>(); ints.add(1); ints.add(2); List<? extends Number> nums = ints; nums.add(3.14); // compile-time error 

La respuesta
¿Debería declararse el contenedor con comodín ? extends ? extends , solo puede leer los valores. No se puede agregar nada a la lista excepto null . Para agregar un objeto a la lista necesitamos otro tipo de comodín - ? super ? super


2. ¿Por qué no puedo obtener un artículo de la lista a continuación?
 public static <T> T getFirst(List<? super T> list) {  return list.get(0); // compile-time error } 

La respuesta
¿No puede leer un elemento de un contenedor con comodín ? super ? super , excepto por un objeto de clase Object

 public static <T> Object getFirst(List<? super T> list) {  return list.get(0); } 



El Principio Get and Put o PECS (Productor Extiende Consumer Super)


La función comodín con límites superior e inferior ofrece características adicionales relacionadas con el uso seguro de los tipos. Solo puede leer de un tipo de variable, solo escribir en otro (la excepción es la capacidad de escribir null para extends y leer Object para super ). Para que sea más fácil recordar cuándo usar qué comodín, existe el principio PECS: Producer Extender Consumer Super.

  • Si declaramos un comodín con extensiones , entonces este es el productor . Él solo "produce", proporciona un elemento del contenedor y no acepta nada.
  • Si anunciamos un comodín con super , entonces esto es consumidor . Solo acepta, pero no puede proporcionar nada.

Considere usar Wildcard y el principio PECS usando el método de copia en la clase java.util.Collections como ejemplo.

 public static <T> void copy(List<? super T> dest, List<? extends T> src) { … } 

El método copia elementos de la lista src original a la lista dest . src : ¿declarado con comodín ? extends ? extends y es el productor, y se declara dest con comodín ? super ? super y es un consumidor. Dada la covarianza y contravarianza del comodín, puede copiar elementos de la lista de ints a la lista de nums :
 List<Number> nums = Arrays.<Number>asList(4.1F, 0.2F); List<Integer> ints = Arrays.asList(1,2); Collections.copy(nums, ints); 


Si confundimos los parámetros del método de copia por error e intentamos copiar de la lista de nums a la lista de ints , el compilador no nos permitirá hacer esto:
 Collections.copy(ints, nums); // Compile-time error 


<?> y tipos sin formato


A continuación se muestra un comodín con un comodín ilimitado. Acabamos de poner <?> , Sin las palabras clave super o extends :
 static void printCollection(Collection<?> c) {  // a wildcard collection  for (Object o : c) {      System.out.println(o);  } } 


De hecho, dicho comodín "ilimitado" sigue siendo limitado, desde arriba. Collection<?> También es un comodín, como " ? extends Object ". Un registro de la forma Collection<?> equivalente a la Collection<? extends Object> Collection<? extends Object> , lo que significa que la colección puede contener objetos de cualquier clase, ya que todas las clases en Java heredan de Object , por lo que la sustitución se llama ilimitada.

Si omitimos la indicación de tipo, por ejemplo, como aquí:
 ArrayList arrayList = new ArrayList(); 

luego dicen que ArrayList es el tipo Raw del ArrayList parametrizado <T> . Con los tipos sin formato, volvemos a la era de los genéricos y abandonamos conscientemente todas las características inherentes a los tipos con parámetros.

Si intentamos llamar a un método parametrizado en el tipo Raw, el compilador nos dará una advertencia de "Llamada no verificada". Si intentamos asignar una referencia a un tipo Raw parametrizado a un tipo, el compilador emitirá una advertencia "Asignación no verificada". Ignorar estas advertencias, como veremos más adelante, puede provocar errores durante la ejecución de nuestra aplicación.
 ArrayList<String> strings = new ArrayList<>(); ArrayList arrayList = new ArrayList(); arrayList = strings; // Ok strings = arrayList; // Unchecked assignment arrayList.add(1); //unchecked call 


Captura de comodines


Ahora intentemos implementar un método que permute los elementos de una lista en el orden inverso.

 public static void reverse(List<?> list); // ! public static void reverse(List<?> list) { List<Object> tmp = new ArrayList<Object>(list); for (int i = 0; i < list.size(); i++) {   list.set(i, tmp.get(list.size()-i-1)); // compile-time error } } 

Se produjo un error de compilación porque el método reverse toma una lista con un carácter comodín ilimitado <?> Como argumento.
<?> significa lo mismo que <? extends Object> <? extends Object> . Por lo tanto, de acuerdo con el principio PECS, la list es el producer . Y el producer solo produce elementos. Y en el bucle for llamamos al método set() , es decir tratando de escribir en la list . Y así descansamos contra la protección de Java, que no nos permite establecer algún valor por índice.

Que hacer El patrón de Wildcard Capture nos ayudará. Aquí creamos un método genérico de rev . Se declara con una variable de tipo T Este método acepta una lista de tipos T , y podemos hacer un conjunto.
 public static void reverse(List<?> list) { rev(list); } private static <T> void rev(List<T> list) { List<T> tmp = new ArrayList<T>(list); for (int i = 0; i < list.size(); i++) {   list.set(i, tmp.get(list.size()-i-1)); } } 

Ahora todo se compilará con nosotros. La captura de comodines fue capturada aquí. Cuando se llama al método reverse(List<?> list) , se pasa una lista de algunos objetos (por ejemplo, cadenas o enteros) como argumento. Si podemos capturar el tipo de estos objetos y asignarlo a una variable de tipo X , entonces podemos concluir que T es X

Puede leer más sobre Wildcard Capture aquí y aquí .

Conclusión


Si necesita leer desde el contenedor, utilice un comodín con el borde superior " ? extends ". Si necesita escribir en el contenedor, utilice un comodín con un borde inferior de " ? super ". No use comodines si necesita grabar y leer.

¡No use tipos Raw ! Si el argumento de tipo no está definido, utilice el comodín <?> .

Escribir variables


Cuando escribimos el identificador entre paréntesis angulares, por ejemplo, <T> o <E> al declarar una clase o método, creamos una variable de tipo . Una variable de tipo es un identificador no calificado que se puede usar como un tipo en el cuerpo de una clase o método. Una variable de tipo se puede acotar arriba.
 public static <T extends Comparable<T>> T max(Collection<T> coll) { T candidate = coll.iterator().next(); for (T elt : coll) {   if (candidate.compareTo(elt) < 0) candidate = elt; } return candidate; } 

En este ejemplo, la expresión T extends Comparable<T> define T (una variable de tipo) limitada anteriormente por el tipo Comparable<T> . A diferencia del comodín, las variables de tipo solo se pueden limitar en la parte superior (solo se extends ). No se puede escribir super . Además, en este ejemplo, T depende de sí mismo, se llama recursive bound , un borde recursivo.

Aquí hay otro ejemplo de la clase Enum:
 public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable 

Aquí, la clase Enum se parametriza por tipo E, que es un subtipo de Enum<E> .

Límites múltiples


Multiple Bounds : múltiples restricciones. Está escrito a través del carácter " & ", es decir, decimos que el tipo representado por una variable de tipo T debe estar limitado desde arriba por la clase Object y la interfaz Comparable .

 <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) 

Grabar Object & Comparable<? super T> Object & Comparable<? super T> forma el tipo de intersección Multiple Bounds . La primera limitación, en este caso, Object , se usa para erasure , el proceso de maceración de tipos. Lo realiza el compilador en la etapa de compilación.

Conclusión


Una variable de tipo solo se puede limitar sobre uno o más tipos. En el caso de restricciones múltiples, el borde izquierdo (la primera restricción) se usa en el proceso de sobrescritura (Borrado de tipo).

Tipo de borrado


Type Erasure es una asignación de tipos (posiblemente incluyendo tipos parametrizados y variables de tipo) a tipos que nunca son tipos parametrizados o tipos variables. Escribimos el tipo T mashing como |T| .

La pantalla de maceración se define de la siguiente manera:
  • Mashing el tipo parametrizado G < T1 , ..., Tn > es | G |
  • Machacar un tipo anidado TC es | T |. C
  • Mashing el tipo de matriz T [] es | T | []
  • Mezclar una variable de tipo es machacar su borde izquierdo
  • Mashing cualquier otro tipo es este tipo en sí


Durante la ejecución de Type Erasure (type mashing), el compilador realiza las siguientes acciones:
  • agrega fundición de tipos para proporcionar seguridad de tipo si es necesario
  • genera métodos de puente para mantener el polimorfismo


T (tipo)
| T | (Tipo de trituración)
List <Integer>, List <String>, List <List <String >>
Lista
List <Integer> []
Lista []
Lista
Lista
int
int
Entero
Entero
<T extiende Comparable <T>>
Comparable
<T extiende Objeto y Comparable <? super T >>
Objeto
LinkedCollection <E> .Node
LinkedCollection.Node

Esta tabla muestra en qué se convierten los diferentes tipos durante el proceso de maceración, borrado de tipos.

En la siguiente captura de pantalla hay dos ejemplos del programa:


La diferencia entre los dos es que se produce un error en tiempo de compilación a la izquierda y a la derecha todo se compila sin errores. Por qué

La respuesta
En Java, dos métodos diferentes no pueden tener la misma firma. En el proceso Type Erasure, el compilador agregará el método bridge public int compareTo(Object o) . Pero la clase ya contiene un método con tal firma que causará un error durante la compilación.

Compile la clase Name quitando el compareTo(Object o) y observe el bytecode resultante usando javap:
 # javap Name.class Compiled from "Name.java" public class ru.sberbank.training.generics.Name implements java.lang.Comparable<ru.sberbank.training.generics.Name> { public ru.sberbank.training.generics.Name(java.lang.String); public java.lang.String toString(); public int compareTo(ru.sberbank.training.generics.Name); public int compareTo(java.lang.Object); } 

Vemos que la clase contiene un int compareTo(java.lang.Object) , aunque lo eliminamos del código fuente. Este es el método puente que agregó el compilador.


Tipos reificables


En Java, decimos que un tipo es reifiable si su información es totalmente accesible en tiempo de ejecución. Los tipos reificables incluyen:
  • Tipos primitivos ( int , long , boolean )
  • Tipos no parametrizados (no genéricos) ( String , Integer )
  • Tipos parametrizados cuyos parámetros se representan como comodines ilimitados (caracteres comodín ilimitados) ( Lista <?> , Colección <?> )
  • Tipos sin formato (sin forma) ( List , ArrayList )
  • Matrices cuyos componentes son tipos Reifiable ( int [] , Number [] , List <?> [] , List [ )


¿Por qué hay información disponible sobre algunos tipos pero no sobre otros? El hecho es que debido al proceso de sobrescritura de tipos por parte del compilador, se puede perder información sobre algunos tipos. Si se pierde, este tipo ya no será reificable. Es decir, no está disponible en tiempo de ejecución. Si está disponible, respectivamente, reificable.

La decisión de no hacer que todos los tipos genéricos estén disponibles en tiempo de ejecución es una de las decisiones de diseño más importantes y conflictivas en el sistema de tipos Java. Esto se hace, en primer lugar, por compatibilidad con el código existente. Tuve que pagar por la compatibilidad de la migración: la accesibilidad total de un sistema de tipos genéricos en tiempo de ejecución no es posible.

Qué tipos no son reificables:
  • Tipo variable ( T )
  • Tipo parametrizado con el tipo de parámetro especificado ( List <Number> ArrayList <String> , List <List <String>> )
  • Un tipo parametrizado con el límite superior o inferior especificado ( Lista <? Amplía número>, Comparable <? Super String> ). Pero aquí hay una reserva: Lista <? extiende Object> - no reifiable, pero List <?> - reifiable


Y una tarea más. ¿Por qué en el siguiente ejemplo no se puede crear una excepción parametrizada?

 class MyException<T> extends Exception {  T t; } 

La respuesta
Cada expresión catch en try-catch verifica el tipo de la excepción recibida durante la ejecución del programa (que es equivalente a la instancia de), respectivamente, el tipo debe ser Reifiable. Por lo tanto, Throwable y sus subtipos no se pueden parametrizar.

 class MyException<T> extends Exception {// Generic class may not extend 'java.lang.Throwable'  T t; } 



Advertencias no verificadas


Compilar nuestra aplicación puede producir la llamada Unchecked Warning , una advertencia de que el compilador no pudo determinar correctamente el nivel de seguridad de uso de nuestros tipos. Esto no es un error, sino una advertencia, por lo que puede omitirlo. Pero es aconsejable arreglarlo todo para evitar problemas en el futuro.

Contaminación del montón


Como mencionamos anteriormente, la asignación de una referencia a un tipo Raw a una variable de un tipo parametrizado lleva a la advertencia "Asignación no verificada". Si lo ignoramos, es posible una situación llamada " Heap Pollution " (contaminación del montón). Aquí hay un ejemplo:
 static List<String> t() {  List l = new ArrayList<Number>();  l.add(1);  List<String> ls = l; // (1)  ls.add("");  return ls; } 

En la línea (1), el compilador advierte sobre "Asignación no verificada".

Necesitamos dar otro ejemplo de "contaminación de montón" - cuando usamos objetos parametrizados. El fragmento de código a continuación muestra claramente que no está permitido usar tipos parametrizados como argumentos para un método que usa Varargs . En este caso, el parámetro del método m es List<String>… , es decir de hecho, una matriz de elementos de tipo List<String> . Dada la regla de mostrar tipos durante el stringLists , el tipo stringLists convierte en una matriz de listas sin procesar ( List[] ), es decir la asignación se puede hacer Object[] array = stringLists; y luego escribe en una array un objeto que no sea la lista de cadenas (1), que ClassCastException en la cadena (2).

 static void m(List<String>... stringLists) {  Object[] array = stringLists;  List<Integer> tmpList = Arrays.asList(42);  array[0] = tmpList; // (1)  String s = stringLists[0].get(0); // (2) } 


Considere otro ejemplo:
 ArrayList<String> strings = new ArrayList<>(); ArrayList arrayList = new ArrayList(); arrayList = strings; // (1) Ok arrayList.add(1); // (2) unchecked call 

Java permite la asignación en la línea (1). Esto es necesario para la compatibilidad con versiones anteriores. Pero si intentamos ejecutar el método add en la línea (2), recibimos una advertencia de Unchecked call : el compilador nos advierte de un posible error. De hecho, estamos tratando de agregar un número entero a la lista de cadenas.

Reflexion


Aunque, durante la compilación, los tipos parametrizados se someten a un procedimiento de borrado de tipo, podemos obtener información utilizando Reflection.

  • Todos los reificables están disponibles a través del mecanismo de reflexión.
  • La información sobre el tipo de campos de clase, los parámetros del método y los valores devueltos por ellos está disponible a través de Reflection.

Reflection Reifiable , . , , , - , :
 java.lang.reflect.Method.getGenericReturnType() 

Generics java.lang.Class . :
 List<Integer> ints = new ArrayList<Integer>(); Class<? extends List> k = ints.getClass(); assert k == ArrayList.class; 


ints List<Integer> ArrayList< Integer> . ints.getClass() Class<ArrayLis> , List<Integer> List . Class<ArrayList> k Class<? extends List> , ? extends . ArrayList.class Class<ArrayList> .

Conclusión


, Reifiable. Reifiable : , , , Raw , reifiable.

Unchecked Warnings « » .

Reflection , Reifiable. Reflection , .

Type Inference


« ». () . :
 List<Integer> list = new ArrayList<Integer>(); 

- Java 7 ArrayList :
 List<Integer> list = new ArrayList<>(); 

ArrayListList<Integer> . type inference .

Java 8 JEP 101.
Type Inference. :
  • (reduction)
  • (incorporation)
  • (resolution)

: , , — .
, . JEP 101 .

, :
 class List<E> {  static <Z> List<Z> nil() { ... };  static <Z> List<Z> cons(Z head, List<Z> tail) { ... };  E head() { ... } } 

List.nil() :
 List<String> ls = List.nil(); 

, List.nil() String — JDK 7, .

, , , :
 List.cons(42, List.nil()); //error: expected List<Integer>, found List<Object> 

JDK 7 compile-time error. JDK 8 . JEP-101, — . JDK 8 — :
 List.cons(42, List.<Integer>nil()); 


JEP-101 , , :
 String s = List.nil().head(); //error: expected String, found Object 

, . , JDK , :
 String s = List.<String>nil().head(); 


JEP 101 StackOverflow . , , 7- , 8- – ? :
 class Test {  static void m(Object o) {      System.out.println("one");  }  static void m(String[] o) {      System.out.println("two");  }  static <T> T g() {      return null;  }  public static void main(String[] args) {      m(g());  } } 


- JDK1.8:
   public static void main(java.lang.String[]);   descriptor: ([Ljava/lang/String;)V   flags: ACC_PUBLIC, ACC_STATIC   Code:     stack=1, locals=1, args_size=1        0: invokestatic  #6   // Method g:()Ljava/lang/Object;        3: checkcast     #7   // class "[Ljava/lang/String;"        6: invokestatic  #8   // Method m:([Ljava/lang/String;)V        9: return     LineNumberTable:       line 15: 0       line 16: 9 


0 g:()Ljava/lang/Object; java.lang.Object . , 3 («») , java.lang.String , 6 m:([Ljava/lang/String;) , «two».

- JDK1.7 – Java 7:
   public static void main(java.lang.String[]);   flags: ACC_PUBLIC, ACC_STATIC   Code:     stack=1, locals=1, args_size=1        0: invokestatic  #6   // Method g:()Ljava/lang/Object;        3: invokestatic  #7   // Method m:(Ljava/lang/Object;)V        6: return            LineNumberTable:       line 15: 0       line 16: 6 


, checkcast , Java 8, m:(Ljava/lang/Object;) , «one». Checkcast – , Java 8.

, Oracle JDK1.7 JDK 1.8 , Java, , .

, Java 8 , Java 7, :

 public static void main(String[] args) { m((Object)g()); } 


Conclusión


Java Generics . , :


  • Bloch, Joshua. Effective Java. Third Edition. Addison-Wesley. ISBN-13: 978-0-13-468599-1

, Java Generics.

Source: https://habr.com/ru/post/es416413/


All Articles