1. ¿Qué es exactamente un genérico?
Antes de discutir la inferencia de tipo, debemos revisar lo que es genérico. Los genéricos son nuevas características de Java SE 1.5. La esencia de genéricos es un tipo parametrizado, es decir, el tipo de datos operado se especifica como un parámetro. En términos de Layman, serían "variables de tipo". Este tipo de variable se puede utilizar en la creación de clases, interfaces y métodos. La forma más fácil de comprender Java Generics es considerarlo como una sintaxis conveniente que puede ahorrarle algunas operaciones en casting de tipo Java:
List <Apple> box = new ArrayList <Apple> (); Box.add (nuevo Apple ()); Apple Apple = Box.get (0);
El código anterior en sí mismo se ha expresado claramente: el cuadro es una List con objetos de Apple. El método get devuelve una instancia de objeto Apple, y este proceso no requiere una conversión de tipo. No hay genéricos, el código anterior debe escribirse así:
Apple Apple = (Apple) Box.get (0);
Por supuesto, los genéricos no son tan simples como lo que describí aquí, pero este no es el protagonista de nuestros días. Los estudiantes que no entienden muy bien los genéricos necesitan compensar las lecciones ~ Por supuesto, los mejores materiales de referencia siguen siendo los documentos oficiales.
2. Problemas causados por genéricos (antes de Java 7)
La mayor ventaja de los genéricos es que proporcionan la seguridad de tipo del programa y pueden ser compatibles con versiones anteriores. Sin embargo, también hay cosas que hacen que los desarrolladores sean descontentos. El tipo de genéricos debe escribirse cada vez que definan. Esta especificación de visualización no solo se siente un poco detallada, sino que, lo más importante, muchos programadores no están familiarizados con los genéricos, por lo que no pueden dar parámetros de tipo correcto en muchos casos. Ahora, el compilador infiere automáticamente los tipos de genéricos de parámetros, lo que puede reducir esta situación y mejorar la legibilidad del código.
3. Mejoras en la derivación de tipo de genéricos en Java 7
El uso de tipos genéricos en versiones anteriores de Java 7 requiere agregar tipos genéricos en ambos lados al declarar y asignar valores. Por ejemplo:
Map <string, integer> map = new Hashmap <String, Integer> ();
Muchas personas deben haber sido lo mismo que yo al principio, y estaban desconcertados por esto: ¿no declaré el tipo de parámetro en la declaración variable? ¿Por qué todavía necesito escribirlo cuando se inicializa el objeto? Esto también es lo que hace que los genéricos se quejen cuando aparecieron por primera vez. Sin embargo, es gratificante que, mientras Java está mejorando, los diseñadores también están mejorando constantemente el compilador de Java para que sea más inteligente y humanizado. Aquí está nuestro protagonista hoy: Tipo Pushdown ... bueno ... no es una derivación de tipo, es decir, inferencia de tipo. Cuando aparece este tipo, cuando escribe el código anterior, puede omitir felizmente los tipos de parámetros cuando se instancia la instanciación de objetos, y se vuelve así:
Map <string, integer> map = new HashMap <> ();
En esta declaración, el compilador inferirá automáticamente el tipo genérico al instancias de HashMap en función del tipo genérico cuando la declaración variable. Nuevamente, asegúrese de prestar atención al "<>" detrás new HashMap . Solo agregar esta "<>" significa que es una inferencia de tipo automático, de lo contrario es un HashMap no genérico, y se dará un mensaje de advertencia al compilar el código fuente utilizando el compilador. Este par de soportes de ángulo "<>" se llama "diamante" en el documento oficial.
Sin embargo, la derivación del tipo en este momento no está completa (incluso un producto semi-acabado), porque la inferencia del tipo Al crear instancias genéricas en Java SE 7 es limitada: solo si el tipo parametrizado del constructor se declara significativamente en el contexto, se puede usar la inferencia de tipo, de lo contrario no funcionará. Por ejemplo: el siguiente ejemplo no se puede compilar correctamente en Java 7 (pero ahora se puede compilar en Java 8, porque el tipo genérico se infiere automáticamente en función de los parámetros del método):
List <String> list = new ArrayList <> (); list.add ("a"); // ya que addall espera obtener parámetros de colección de tipos <? extiende la cadena>, la siguiente declaración no puede pasar list.addall (new ArrayList <> ());4. Reevolución en Java 8
En la última documentación oficial de Java, podemos ver la definición de derivación de tipo:
La inferencia de tipos es la capacidad de un compilador de Java para ver cada invocación de método y declaración correspondiente para determinar el argumento de tipo (o argumentos) que hace que la invocación sea aplicable. El algoritmo de inferencia determina los tipos de argumentos y, si está disponible, el tipo que se asigna o devuelve el resultado. Finalmente, el algoritmo de inferencia intenta encontrar el tipo más específico que funcione con todos los argumentos.
En resumen, la derivación del tipo se refiere a la capacidad del compilador para determinar los tipos de parámetros requeridos en función del método que llama y la declaración correspondiente. Y también se da un ejemplo en la documentación oficial para explicar:
static <t> t pick (t a1, t a2) {return a2; } Serializable s = pick ("d", new ArrayList <String> ()); Aquí, el compilador puede deducir que el tipo del segundo parámetro pasado en pick es Serializable .
En versiones anteriores de Java, si el ejemplo anterior puede ser compilado, debe escribir esto:
Serializable s = this. <erializable> pick ("d", new ArrayList <String> ());La razón detallada para escribir esto se puede ver en el capítulo genérico del pensamiento de programación Java de Bruce Eckel (cuarta edición). Por supuesto, este libro se basa en Java 6, y esta versión no tiene un concepto de derivación de tipo. Al ver esto, muchas personas pueden ver claramente el poder de la derivación del tipo en la última versión. Ya no se limita al proceso de declaración e instanciación de clases genéricas, sino que se extiende a métodos con parámetros genéricos.
4.1 Tipo de inferencia y métodos genéricos
Con respecto al tipo de derivación y métodos genéricos en la nueva versión, el documento también ofrece un ejemplo un poco más complejo. Lo publiqué aquí. El principio es el mismo que el ejemplo Serializable anterior, por lo que no entraré en detalles. Si desea consolidarlo, puede echar un vistazo:
public class boxDemo {public static <u> void addbox (u u, java.util.list <box <u>> cajas) {box <u> box = new Box <> (); box.set (u); boxes.add (caja); } public static <u> void outputboxes (java.util.list <box <u>> cajas) {int contador = 0; for (box <u> box: boxes) {u boxContents = box.get (); System.out.println ("Box #" + contador + "contiene [" + boxContents.ToString () + "]"); contador ++; }} public static void main (string [] args) {java.util.arrayList <box <Integer>> listOfInTegerboxes = new java.util.arrayList <> (); BoxDemo. <integer> addbox (integer.valueOf (10), listofintegerBoxes); BoxDemo.addbox (integer.valueOf (20), listOfIntegerBoxes); BoxDemo.addbox (integer.valueOf (30), listOfIntegerBoxes); BoxDemo.outputboxes (listOfInTegerboxes); }}La salida del código anterior es:
El cuadro #0 contiene [10] el cuadro #1 contiene [20] el cuadro #2 contiene [30]
Permítanme mencionar que el enfoque del método genérico addBox es el tipo de descripción que ya no necesita mostrar en la llamada de método en la nueva versión Java, como esta:
BoxDemo. <integer> addbox (integer.valueOf (10), listofintegerBoxes);
El compilador puede inferir automáticamente que el tipo de parámetro es Integer de los parámetros pasados a addBox .
4.2 Inferencia de tipo y constructores genéricos de clases genéricas y no genéricas
Bueno ... esta puede ser una mejor oración en inglés: inferencia de tipo y constructores genéricos de clases genéricas y no genéricas
De hecho, los constructores genéricos no son patentes para clases genéricas. Las clases no genéricas también pueden tener sus propios constructores genéricos. Echa un vistazo a este ejemplo:
clase myclass <x> {<t> myclass (t t) {// ...}}Si se realiza la siguiente instancia a la clase MyClass:
Nueva myclass <integer> ("") Ok, aquí mostramos que el parámetro Tipo X de MyClass es Integer , y para el constructor, el compilador deduce que el parámetro formal t es String basada en el objeto String entrante (""). Esto se ha implementado en la versión Java7. ¿Qué mejoras se han realizado en Java8? Después de Java8, podemos escribir esta instanciación de una clase genérica con un constructor genérico como este:
MyClass <Integer> myObject = new MyClass <> (""); Sí, sigue siendo el par de soportes de ángulo (<>), que se llama diamante, para que nuestro compilador pueda deducir automáticamente que los parámetros formales x es Integer y t es String . Esto en realidad es muy similar a nuestro ejemplo inicial de Map<String,String> , excepto que hay una generación del constructor.
Cabe señalar que la derivación del tipo solo se puede derivar en función del tipo de parámetro de la llamada, el tipo de destino (esto se discutirá pronto) y el tipo de retorno (si hay una devolución), y no se puede derivar en función de algunos requisitos después del programa.
4.3 Tipo objetivo
Como se mencionó anteriormente, el compilador puede realizar una derivación de tipo basada en el tipo de destino. El tipo objetivo de una expresión se refiere al tipo de datos correcto que el compilador necesita en función de dónde aparece la expresión. Por ejemplo, este ejemplo:
static <t> list <t> vacylist (); list <string> listOne = collections.emptylist ();
Aquí, List <String> es el tipo de destino, porque lo que se necesita aquí es List<String> , y Collections.emptyList() Devuelve List<T> , por lo que el compilador aquí infiere que t debe ser String . Esto está bien en Java 7 y 8. Sin embargo, en Java 7, no se puede compilar normalmente en la siguiente situación:
Void ProcessStringList (List <String> StringList) {// Process StringList} ProcessStringList (Collections.EmptyList ());En este momento, Java7 dará este mensaje de error:
// List <S Object> no se puede convertir en List <String>
Razón: Collections.emptyList() Devuelve List<T> , y T aquí requiere un tipo específico, pero debido a que no se puede inferir de la declaración del método de que lo que se requiere es String , el compilador le da a t un valor Object . Obviamente, List<Object> no se puede convertir en List<String>. Entonces, en la versión Java7, debe llamar a este método como este:
ProcessStringList (colecciones. <String> vacilist ());
Sin embargo, en Java 8, debido a la introducción del concepto de tipo objetivo, es obvio que lo que el compilador necesita es List<String> (es decir, el tipo de destino aquí), por lo que el compilador infiere que la T en la List<T> debe ser String , por lo que la descripción de processStringList(Collections.emptyList()); está bien.
El uso de los tipos de objetivos es más obvio en las expresiones lambda.
Resumir
Ok, lo anterior son algunas ideas personales sobre la derivación de los tipos en Java. En resumen, la derivación de tipo cada vez más perfecta es completar algún trabajo de conversión de tipo que parece ser natural, pero todo este trabajo se deja al compilador para la derivación automática en lugar de permitir que los desarrolladores lo muestren. Espero que el contenido de este artículo sea útil para todos en el aprendizaje de Java. Si tiene alguna pregunta, puede dejar un mensaje para comunicarse.