2010-11-11 13 views
5

Considere el siguiente código:La conversión a una clase interna con los genéricos

public class Outer<T> { 

    public class Inner{ 
    } 

    public static <T> Outer<T>.Inner get(){ 
     Object o = new Object(); 
     return (Outer<T>.Inner)o; 
    } 

    public static void main(String[] args) throws Exception { 
     Outer.<String>get(); 
    } 
} 

Este código se compila correctamente en Eclipse, pero falla al compilar en javac:

Outer.java:10: ')' expected 
     return (Outer<T>.Inner)o; 
         ^
Outer.java:10: ';' expected 
     return (Outer<T>.Inner)o; 
         ^
Outer.java:10: illegal start of expression 
     return (Outer<T>.Inner)o; 
          ^
3 errors 

Es esto un error en javac o ¿Eclipse?

Si cambio el elenco de (Outer.Inner)o compila, aunque hay una advertencia:

Eclipse:

Outer.Inner is a raw type. References to generic type Outer<T>.Inner should be parameterized 

javac:

Outer.java:10: warning: [unchecked] unchecked conversion 
found : Outer.Inner 
required: Outer<T>.Inner 
     return (Outer.Inner)o; 
      ^
1 warning 

javac versión: 1.6.0_21

+0

¿Cuál es la advertencia? –

+0

agregado a la pregunta – dogbane

+0

wow! Yo también probé esto. Eclipse está bien con el 1er código pero javac no tiene +1 por decirme algo nuevo. –

Respuesta

0

Usted cann ot arroja un Object a algo que no es.

+2

Si tiene una referencia a objeto, se puede convertir, literalmente, en cualquier tipo de referencia en lo que se refiere al compilador. La inspección le diría que esto arrojaría un error de tiempo de ejecución seguro, pero eso no es lo que el compilador se queja. –

+0

Heh, eliminé esa parte antes de responder. Claro, al compilador puede no importarle, pero la VM lo hará. Además, está usando como genérico de clase y método genérico. ¿No ganaría el método genérico? – Jeremy

+1

el parámetro de clase se ignora por completo para los métodos estáticos. La parametrización de una clase solo se aplica a las referencias a instancias. –

1

Lamentablemente, si está lanzando desde un Object, entonces no puede esquivar la advertencia de lanzamiento desactivada. Esto se debe a que Object no tiene suficiente información de tipo para tener el T.

(genéricos de Java son a base de borrado. Por lo tanto no hay manera de saber si un objeto tiene tipo de argumento T en tiempo de ejecución --- argumentos de tipo se utilizan en tiempo de compilación única.)

+1

No es la advertencia de lanzamiento sin marcar que es un problema. Javac no podrá compilar debido a los errores que ha enumerado. En eclipse se da la advertencia porque se compila con éxito. –

+1

@Mark: en realidad, si leo la pregunta correctamente, el OP podría hacer que se compilara correctamente usando 'Outer.Inner' en javac. Y eso es lo mejor que puedes hacer, en este caso. ¿Se debe permitir 'Outer .Inner' alguna vez? Esa es la parte de la que no estoy seguro. –

+0

Estoy de acuerdo y no te resté porque es una observación perfectamente válida. ¿Debería 'Outer .Inner' estar permitido? No veo por qué no. El compilador permite todos los otros tipos de moldes no verificados (como el lanzamiento de un 'Objeto' a una' Lista 'por ejemplo), por lo que no veo por qué esto es críticamente diferente. –

0

La siguiente "arreglar "funcionó cuando compilé con javac. También se compiló con éxito en Eclipse. El problema que percibo es que no puede crear un nuevo objeto a partir de una variable (como lo hizo en su caso). No sé cómo explicarlo o validar mi teoría.

/** 
* 
*/ 
package testcases; 

/** 
* @author The Elite Gentleman 
* 
*/ 
public class Outer<T> { 

    public class Inner{ 
    } 

    public static <T> Outer<T>.Inner get(){ 
     //Object o = new Object(); 
     //return (Outer<T>.Inner)o; 
     return new Outer<T>().new Inner(); 
    } 

    public static void main(String[] args) throws Exception { 
     Outer.<String>get(); 
    } 
} 

Básicamente, ya que Interior no es una clase anidada estática, con el fin de crear una instancia de ella, esta es la forma en que lo haría:

new Outer<T>().new Inner(); 

Además, Object o = new Object(); no garantiza que el objeto o es en realidad una instancia de tipo Inner clase.


actualización Mi solución ayudó con instancias de objetos y no para la fundición de objetos existentes objeto instanciado. Para eso, no tengo respuestas (pero somos desarrolladores, resolveremos algo :-)).

Lo que se me ocurre es por qué no hacer Inner clase clase anidada estática?

+1

No estoy seguro de que sea una "solución" si es funcionalmente completamente diferente. Esto está creando una nueva instancia, el OP estaba lanzando un objeto existente. –

+0

+1 para notar que no es una clase anidada estática –

+0

@Mark Peters, cierto, pero para este ejemplo, esencialmente, 'Object o = new Object();' no garantiza que el objeto 'o' sea de tipo' Inner 'también, de ahí mi" solución ". –

0

Hay dos problemas en su código:

En primer lugar es en tiempo de compilación: no se puede convertir un objeto de forma segura a un tipo genérico como genéricos no están materializadas en Java. Lanzar a un genérico no instanciado es claramente peligroso para su javac. Me temo que la respuesta del compilador a esto depende del proveedor y la versión del compilador.

En segundo lugar, el lanzamiento de un objeto, de tipo objeto, a cualquier otro tipo arrojará un ClassCastException en tiempo de ejecución ya que en Java la información del tipo está contenida en el objeto y no cambiará después de la creación.

+0

No me importa lo que sucederá en el tiempo de ejecución, porque este es un ejemplo simplificado. Solo me importa por qué javac no está compilando mi código. – dogbane

+0

Luego, debe dar su versión de javac porque ya hice moldes similares y solo recibí la advertencia 'no marcada', aunque ahora no puedo verificar la versión de javac. – Guillaume

+0

versión javac: 1.6.0_21 – dogbane

0

si tiene que usar un yeso, se puede suprimir la advertencia con @SuppressWarnings

Object o = new Object(); 

    @SuppressWarnings("unchecked") 
    Outer<T>.Inner returnVal = (Outer.Inner) o; 

    return returnVal; 

Pero darse cuenta de que existe la advertencia porque estás haciendo algo inseguro. El objeto no se puede convertir a String. Esto dará lugar a una excepción en tiempo de ejecución.

y como el Caballero Elite se dio cuenta, es posible que desee marcar interno como estática:

public static class Inner { 
+2

Suprimir una advertencia no evadirá el error de compilación. –

+0

@Mark dijo "Si cambio el modelo ... compila, aunque hay una advertencia" –

+1

@Mark pero sacas un buen punto, debería haber tomado el apagado. Corregido ahora. –

2

Lo más divertido es que, a menos que haya algo que echo de menos de genéricos de Java, tanto

return (Outer<T>.Inner) o; 

Y

return (Outer.Inner) o; 

Tanto compilación para el mismo código de bytes.

El problema para la primera línea ocurre en el análisis, lo que significa que javac y Eclipse no usan el mismo analizador para el código fuente de Java. Debería hacer una pregunta acerca de las diferencias que existen entre el analizador de Java de Eclipse JDT y el de javac. (O publique un error en Eclipse).

Si insistes en mantener ese comportamiento (sugiero refactorizar Inner a una clase interna estática), puedes usar @SuppressWarning con una asignación de campo (para restringir @SuppressWarning al alcance más pequeño posible).

@SuppressWarnings({"rawtypes","unchecked"}) 
Outer<T>.Inner casted = (Outer.Inner)o; 
return casted; 

EDITAR: OK, creo que lo tengo - JDT de Eclipse analiza el código de Java antes de pasarlo al compilador - y su analizador puede tener sentido de un reparto tan, mientras que (al menos su y mi versión de) javac no puede. (Y después de eso, Eclipse pasa directamente el código analizado a compilación). Antes de presentar un error, observe cómo se comporta la última versión de Java 6.

+0

El bytecode es el mismo debido al "borrado de tipo" en el que toda la información sobre los genéricos se elimina en el momento de la compilación. – dogbane

+0

Editado esa respuesta, actualmente estoy descargando la última versión de JDK de Sun para ver qué sucede. –

+0

así que me estás diciendo que nunca has compilado con Sun JDK todavía? –

0

(Dando otra respuesta porque mi anterior es demasiado ruidoso.)

Hay dos grandes pasos para hacer el fin de compilar el código fuente de Java a código de bytes:

  1. El código fuente debe ser analizada en un árbol de sintaxis
  2. El árbol de sintaxis se usa para generate bytecode.

Cuando javac compila, utiliza su propio generador de analizador y código de bytes (cuyos detalles de implementación dependerán del JDK que esté utilizando).

Cuando se compila JDT de Eclipse, usa su propio analizador de código y después de eso ... No sé. El punto que quiero hacer es que, de una forma u otra, "omiten" algunos de los analizadores de javac. (Por ejemplo, podrían pasar a los archivos Java modificados de javac que reemplazan todos los moldes de clases genéricas por clases crudas).

Mi punto es - al final, es un error en la parte de javac que analiza el código fuente de Java. (No hay ninguna razón por la cual tal construcción no estaría permitida en la especificación del lenguaje.)

Para contrarrestar esa falla, puede: * Modificar el diseño de su aplicación para evitarla por completo. * Cada vez que tiene que hacer este lanzamiento, todos ponen un tipo sin procesar y una anotación @SuppressWarnings en lugar del lanzamiento natural.

Cuestiones relacionadas