new icon example borderfactory java generics raw-types

java - icon - ¿Qué es un tipo crudo y por qué no deberíamos usarlo?



my icon java (14)

¿Qué son los tipos sin procesar en Java y por qué a menudo escucho que no deben usarse en el nuevo código?

Los tipos crudos son historia antigua del lenguaje Java. Al principio había Collections y sostenían Objects nada más y nada menos. Cada operación en Collections requiere conversiones de Object al tipo deseado.

List aList = new ArrayList(); String s = "Hello World!"; aList.add(s); String c = (String)aList.get(0);

Si bien esto funcionó la mayor parte del tiempo, los errores ocurrieron.

List aNumberList = new ArrayList(); String one = "1";//Number one aNumberList.add(one); Integer iOne = (Integer)aNumberList.get(0);//Insert ClassCastException here

Las antiguas colecciones sin tipo no podían imponer la seguridad de tipo, por lo que el programador tenía que recordar lo que almacenaba en una colección.
Los genéricos se inventaron para sortear esta limitación, el desarrollador declararía el tipo almacenado una vez y el compilador lo haría en su lugar.

List<String> aNumberList = new ArrayList<String>(); aNumberList.add("one"); Integer iOne = aNumberList.get(0);//Compile time error String sOne = aNumberList.get(0);//works fine

Para comparacion:

// Old style collections now known as raw types List aList = new ArrayList(); //Could contain anything // New style collections with Generics List<String> aList = new ArrayList<String>(); //Contains only Strings

Más compleja la interfaz Comparable:

//raw, not type save can compare with Other classes class MyCompareAble implements CompareAble { int id; public int compareTo(Object other) {return this.id - ((MyCompareAble)other).id;} } //Generic class MyCompareAble implements CompareAble<MyCompareAble> { int id; public int compareTo(MyCompareAble other) {return this.id - other.id;} }

Tenga en cuenta que es imposible implementar la interfaz compareTo(MyCompareAble) con compareTo(MyCompareAble) con tipos sin compareTo(MyCompareAble) . Por qué no deberías usarlos:

  • Cualquier Object almacenado en una Collection debe ser lanzado antes de que pueda ser utilizado
  • El uso de genéricos permite la compilación de tiempo
  • Usar tipos brutos es lo mismo que almacenar cada valor como Object

Lo que hace el compilador: los genéricos son compatibles con versiones anteriores, usan las mismas clases java que los tipos en bruto. La magia ocurre sobre todo en tiempo de compilación.

List<String> someStrings = new ArrayList<String>(); someStrings.add("one"); String one = someStrings.get(0);

Se compilará como:

List someStrings = new ArrayList(); someStrings.add("one"); String one = (String)someStrings.get(0);

Este es el mismo código que escribiría si usara los tipos en bruto directamente. Pensé que no estoy seguro de lo que sucede con la interfaz CompareAble , creo que crea dos funciones compareTo , una que toma MyCompareAble y la otra que toma un Object y lo pasa a la primera después de lanzarlo.

¿Cuáles son las alternativas a los tipos crudos: usar generics

Preguntas:

  • ¿Qué son los tipos sin procesar en Java y por qué a menudo escucho que no deben usarse en el nuevo código?
  • ¿Cuál es la alternativa si no podemos usar tipos brutos y cómo es mejor?

¿Qué es un tipo crudo?

La especificación del lenguaje Java define un tipo en bruto de la siguiente manera:

Tipos crudos JLS 4.8

Un tipo en bruto se define como uno de:

  • El tipo de referencia que se forma al tomar el nombre de una declaración de tipo genérico sin una lista de argumentos de tipo acompañante.

  • Un tipo de matriz cuyo tipo de elemento es un tipo sin formato.

  • Un tipo de miembro no static de un tipo sin procesar R que no se hereda de una superclase o superinterfaz de R

Aquí hay un ejemplo para ilustrar:

public class MyType<E> { class Inner { } static class Nested { } public static void main(String[] args) { MyType mt; // warning: MyType is a raw type MyType.Inner inn; // warning: MyType.Inner is a raw type MyType.Nested nest; // no warning: not parameterized type MyType<Object> mt1; // no warning: type parameter given MyType<?> mt2; // no warning: type parameter given (wildcard OK!) } }

Aquí, MyType<E> es un tipo parametrizado ( JLS 4.5 ). Es común referirse coloquialmente a este tipo como simplemente MyType para abreviar, pero técnicamente el nombre es MyType<E> .

mt tiene un tipo sin formato (y genera una advertencia de compilación) por el primer punto en la definición anterior; inn también tiene un tipo en bruto por el tercer punto de bala.

MyType.Nested no es un tipo parametrizado, aunque es un tipo de miembro de un tipo parametrizado MyType<E> , porque es static .

mt1 y mt2 están declarados con parámetros de tipo reales, por lo que no son tipos sin procesar.

¿Qué tienen de especial los tipos crudos?

Esencialmente, los tipos crudos se comportan como antes de que se introdujeran los genéricos. Es decir, lo siguiente es completamente legal en tiempo de compilación.

List names = new ArrayList(); // warning: raw type! names.add("John"); names.add("Mary"); names.add(Boolean.FALSE); // not a compilation error!

El código anterior funciona bien, pero suponga que también tiene lo siguiente:

for (Object o : names) { String name = (String) o; System.out.println(name); } // throws ClassCastException! // java.lang.Boolean cannot be cast to java.lang.String

Ahora nos encontramos con problemas en tiempo de ejecución, porque los names contienen algo que no es una instanceof String .

Es de suponer que, si desea que los names contengan solo String , quizás aún pueda usar un tipo en bruto y verificar manualmente cada add , y luego convertir manualmente en String cada elemento a partir de los names . Aún mejor , aunque NO es usar un tipo sin formato y dejar que el compilador haga todo el trabajo por usted , aprovechando el poder de los genéricos de Java.

List<String> names = new ArrayList<String>(); names.add("John"); names.add("Mary"); names.add(Boolean.FALSE); // compilation error!

Por supuesto, si QUIERES que los names permitan un valor Boolean , entonces puedes declararlo como List<Object> names , y el código anterior se compilaría.

Ver también

¿En qué se diferencia un tipo sin formato de usar <Object> como parámetros de tipo?

La siguiente es una cita de Effective Java 2nd Edition, Item 23: No utilice tipos brutos en el nuevo código :

¿Cuál es exactamente la diferencia entre la List tipos sin procesar y la List<Object> tipos parametrizados List<Object> ? En términos generales, el primero ha excluido la verificación de tipos genéricos, mientras que el último le dijo explícitamente al compilador que es capaz de contener objetos de cualquier tipo. Si bien puede pasar una List<String> a un parámetro de tipo List , no puede pasarla a un parámetro de tipo List<Object> . Existen reglas de subtipo para los genéricos, y List<String> es un subtipo del tipo de List bruto, pero no del tipo parametrizado List<Object> . Como consecuencia, pierde la seguridad de tipos si usa un tipo sin formato como List , pero no si usa un tipo parametrizado como List<Object> .

Para ilustrar el punto, considere el siguiente método que toma un List<Object> y agrega un new Object() .

void appendNewObject(List<Object> list) { list.add(new Object()); }

Los genéricos en Java son invariantes. Una List<String> no es una List<Object> , por lo que lo siguiente generaría una advertencia del compilador:

List<String> names = new ArrayList<String>(); appendNewObject(names); // compilation error!

Si hubiera declarado appendNewObject para tomar un tipo de List bruto como parámetro, entonces esto se compilaría y, por lo tanto, perdería el tipo de seguridad que obtiene de los genéricos.

Ver también

¿En qué se diferencia un tipo sin formato de usar <?> Como parámetro de tipo?

List<Object> , List<String> , etc. son todos List<?> , Por lo que puede ser tentador decir que solo son List . Sin embargo, hay una gran diferencia: como la List<E> solo define add(E) , no puede agregar cualquier objeto arbitrario a la List<?> . Por otro lado, dado que la List tipos sin List no tiene seguridad de tipos, puede add casi cualquier cosa a una List .

Considere la siguiente variación del fragmento anterior:

static void appendNewObject(List<?> list) { list.add(new Object()); // compilation error! } //... List<String> names = new ArrayList<String>(); appendNewObject(names); // this part is fine!

¡El compilador hizo un excelente trabajo al protegerlo de violar potencialmente la invariancia de tipo de la List<?> ! Si hubiera declarado el parámetro como List list tipo sin List list , entonces el código se compilaría y violaría el tipo invariante de los List<String> names de List<String> names .

Un tipo en bruto es el borrado de ese tipo.

De vuelta a JLS 4.8:

Es posible utilizar como tipo el borrado de un tipo parametrizado o el borrado de un tipo de matriz cuyo tipo de elemento es un tipo parametrizado. Tal tipo se llama un tipo en bruto .

[...]

Las superclases (respectivamente, superinterfaces) de un tipo en bruto son los borrados de las superclases (superinterfaces) de cualquiera de las parametrizaciones del tipo genérico.

El tipo de constructor, método de instancia o campo no static de un tipo sin procesar C que no se hereda de sus superclases o superinterfaces es el tipo sin procesar que corresponde al borrado de su tipo en la declaración genérica correspondiente a C

En términos más simples, cuando se usa un tipo sin formato, los constructores, los métodos de instancia y static campos no static también se borran .

Tomemos el siguiente ejemplo:

class MyType<E> { List<String> getNames() { return Arrays.asList("John", "Mary"); } public static void main(String[] args) { MyType rawType = new MyType(); // unchecked warning! // required: List<String> found: List List<String> names = rawType.getNames(); // compilation error! // incompatible types: Object cannot be converted to String for (String str : rawType.getNames()) System.out.print(str); } }

Cuando usamos el MyType sin MyType , getNames se borra, ¡para que devuelva una List procesar!

JLS 4.6 continúa explicando lo siguiente:

El borrado de tipos también asigna la firma de un constructor o método a una firma que no tiene tipos parametrizados o variables de tipo. El borrado de un constructor o de la firma del método es una firma que consiste en el mismo nombre que s y los borrados de todos los tipos de parámetros formales dados en s .

El tipo de retorno de un método y los parámetros de tipo de un método genérico o constructor también se borran si el método o la firma del constructor se borran.

El borrado de la firma de un método genérico no tiene parámetros de tipo.

El siguiente informe de errores contiene algunos pensamientos de Maurizio Cimadamore, un compilador de desarrollo, y Alex Buckley, uno de los autores de JLS, sobre por qué debería ocurrir este tipo de comportamiento: https://bugs.openjdk.java.net/browse/JDK-6400189 . (En resumen, hace que la especificación sea más simple.)

Si no es seguro, ¿por qué se le permite usar un tipo sin formato?

Aquí hay otra cita de JLS 4.8:

El uso de tipos brutos se permite solo como una concesión a la compatibilidad de código heredado. El uso de tipos en bruto en el código escrito después de la introducción de la genérico en el lenguaje de programación Java está totalmente desaconsejado. Es posible que las versiones futuras del lenguaje de programación Java no permitan el uso de tipos sin formato.

Effective Java 2nd Edition también tiene esto para agregar:

Dado que no debe usar tipos brutos, ¿por qué los diseñadores de idiomas lo permitieron? Para proporcionar compatibilidad.

La plataforma Java estaba a punto de entrar en su segunda década cuando se introdujeron los genéricos, y existía una enorme cantidad de código Java en existencia que no usaba genéricos. Se consideró crítico que todo este código siga siendo legal e interoperable con el nuevo código que usa genéricos. Debía ser legal pasar instancias de tipos parametrizados a métodos diseñados para su uso con tipos ordinarios, y viceversa. Este requisito, conocido como compatibilidad de migración , condujo a la decisión de admitir tipos sin formato.

En resumen, los tipos en bruto NUNCA deben usarse en un nuevo código. Siempre debes usar tipos parametrizados .

¿No hay excepciones?

Desafortunadamente, debido a que los genéricos de Java no son reificados, hay dos excepciones donde los tipos sin procesar deben usarse en el nuevo código:

  • List.class clase, por ejemplo, List.class , no List<String>.class
  • instanceof operando, por ejemplo, o instanceof Set , no o instanceof Set<String>

Ver también


Aquí estoy considerando varios casos a través de los cuales puede aclarar el concepto

1. ArrayList<String> arr = new ArrayList<String>(); 2. ArrayList<String> arr = new ArrayList(); 3. ArrayList arr = new ArrayList<String>();

Caso 1

ArrayList<String> arr it es una variable de referencia ArrayList con tipo String que hace referencia a un objeto ArralyList de tipo String . Significa que solo puede contener objetos de tipo cadena.

Es un Strict to String no un tipo Raw, por lo tanto, nunca generará una advertencia.

arr.add("hello");// alone statement will compile successfully and no warning. arr.add(23); //prone to compile time error. //error: no suitable method found for add(int)

Caso 2

En este caso, ArrayList<String> arr es un tipo estricto pero su Object new ArrayList(); es un tipo crudo

arr.add("hello"); //alone this compile but raise the warning. arr.add(23); //again prone to compile time error. //error: no suitable method found for add(int)

Aquí arr es un tipo estricto. Por lo tanto, generará un error de tiempo de compilación al agregar un integer .

Advertencia : - Se hace referencia a un objeto de tipo Raw a una variable referenciada de tipo Strict de ArrayList .

Caso 3

En este caso, ArrayList arr es un tipo en bruto pero su Object new ArrayList<String>(); Es un tipo estricto.

arr.add("hello"); arr.add(23); //compiles fine but raise the warning.

Agregará cualquier tipo de objeto en él porque arr es un tipo sin formato.

Advertencia : - Se hace referencia a un objeto de tipo Strict a una variable raw formato referenciada.


Aquí hay otro caso donde los tipos crudos te morderán:

public class StrangeClass<T> { @SuppressWarnings("unchecked") public <X> X getSomethingElse() { return (X)"Testing something else!"; } public static void main(String[] args) { final StrangeClass<String> withGeneric = new StrangeClass<>(); final StrangeClass withoutGeneric = new StrangeClass(); final String value1, value2; // Compiles value1 = withGeneric.getSomethingElse(); // Produces compile error: // incompatible types: java.lang.Object cannot be converted to java.lang.String value2 = withoutGeneric.getSomethingElse(); } }

Como se mencionó en la respuesta aceptada, pierde todo el soporte para genéricos dentro del código del tipo sin formato. Cada parámetro de tipo se convierte en su borrado (que en el ejemplo anterior es solo Object ).


El compilador quiere que escribas esto:

private static List<String> list = new ArrayList<String>();

porque de lo contrario, podría agregar cualquier tipo que desee en la list , haciendo que la creación de instancias como new ArrayList<String>() tenga sentido. Los genéricos de Java son solo una característica en tiempo de compilación, por lo que un objeto creado con la new ArrayList<String>() aceptará felizmente los elementos Integer o JFrame si se asigna a una referencia de la lista "tipo sin formato"; el objeto en sí mismo no sabe qué tipos Se supone que debe contener, solo el compilador lo hace.


Encontré esta página después de hacer algunos ejercicios de muestra y tener exactamente el mismo desconcierto.

============== Salí de este código como se indica en la muestra ===============

public static void main(String[] args) throws IOException { Map wordMap = new HashMap(); if (args.length > 0) { for (int i = 0; i < args.length; i++) { countWord(wordMap, args[i]); } } else { getWordFrequency(System.in, wordMap); } for (Iterator i = wordMap.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); System.out.println(entry.getKey() + " :/t" + entry.getValue()); }

====================== Para este código ========================

public static void main(String[] args) throws IOException { // replace with TreeMap to get them sorted by name Map<String, Integer> wordMap = new HashMap<String, Integer>(); if (args.length > 0) { for (int i = 0; i < args.length; i++) { countWord(wordMap, args[i]); } } else { getWordFrequency(System.in, wordMap); } for (Iterator<Entry<String, Integer>> i = wordMap.entrySet().iterator(); i.hasNext();) { Entry<String, Integer> entry = i.next(); System.out.println(entry.getKey() + " :/t" + entry.getValue()); } }

================================================== =============================

Puede ser más seguro, pero tomó 4 horas para desordenar la filosofía ...


Lo que está diciendo es que su list es una List de objetos no especificados. Es decir, Java no sabe qué tipo de objetos están dentro de la lista. Luego, cuando desee iterar la lista, debe lanzar cada elemento para poder acceder a las propiedades de ese elemento (en este caso, String).

En general, es una mejor idea para parametrizar las colecciones, por lo que no tiene problemas de conversión, solo podrá agregar elementos del tipo parametrizado y su editor le ofrecerá los métodos apropiados para seleccionar.

private static List<String> list = new ArrayList<String>();


Los tipos sin procesar están bien cuando expresan lo que quieres expresar.

Por ejemplo, una función de deserialización puede devolver una List , pero no conoce el tipo de elemento de la lista. Así que la List es el tipo de retorno apropiado aquí.


Un tipo "sin procesar" en Java es una clase que no es genérica y trata con objetos "sin procesar", en lugar de con parámetros de tipo genérico seguros.

Por ejemplo, antes de que estuvieran disponibles los genéricos de Java, usarías una clase de colección como esta:

LinkedList list = new LinkedList(); list.add(new MyObject()); MyObject myObject = (MyObject)list.get(0);

Cuando agrega su objeto a la lista, no le importa qué tipo de objeto es, y cuando lo obtiene de la lista, tiene que convertirlo explícitamente al tipo que está esperando.

Usando genéricos, elimina el factor "desconocido", porque debe especificar explícitamente qué tipo de objetos pueden ir en la lista:

LinkedList<MyObject> list = new LinkedList<MyObject>(); list.add(new MyObject()); MyObject myObject = list.get(0);

Tenga en cuenta que con los genéricos no tiene que lanzar el objeto procedente de la llamada get, la colección está predefinida para que solo funcione con MyObject. Este hecho es el principal factor determinante para los genéricos. Cambia una fuente de errores de tiempo de ejecución en algo que puede verificarse en tiempo de compilación.


Un tipo sin formato es la falta de un parámetro de tipo cuando se utiliza un tipo genérico.

El tipo sin formato no se debe usar porque podría causar errores de tiempo de ejecución, como insertar un double en lo que se suponía que era un Set de int s.

Set set = new HashSet(); set.add(3.45); //ok

Cuando recuperas las cosas del Set , no sabes lo que está saliendo. Asumamos que esperas que sea todo int s, lo estás convirtiendo en Integer ; excepción en el tiempo de ejecución cuando llega el double 3.45.

Con un parámetro de tipo agregado a su Set , obtendrá un error de compilación de una vez. Este error preventivo le permite solucionar el problema antes de que algo explote durante el tiempo de ejecución (ahorrando así tiempo y esfuerzo).

Set<Integer> set = new HashSet<Integer>(); set.add(3.45); //NOT ok.


Un tipo sin formato es el nombre de una clase o interfaz genérica sin ningún tipo de argumentos. Por ejemplo, dada la clase de caja genérica:

public class Box<T> { public void set(T t) { /* ... */ } // ... }

Para crear un tipo parametrizado de Box<T> , debe proporcionar un argumento de tipo real para el parámetro de tipo formal T :

Box<Integer> intBox = new Box<>();

Si se omite el argumento de tipo real, creará un tipo sin procesar de Box<T> :

Box rawBox = new Box();

Por lo tanto, Box es el tipo sin formato del tipo genérico Box<T> . Sin embargo, una clase no genérica o tipo de interfaz no es un tipo en bruto.

Los tipos sin procesar aparecen en el código heredado porque muchas clases de API (como las clases de Colecciones) no eran genéricas antes de JDK 5.0. Cuando se usan tipos sin procesar, esencialmente se obtiene un comportamiento pre-genérico: una Box le proporciona Object . Para compatibilidad con versiones anteriores, se permite asignar un tipo parametrizado a su tipo sin procesar:

Box<String> stringBox = new Box<>(); Box rawBox = stringBox; // OK

Pero si asigna un tipo sin formato a un tipo parametrizado, recibirá una advertencia:

Box rawBox = new Box(); // rawBox is a raw type of Box<T> Box<Integer> intBox = rawBox; // warning: unchecked conversion

También recibe una advertencia si utiliza un tipo sin formato para invocar métodos genéricos definidos en el tipo genérico correspondiente:

Box<String> stringBox = new Box<>(); Box rawBox = stringBox; rawBox.set(8); // warning: unchecked invocation to set(T)

La advertencia muestra que los tipos sin procesar omiten las comprobaciones de tipo genérico, aplazando la captura de código no seguro al tiempo de ejecución. Por lo tanto, debes evitar el uso de tipos crudos.

La sección Borrado de tipos tiene más información sobre cómo el compilador de Java usa tipos en bruto.

Mensajes de error sin marcar

Como se mencionó anteriormente, al mezclar código heredado con código genérico, puede encontrar mensajes de advertencia similares a los siguientes:

Nota: Example.java usa operaciones no verificadas o inseguras.

Nota: Recompile con -Xlint: sin marcar para más detalles.

Esto puede suceder cuando se usa una API más antigua que funciona con tipos sin procesar, como se muestra en el siguiente ejemplo:

public class WarningDemo { public static void main(String[] args){ Box<Integer> bi; bi = createBox(); } static Box createBox(){ return new Box(); } }

El término "sin marcar" significa que el compilador no tiene suficiente información de tipo para realizar todas las comprobaciones de tipo necesarias para garantizar la seguridad de tipo. La advertencia "sin marcar" está desactivada, por defecto, aunque el compilador da una pista. Para ver todas las advertencias "sin marcar", vuelva a compilar con -Xlint: sin marcar.

Recompilando el ejemplo anterior con -Xlint: no seleccionado revela la siguiente información adicional:

WarningDemo.java:4: warning: [unchecked] unchecked conversion found : Box required: Box<java.lang.Integer> bi = createBox(); ^ 1 warning

Para desactivar completamente las advertencias sin marcar, use el indicador -Xlint: -unchecked. La @SuppressWarnings("unchecked") suprime las advertencias sin marcar. Si no está familiarizado con la sintaxis de @SuppressWarnings , vea Anotaciones.

Fuente original: Tutoriales de Java


Página tutorial .

Un tipo sin formato es el nombre de una clase o interfaz genérica sin ningún tipo de argumentos. Por ejemplo, dada la clase de caja genérica:

public class Box<T> { public void set(T t) { /* ... */ } // ... }

Para crear un tipo de Cuadro parametrizado, debe proporcionar un argumento de tipo real para el parámetro de tipo formal T:

Box<Integer> intBox = new Box<>();

Si se omite el argumento de tipo real, creará un tipo sin procesar de Cuadro:

Box rawBox = new Box();


¿Qué es un tipo sin formato y por qué a menudo escucho que no se deben usar en un código nuevo?

Un "tipo bruto" es el uso de una clase genérica sin especificar un tipo de argumento (s) para su tipo (s) parametrizado, por ejemplo, utilizando List lugar de List<String> . Cuando se introdujeron genéricos en Java, se actualizaron varias clases para usar genéricos. El uso de estas clases como "tipo sin formato" (sin especificar un argumento de tipo) permitió que el código heredado aún se compile.

Los "tipos sin procesar" se utilizan para la compatibilidad hacia atrás. No se recomienda su uso en código nuevo porque usar la clase genérica con un argumento de tipo permite una tipificación más fuerte, lo que a su vez puede mejorar la comprensión del código y conducir a detectar problemas potenciales antes.

¿Cuál es la alternativa si no podemos usar tipos brutos y cómo es mejor?

La alternativa preferida es usar clases genéricas según lo previsto, con un argumento de tipo adecuado (por ejemplo, List<String> ). Esto permite al programador especificar tipos más específicamente, transmite más significado a los futuros mantenedores sobre el uso previsto de una variable o estructura de datos, y permite que el compilador aplique una mejor seguridad de tipos. Estas ventajas juntas pueden mejorar la calidad del código y ayudar a prevenir la introducción de algunos errores de codificación.

Por ejemplo, para un método en el que el programador quiere asegurarse de que una variable de lista llamada ''nombres'' solo contiene cadenas:

List<String> names = new ArrayList<String>(); names.add("John"); // OK names.add(new Integer(1)); // compile error


private static List<String> list = new ArrayList<String>();

Debe especificar el tipo de parámetro.

La advertencia indica que los tipos que están definidos para admitir generics deben estar parametrizados, en lugar de usar su forma sin formato.

List se define para admitir los genéricos: public class List<E> . Esto permite muchas operaciones de tipo seguro, que se verifican en tiempo de compilación.