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


Preguntas:

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

523
2018-05-05 02:48


origen


Respuestas:


¿Qué es un tipo crudo?

La especificación del lenguaje Java define una tipo sin procesar como sigue:

JLS 4.8 Raw Types

Un tipo sin formato se define como uno de los siguientes:

  • 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 no-static tipo de miembro de un tipo sin formato R eso no es heredado 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) según el primer punto de la definición anterior; inn también tiene un tipo sin procesar en el tercer punto.

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

mt1y mt2 ambos se declaran con parámetros de tipo reales, por lo que no son tipos crudos.


¿Qué tiene 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 supongamos 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 el tiempo de ejecución, porque names contiene algo que no es un instanceof String.

Presumiblemente, si quieres names contener solo String, tú podría tal vez todavía use un tipo crudo y verificar manualmente cada  add usted mismo, y luego moldear manualmente a String cada artículo de names. Aun mejor, aunque NO es para usar un tipo sin procesar y deja que el compilador haga todo el trabajo por ti, 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 HACER querer names para permitir un 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 de raw del uso? <Object> como parámetros de tipo?

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

¿Cuál es la diferencia entre el tipo crudo List y el tipo parametrizado List<Object>? En términos generales, el primero ha optado por la verificación de tipo genérico, mientras que el segundo le dijo explícitamente al compilador que es capaz de contener objetos de cualquier tipo. Mientras puedes pasar un List<String> a un parámetro de tipo List, no puedes pasarlo a un parámetro de tipo List<Object>. Existen reglas de subtipificación para genéricos, y List<String>es un subtipo del tipo crudo List, pero no del tipo parametrizado List<Object>. Como consecuencia, pierdes seguridad tipo si usas tipo crudo como List, pero no si usa un tipo parametrizado como List<Object>.

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

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

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

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

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

Ver también


¿En qué se diferencia un tipo de raw del uso? <?> como un parámetro de tipo?

List<Object>, List<String>, etc. son todos List<?>, entonces puede ser tentador decir que solo son List en lugar. Sin embargo, hay una gran diferencia: desde un List<E> solo define add(E), no puedes agregar cualquier objeto arbitrario a un List<?>. Por otro lado, desde el tipo crudo List no tiene seguridad tipo, puede add casi cualquier cosa a un 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 trabajo maravilloso al protegerte de una posible violación de la invarianza de tipo de List<?>! Si ha declarado el parámetro como el tipo sin procesar List list, entonces el código compilaría, y violaría el tipo invariante de List<String> names.


Un tipo sin formato es la eliminación de ese tipo

Volver a JLS 4.8:

Es posible usar como un tipo la borradura de un tipo parametrizado o el borrado de un tipo de matriz cuyo tipo de elemento es un tipo parametrizado. Tal tipo se llama tipo sin procesar.

[...]

Las superclases (respectivamente, superinterfaces) de un tipo sin formato son las eliminaciones de las superclases (superinterfaces) de cualquiera de las parametrizaciones del tipo genérico.

El tipo de constructor, método de instancia o nostatic campo de un tipo crudo 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 crudo, los constructores, los métodos de instancia y los nostatic los campos son también borrado.

Toma 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 la materia prima MyType, getNames se borra también, por lo que devuelve un List!

JLS 4.6 continúa explicando lo siguiente:

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

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

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

El siguiente informe de error contiene algunos pensamientos de Maurizio Cimadamore, un desarrollador de compiladores, y Alex Buckley, uno de los autores del JLS, sobre por qué debería ocurrir este tipo de comportamiento: https://bugs.openjdk.java.net/browse/JDK-6400189. (En resumen, simplifica la especificación).


Si no es seguro, ¿por qué está permitido usar un tipo sin procesar?

Aquí hay otra cita de JLS 4.8:

El uso de tipos crudos solo se permite como concesión a la compatibilidad del código heredado. Se desaconseja encarecidamente el uso de tipos crudos en el código escrito después de la introducción de genericidad en el lenguaje de programación Java. Es posible que 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 crudos, ¿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 que no usaba genéricos. Se consideró crítico que todo este código siga siendo legal e interoperable con un nuevo código que sí utiliza genéricos. Tenía que ser legal pasar instancias de tipos parametrizados a métodos que se diseñaron para su uso con tipos ordinarios, y viceversa. Este requisito, conocido como compatibilidad de migración, impulsó la decisión de admitir tipos crudos.

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


¿No hay excepciones?

Lamentablemente, como los genéricos de Java no están reificados, existen dos excepciones en las que se deben usar los tipos sin formato en el código nuevo:

  • Literales de clase, p. List.classno List<String>.class
  • instanceof operando, p. o instanceof Setno o instanceof Set<String>

Ver también


623
2018-05-05 04:50



¿Qué son los tipos crudos en Java y por qué a menudo escucho que no deberían usarse en el nuevo código?

Los tipos crudos son historia antigua del lenguaje Java. Al principio hubo Collections y sostuvieron Objects Nada más y nada menos. Cada operación en Collections requiere moldes 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 sucedieron

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 hacer cumplir la seguridad del tipo, por lo que el programador tenía que recordar lo que almacenaba en una colección.
Los genéricos fueron inventados para evitar 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 Compareable:

//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 el CompareAble Interfaz con compareTo(MyCompareAble) con tipos crudos. Por qué no deberías usarlos:

  • Alguna Object almacenado en una Collection tiene que ser lanzado antes de que pueda ser utilizado
  • El uso de genéricos permite verificar el tiempo de compilación
  • Usar tipos crudos 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 de Java que los tipos sin procesar. La magia ocurre principalmente 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ías si usaras los tipos crudos directamente. Pensé que no estoy seguro de qué pasaría con el CompareAble interfaz, supongo que crea dos compareTo funciones, una tomando MyCompareAble y el otro tomando una Object y pasándolo al primero después de lanzarlo.

¿Cuáles son las alternativas a los tipos sin procesar? genéricos


48
2018-05-05 21:50



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 Box genérica:

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

Para crear un tipo parametrizado de Box<T>, proporciona 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, se crea un tipo de Box<T>:

Box rawBox = new Box();

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

Los tipos de raw 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. Al usar tipos crudos, esencialmente obtienes un comportamiento pre-genérico: un Box te dio Objects. Para compatibilidad con versiones anteriores, se permite asignar un tipo parametrizado a su tipo sin formato:

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

Pero si asigna un tipo sin procesar a un tipo parametrizado, recibe 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 usa 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 crudos omiten las comprobaciones de tipo genérico, lo que retrasa la captura del código inseguro en el tiempo de ejecución. Por lo tanto, debe evitar el uso de tipos sin procesar.

La sección Borrar tipo tiene más información sobre cómo el compilador Java usa tipos sin formato.

Mensajes de error no verificados

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: desmarcado para más detalles.

Esto puede suceder cuando se utiliza una API anterior que opera en 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 del tipo. La advertencia "desmarcada" está deshabilitada, por defecto, aunque el compilador da una pista. Para ver todas las advertencias "sin marcar", vuelva a compilar con -Xlint: desmarcado.

Volver a compilar el ejemplo anterior con -Xlint: desmarcado 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 deshabilitar completamente las advertencias no verificadas, use el indicador -Xlint: -unchecked. los @SuppressWarnings("unchecked") la anotación suprime las advertencias sin marcar. Si no está familiarizado con el @SuppressWarnings sintaxis, ver Anotaciones.

Fuente original: Tutoriales de Java 


21
2017-08-10 23:58



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

Debe especificar el parámetro de tipo.

La advertencia informa que los tipos que se definen para admitir genéricos debe ser parametrizado, en lugar de usar su forma cruda.

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


16
2018-06-16 07:44



Un tipo "en bruto" en Java es una clase que no es genérica y se ocupa de objetos "en bruto", en lugar de parámetros de tipo genérico seguro para tipos.

Por ejemplo, antes de que los genéricos de Java estuvieran disponibles, usaría 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.

Con los genéricos, elimine 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 mismo hecho es el principal factor de impulso para los genéricos. Cambia una fuente de errores de tiempo de ejecución en algo que se puede verificar en tiempo de compilación.


12
2018-05-05 02:58



¿Qué es un tipo sin procesar y por qué a menudo escucho que no deberían usarse en un código nuevo?

Un "tipo sin procesar" es el uso de una clase genérica sin especificar un tipo argumento (s) para su (s) tipo (s) parametrizado (s), p. utilizando List en lugar de List<String>. Cuando se introdujeron los 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 se compilara aún.

Los "tipos crudos" se utilizan para la compatibilidad con versiones anteriores. No se recomienda su uso en el nuevo código porque el uso de la clase genérica con un argumento tipo permite un tipado más fuerte, lo que a su vez puede mejorar la comprensibilidad del código y conducir a la detección de posibles problemas antes.

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

La alternativa preferida es utilizar clases genéricas según lo previsto, con un argumento de tipo adecuado (p. 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 al compilador aplicar una mejor seguridad de tipo. 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 donde el programador quiere asegurarse de que una variable de la Lista llamada 'nombres' contenga solo cadenas:

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

10
2018-05-05 04:31



El compilador quiere que escribas esto:

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

porque de lo contrario, podrías agregar cualquier tipo que te guste list, haciendo la instanciación como new ArrayList<String>() inútil. Los genéricos de Java son solo una característica de tiempo de compilación, por lo que un objeto creado con new ArrayList<String>() aceptaré felizmente Integer o JFrame elementos si están asignados a una referencia del "tipo sin procesar" List - el objeto en sí mismo no sabe nada sobre qué tipos se supone que debe contener, solo el compilador lo hace.


10
2018-06-16 07:53



Aquí estoy considerando varios casos a través de los cuales puedes 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 es un ArrayList variable de referencia con tipo String que referencia a un ArralyList Objeto del tipo String. Significa que solo puede contener String type Object.

Es un Estricto String no es un tipo sin procesar, por lo que 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 tu Objeto 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, aumentará el error de tiempo de compilación al agregar un integer.

Advertencia :- UN Raw Tipo Objeto se hace referencia a un Strict tipo variable referenciada de ArrayList.

Caso 3

En este caso ArrayList arr es un tipo crudo pero tu Objeto new ArrayList<String>(); es un tipo estricto

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

Se agregará cualquier tipo de objeto en él porque arr es un tipo crudo.

Advertencia :- UN Strict Tipo Objeto se hace referencia a un raw tipo de referencia variable.


9
2018-05-13 13:51



UN crudo-type es la falta de una tipo de parámetro cuando se usa un tipo genérico.

Raw-type no debe utilizarse 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 ints.

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

Al recuperar las cosas de la Set, no sabes lo que está saliendo. Supongamos que esperas que sea todo ints, lo estás lanzando a Integer; excepción en tiempo de ejecución cuando el double 3.45 viene junto.

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

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

8
2018-05-05 04:44



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

En general, es una mejor idea parametrizar las colecciones, para que no tenga 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>();

4
2018-06-16 07:47