Red de conocimiento informático - Consumibles informáticos - Haga una pregunta rápida sobre JAVA

Haga una pregunta rápida sobre JAVA

Este tutorial le presenta el conocimiento general de Java. Es posible que esté familiarizado con los genéricos en otros lenguajes, especialmente las plantillas C. Si es así, verá rápidamente las similitudes y diferencias importantes entre los dos. Si no estás familiarizado con estructuras gramaticales similares, es mejor empezar desde cero sin olvidar los errores.

Los genéricos permiten abstraer tipos. Los ejemplos más comunes son los tipos de contenedores, que son cualquiera de los árboles de clases de la colección.

El siguiente es un uso típico:

list Myint list = new linked list(); // 1

myIntList.add(new integer(0)) ; // 2

Entero x =(Entero)Mi iterador(). next(); // 3

La conversión de tipos en la línea 3 es un poco molesta. Normalmente, el programador sabe qué tipo de datos van en una lista particular. Sin embargo, esta conversión de tipo es necesaria.

(Imprescindible). El compilador solo puede garantizar que un iterador devuelva un tipo de objeto. Para garantizar la seguridad de los tipos de asignación a variables enteras, es necesaria la conversión de tipos.

Por supuesto, este tipo de conversión de tipos no solo es confuso, sino que también puede generar errores de tiempo de ejecución porque el programador puede cometer errores. ¿Cómo comunica claramente un programador su intención de restringir el contenido de una lista a un tipo de datos específico? Ésta es la idea central detrás de los genéricos. Esta es una versión genérica del fragmento de programa anterior:

list Myint list = new linked list(); // 1

myIntList.add(new integer(0)); / 2

Entero x = myIntList.iterator(). next(); // 3

Preste atención a la declaración de tipo de la variable myIntList. Especifica que esta no es una lista arbitraria, sino una lista de números enteros, escrita como: Lista

Decimos que Lista es una interfaz general que acepta parámetros de tipo. En este caso, el parámetro de tipo es un número entero. Al crear este objeto de lista, también especificamos un parámetro de tipo.

Tenga en cuenta también que no hay conversión de tipo en la línea 3. Ahora bien, podría pensar que hemos eliminado con éxito la confusión en el programa. Reemplazamos la conversión de tipo en la línea 3 con el parámetro de tipo en la línea 1. Sin embargo, aquí hay una gran diferencia. El compilador ahora puede comprobar la corrección de los programas en tiempo de compilación. Cuando decimos que myIntList se declara como un tipo de lista, nos dice que siempre y donde sea que se use la variable myIntList, el compilador garantiza el tipo correcto de los elementos que contiene. Por el contrario, la conversión de tipos significa que el programador cree que el tipo debería estar en ese punto del código. El resultado práctico es que puede aumentar la legibilidad y la solidez, especialmente en programas grandes.

2. Definición simple de genéricos

El siguiente es un extracto de las definiciones de la interfaz de lista y de la interfaz de iterador en el paquete java.util:

Lista de interfaces públicas {

void add(E x);

Iterador iterador();

}

Iterador de interfaz pública {

e next();

El valor booleano tiene next();

}

Estos deberían resultarle familiares, excepto la parte entre corchetes angulares. es la declaración de parámetros de tipo formal en listas de interfaz e iteradores. Los parámetros de tipo se pueden usar a lo largo de la declaración de una clase, y otros tipos comunes se pueden usar casi en cualquier lugar (pero consulte la Sección 7 para conocer algunas restricciones importantes).

En la sección de introducción, vimos llamadas a listas declaradas de tipos genéricos, como List.

En esta llamada (a menudo llamada tipo parametrizado), todas las apariciones de un parámetro de tipo formal (aquí E).

Reemplazar con el parámetro de tipo real (Entero en este caso). Como puedes imaginar, List representa una versión en la que e se reemplaza completamente por Integer:

Lista integrada de interfaz pública {

void add(integer x)

Iterador iterador();

}

Esta intuición puede ser útil, pero también puede dar lugar a malentendidos. Esto es útil porque las declaraciones de lista tienen este tipo de alternativas. Esto puede dar lugar a malentendidos porque las declaraciones genéricas nunca se reemplazan de esta manera. No hay múltiples copias del código, ni en el código fuente ni en el código binario, ni en el disco ni en la memoria. Si eres programador de C, entenderás que esto es muy diferente a las plantillas de C.

La declaración de un tipo genérico se compila solo una vez y se obtiene un archivo de clase, como una declaración de clase o interfaz normal. Los parámetros de tipo son como parámetros ordinarios en un método o constructor. Así como un método tiene parámetros de valor formales que describen el tipo de argumento sobre el que opera, una declaración genérica también tiene parámetros de tipo formal. Cuando se llama a un método, los parámetros reales reemplazan a los parámetros formales y se ejecuta el cuerpo del método. Cuando se llama a una declaración genérica, los parámetros de tipo reales reemplazan a los parámetros de tipo formal.

Una convención de nomenclatura: recomendamos utilizar nombres concisos (caracteres únicos si es posible) para nombres de parámetros de tipo formal. Es mejor evitar el uso de letras minúsculas para que se puedan distinguir fácilmente de otros parámetros comunes. Muchos tipos de contenedores utilizan e como tipo de elemento, como en el ejemplo anterior. Hay algunas otras convenciones de nomenclatura en los ejemplos siguientes.

3. Genéricos y herencia de subclases

Pongamos a prueba nuestra comprensión de los genéricos. ¿Es legal el siguiente fragmento de código?

list ls = new ArrayList();//1

List lo = ls//2

La línea 1 es ciertamente legal, pero el problema es complicado Está en la línea 2. Esto plantea la pregunta: ¿una lista de cadenas es una lista de objetos? El instinto de la mayoría de la gente es responder: “¡Por ​​supuesto!”. Bien, mira las siguientes líneas:

lo . add(new Object()); // 3

string s = ls . a Objeto asignado a cadena.

Aquí usamos lo para señalar a ls. Accedemos a ls a través de la lista de cadenas l0. Podemos insertar cualquier objeto en él. El resultado es que lo que se guarda en ls ya no es un String. Cuando intentamos extraer elementos de él, obtenemos resultados inesperados. El compilador de Java ciertamente evitará que esto suceda. La línea 2 provoca un error de compilación.

En resumen, si Foo es un subtipo (subclase o subinterfaz) de Bar y G es una declaración genérica, ¡entonces G es un subtipo de G! !

Esta es probablemente la parte más difícil de aprender genéricos porque va en contra de la intuición. El problema de esta intuición es que supone que el conjunto no cambia. Nuestra intuición es:

Estas son cosas que no se pueden cambiar. Por ejemplo, parecería razonable que el Departamento de Transporte (DMV) proporcionara una lista de conductores a la Oficina del Censo. Pensamos en las listas como listas, asumiendo que Conductor es un subtipo de Persona. De hecho, enviamos una copia de la información de registro del conductor. Sin embargo,

Es posible que la Oficina del Censo haya agregado otros a la lista de conductores, corrompiendo los registros del DOT. Para manejar esta situación, es útil considerar algunos tipos genéricos más flexibles.

Las reglas que hemos visto hasta ahora son relativamente restrictivas.

4. Comodines

Considera escribir una rutina para imprimir todos los elementos de una colección.

Esto es lo que podrías haber escrito en un lenguaje antiguo:

void printCollection(collection c) {

Iterator I = c

for (. int k = 0; k ltc . tamaño(); k ) {

sistema . siguiente());

}

}

Aquí hay un intento ingenuo de usar genéricos (usando una nueva sintaxis de bucle):

void printCollection(colección c) {

For (object e: c ) {

system . out . println(e);

}

}

El problema es que la nueva versión es mucho menos útil que la anterior. versión antigua. La versión anterior del código podía usar cualquier tipo de colección como parámetro, mientras que la nueva versión solo puede usar colecciones. Como acabamos de explicar, no es la clase principal de todos los tipos de colecciones. Entonces, ¿cuál es la clase principal de varias colecciones? Dice:

Conjunto (pronunciado "conjunto desconocido")

Es decir, el tipo de elemento de un conjunto puede coincidir con cualquier tipo. Aparentemente, se llama comodín. Podemos escribir:

void printCollection(colección c) {

for (objeto e: c) {

system out . /p>

}

}

Ahora podemos llamarlo con cualquier tipo de colección. Tenga en cuenta que todavía podemos leer el elemento en C, su tipo es Objeto. Esto siempre es seguro,

porque no importa cuál sea el verdadero tipo de colección, ésta contiene objetos. Pero agregarle elementos no es seguro:

collection c = new ArrayList();

c add(new Object());

Debido a que se desconoce el tipo de elemento de C, no se le pueden agregar objetos. El método add tiene un parámetro de tipo e como tipo de elemento de la colección. Cualquier argumento que pasemos para agregar debe ser una subclase de tipo desconocido. Como no sé de qué tipo es, no se puede pasar nada. La única excepción es nula, que es miembro de todos los tipos. Por otro lado, podemos llamar al método get() y usar su valor de retorno. El valor de retorno es de un tipo desconocido, pero sabemos que siempre es un objeto, por lo que es seguro asignar el valor de retorno de get a un objeto de tipo Objeto o colocarlo donde quieras.

4.1. Comodines acotados

Considere un programa de dibujo simple que se puede utilizar para dibujar varias formas, como rectángulos y círculos. Para representar estas formas en el programa, se puede definir la siguiente estructura de herencia de clases:

Clase abstracta pública Forma{

Pintura vacía abstracta pública (lienzo c);

}

Forma extendida del círculo de clase pública {

privado int x, y, radio

Dibujo público vacío (lienzo c) { //. ..}

}

Clase pública Rectángulo extiende forma {

privado int x, y, ancho, alto

Dibujo público vacío (canvas c) { //...}

}

Estas clases pueden dibujar en el lienzo:

Clase pública Canvas {

publicvoid draw (shape s) {

s.draw(this);

}

}

Todos los gráficos suelen tener muchas formas.

Suponiendo que están representadas por una lista, es conveniente dibujar todas las formas en el lienzo con un método:

public void drawAll(listshapes){

For (shapes s: shape) {

s.draw(this);

}

}

Ahora, las reglas de tipo hacen que drawAll() solo se llame con una lista de formas. Por ejemplo, no se le puede llamar en una lista. Desafortunadamente, dado que este método solo lee formas de esta lista, también debería poder llamar a esta lista. Lo que realmente queremos es que este método acepte cualquier forma: Red de Desarrollo de Software www.mscto.com.

Public void drawAll(listshape){ //..}

Aquí hay una pequeña pero importante diferencia: reemplazamos el tipo Lista con Lista. Ahora se acepta drawAll().

Una lista de las subclases de Shape, para que podamos llamar a esta lista.

Una lista es un ejemplo de carácter comodín restrictivo. ¿Aquí? Representa un tipo desconocido, como el comodín que vimos antes.

Sin embargo, aquí sabemos que este tipo desconocido es en realidad una subclase de Shape (ya sea Shape en sí o una subclase de Shape). Decimos que Shape es el límite superior de este comodín. Como siempre, la flexibilidad de utilizar comodines tiene un precio. La desventaja es que escribir formas ahora es ilegal. Por ejemplo, el siguiente código no está permitido:

public void addRectangle(list shape){

shapes.add(0, new Triangle()); // ¡Error en el tiempo de compilación!

}

Deberías poder indicar por qué el código anterior no está permitido. ¿Porque el segundo tipo de parámetro de formas.add es? extiende forma - Subclase de forma desconocida. Así que no sabemos qué es este tipo, y no sabemos si es una clase padre de Rectángulo; puede ser o no una clase padre, así que pasamos uno aquí;

Los rectángulos no son seguros. Los comodines restrictivos son exactamente lo que necesitamos para resolver el ejemplo del DMV que envía una lista a la Oficina del Censo. Nuestro ejemplo supone que los datos están representados mediante un mapeo de cadenas a personas (representadas por personas o sus subclases, como Driver). Map es un ejemplo de un tipo genérico que tiene dos parámetros de tipo que representan las claves y valores del mapa. Nuevamente, tenga en cuenta la convención de nomenclatura para los parámetros de tipo formal: k representa la clave yv representa el valor.

Censo de clase pública {

AddRegistry vacío estático público (registro de mapa){...}

}

Asignar factor de todos los conductores =...;

census . agregar registro (todos los controladores);

5. Método general

Considere escribir un método que tome una matriz de objetos y Una colección se utiliza como parámetro para completar la función de colocar todos los objetos de la matriz en la colección.

Este es un primer intento:

static void fromArrayToCollection(Object[] a, collection c) {

For (object o: a) {< / p>

c . add(o); // error en tiempo de compilación

}

}

Ahora deberías poder aprender a evitarlo. Error para principiantes al intentar utilizar Colección como tipo de parámetro de colección. Quizás te hayas dado cuenta de que usar colecciones

tampoco funciona. No puedes poner objetos en una colección de tipo desconocido. La solución a este problema es utilizar métodos genéricos. Al igual que las declaraciones de tipo, las declaraciones de método se pueden generalizar, es decir, utilizando uno o más parámetros de tipo.

vacío estático deArrayToCollection(T[] a, colección c){

for (time: a) {

c . Correcto

}

}

Podemos llamar a este método usando cualquier colección siempre que su tipo de elemento sea la clase principal del tipo de elemento de la matriz.

Objeto[] oa = nuevo objeto [100];

colección co = nueva ArrayList();

fromArrayToCollection(oa, co); Representa el objeto

String[] sa = new string [100];

colección cs = new ArrayList()

fromArrayToCollection(sa, cs); // T inferido como cadena

fromArrayToCollection(sa, co); // T inferido como objeto

Integer[] ia = new integer[100];

Float[] fa = new Float[100];

Number[] na =new number[100];

colección cn = new ArrayList();

fromArrayToCollection(ia,cn); // T se infiere como un número

fromArrayToCollection(fa, cn); // T se infiere como un número

fromArrayToCollection(na, cn) ; // T se infiere como un número

fromArrayToCollection(na, co); // T se infiere como un objeto

fromArrayToCollection(na, cs); error de tiempo

Tenga en cuenta que no pasamos parámetros de tipo reales al método genérico. El compilador infiere el valor del parámetro de tipo en función del parámetro real.

Por lo general, inferirá los parámetros de tipo más explícitos que harán que el tipo de llamada sea correcto. (Texto original: generalmente inferirá los argumentos de tipo más específicos que harán que el tipo de llamada sea correcto).

Ahora surge una pregunta: ¿cuándo deberíamos usar métodos genéricos y cuándo deberíamos usar tipos comodín? Para comprender la respuesta, primero veamos algunos métodos en la biblioteca de la colección.

Colección de interfaz pública {

boolean containsAll(colección c);

Boolean addAll(colección c);

}

}

p>

También podemos usar métodos genéricos:

Colección de interfaz pública {

boolean containsAll(collection c);

Boolean addAll (set c);

//Oye, ¡las variables de tipo también pueden tener límites!

}

Sin embargo, en containsAll y addAll, el parámetro de tipo t solo se usa una vez. El tipo del valor de retorno no depende de los parámetros de tipo ni de los demás parámetros del método (aquí solo hay un parámetro simple). Esto nos dice que el parámetro de tipo se usa como polimorfismo, cuyo único propósito es permitir que se usen múltiples tipos de argumentos en diferentes sitios de llamada. Si este es el caso, se deben utilizar comodines. Los comodines están destinados a admitir subclases flexibles, que es lo que queremos enfatizar aquí.

Las funciones genéricas permiten el uso de parámetros de tipo para expresar dependencias entre uno o más parámetros de un método, o entre parámetros y sus valores de retorno.

Sin esta dependencia, no se deben utilizar métodos genéricos.

(Texto original: Los métodos genéricos permiten el uso de parámetros de tipo para expresar dependencias en uno o más parámetros de un método y/o su tipo de retorno. Si no existe tal dependencia, entonces los métodos genéricos no deben usarse used )

También puedes utilizar métodos genéricos y comodines al mismo tiempo. Aquí está el método Collections.copy():

classCollections {

copia vacía estática pública (List dest, List src){...}

}

Tenga en cuenta la dependencia de estos dos tipos de parámetros. Cualquier objeto copiado de la lista de origen debe poder tener su tipo especificado como elemento de la lista de destino (destino) -tipo T. Por lo tanto, el tipo de elemento del tipo fuente puede ser cualquier subtipo de t, no nos importa el tipo específico. La firma del método de copia usa un parámetro de tipo para indicar la dependencia del tipo, pero usa un comodín como tipo de elemento para el segundo parámetro. También podríamos escribir la firma de esta función de otra manera, sin usar comodines en absoluto:

Colección de clases {

Copia vacía estática pública (List dest, List src){.. }

}

Esto también es posible, pero el primer parámetro de tipo se usa tanto para el tipo de dst como para el límite superior del parámetro de tipo S del segundo parámetro, S en sí. Úselo solo una vez.

En el tipo src, nada más depende de ello. Esto significa que podemos usar comodines en lugar de s, lo cual es más limpio que usar parámetros de tipo explícito.

Es mejor utilizar comodines si es posible. Los comodines también tienen la ventaja de que se pueden utilizar fuera de las firmas de métodos, como tipos de campos, variables locales y matrices. He aquí un ejemplo.

Volviendo a nuestro problema de dibujo, digamos que queremos guardar un historial de solicitudes de dibujo. Podemos guardar el historial en las variables miembro estáticas de la clase Shape.

Al llamar a drawAll(), guarde los parámetros entrantes en el historial:

Lista estática lt list gthistory = new ArrayList lt list gt()

public; void drawAll(lista de formas){

history.addLast(forma);

for (forma s: forma){

s.draw(this);

}

}

Finalmente, hablemos de la convención de nomenclatura de los parámetros de tipo. Usamos t para representar tipos, y no hay ningún tipo más específico para distinguir en ningún momento. Esto es común con los métodos genéricos. Si hay varios tipos de parámetros, podemos usar la letra adyacente a t en el alfabeto, como s. Si una función genérica aparece en una clase genérica, es mejor evitar usarla en los parámetros de tipo del método y en el. escriba los parámetros de la clase. Mismo nombre para evitar confusiones. Lo mismo ocurre con las clases internas.