2009-10-12 19 views
83

Después de haber sido enseñado durante mis días de C++ sobre los males del operador de elenco estilo C, al principio me complació encontrar que en Java 5 java.lang.Class había adquirido un método cast.Java Class.cast() frente al operador de elenco

Pensé que finalmente tenemos una forma OO de tratar con el casting.

Resulta Class.cast no es lo mismo que static_cast en C++. Es más como reinterpret_cast. No generará un error de compilación donde se espera y, en su lugar, se pospondrá al tiempo de ejecución. Aquí hay un caso de prueba simple para demostrar diferentes comportamientos.

package test; 

import static org.junit.Assert.assertTrue; 

import org.junit.Test; 


public class TestCast 
{ 
    static final class Foo 
    { 
    } 

    static class Bar 
    { 
    } 

    static final class BarSubclass 
     extends Bar 
    { 
    } 

    @Test 
    public void test () 
    { 
     final Foo foo = new Foo(); 
     final Bar bar = new Bar(); 
     final BarSubclass bar_subclass = new BarSubclass(); 

     { 
      final Bar bar_ref = bar; 
     } 

     { 
      // Compilation error 
      final Bar bar_ref = foo; 
     } 
     { 
      // Compilation error 
      final Bar bar_ref = (Bar) foo; 
     } 

     try 
     { 
      // !!! Compiles fine, runtime exception 
      Bar.class.cast(foo); 
     } 
     catch (final ClassCastException ex) 
     { 
      assertTrue(true); 
     } 

     { 
      final Bar bar_ref = bar_subclass; 
     } 

     try 
     { 
      // Compiles fine, runtime exception, equivalent of C++ dynamic_cast 
      final BarSubclass bar_subclass_ref = (BarSubclass) bar; 
     } 
     catch (final ClassCastException ex) 
     { 
      assertTrue(true); 
     } 
    } 
} 

Entonces, estas son mis preguntas.

  1. ¿Debería desterrar Class.cast() a Generics land? Allí tiene bastantes usos legítimos.
  2. ¿Deben los compiladores generar errores de compilación cuando se utiliza Class.cast() y se pueden determinar las condiciones ilegales en tiempo de compilación?
  3. ¿Debería Java proporcionar un operador de conversión como una construcción de lenguaje similar a C++?
+3

Respuesta simple: (1) ¿Dónde está "Generics land"? ¿En qué se diferencia esto de cómo se usa el operador de reparto ahora? (2) Probablemente. Pero en el 99% de todo el código Java que se haya escrito, es * extremadamente * improbable que alguien use 'Class.cast()' cuando las condiciones ilegales se pueden determinar en tiempo de compilación. En ese caso, todos menos Uds. Usan el operador de conversión estándar. (3) Java tiene un operador de conversión como una construcción de lenguaje. No es similar a C++. Eso se debe a que muchas de las construcciones de lenguaje de Java no son similares a C++. A pesar de las similitudes superficiales, Java y C++ son bastante diferentes. –

+2

Por qué -1 --------- –

Respuesta

88

Solo he usado Class.cast(Object) para evitar advertencias en "genéricos terrestres". A menudo veo métodos haciendo cosas como esta:

@SuppressWarnings("unchecked") 
<T> T doSomething() { 
    Object o; 
    // snip 
    return (T) o; 
} 

A menudo es mejor sustituirlo por:

<T> T doSomething(Class<T> cls) { 
    Object o; 
    // snip 
    return cls.cast(o); 
} 

Ese es el único caso de uso para Class.cast(Object) me he encontrado.

En cuanto a las advertencias del compilador: sospecho que Class.cast(Object) no es especial para el compilador. Podría optimizarse cuando se usa estáticamente (es decir, Foo.class.cast(o) en lugar de cls.cast(o)) pero nunca he visto a nadie usándolo, lo que hace que el esfuerzo de construir esta optimización en el compilador sea algo sin valor.

+0

Mi última palabra es que si 'Foo.class.cast (o)' se comportaría como '(Foo)' y '(Foo)' estaban prohibidos, daría lugar a 2 resultados: menos fundición casual (a nadie le gusta escribir mucho) Búsqueda más fácil de instancias de conversión en el código fuente. Con eso, dejaré la Tierra de los Sueños y desterraré Class.cast a Generics Land :). –

+6

Creo que la forma correcta en este caso sería hacer una instancia de verificación como la siguiente: if (cls.isInstance (o)) { return cls.cast (o); } Excepto si está seguro de que el tipo será correcto, por supuesto. – Puce

+0

Creo que cast() funciona muy bien en algunos patrones tipo fábrica. Por ejemplo, enviar datos desde algún XDR a alguna representación interna basada en una definición .class. Esto funciona de manera muy parecida a ObjectiveC casting –

16

Siempre es problemático y a menudo engañoso intentar traducir constructos y conceptos entre idiomas. Casting no es una excepción. Particularmente porque Java es un lenguaje dinámico y C++ es algo diferente.

Todo el moldeado en Java, sin importar cómo lo haga, se realiza en tiempo de ejecución. La información de tipo se mantiene en tiempo de ejecución. C++ es un poco más de una mezcla. Puedes convertir una estructura en C++ a otra y es simplemente una reinterpretación de los bytes que representan esas estructuras. Java no funciona de esa manera.

También los genéricos en Java y C++ son muy diferentes. No te preocupes demasiado por cómo haces cosas C++ en Java. Necesitas aprender a hacer las cosas de la manera Java.

+0

No toda la información se guarda en el tiempo de ejecución. Como puede ver en mi ejemplo, '(Bar) foo' genera un error en tiempo de compilación, pero' Bar.class.cast (foo) 'no lo hace. En mi opinión, si se utiliza de esta manera, debería. –

+5

@Alexander Pogrebnyak: ¡No hagas eso entonces! 'Bar.class.cast (foo)' le dice explícitamente al compilador que desea hacer el cast en el tiempo de ejecución. Si desea una verificación en tiempo de compilación de la validez del elenco, su única opción es hacer el molde de estilo '(Bar) foo'. –

+0

¿Cuál crees que es la forma de hacer lo mismo? ya que Java no es compatible con herencia de clases múltiples. – YAcaCsh

16

En primer lugar, se desaconseja hacer casi cualquier yeso, por lo que debe limitarlo tanto como sea posible. Se pierden los beneficios de las características de Java fuertemente compiladas en tiempo de compilación.

En cualquier caso, Class.cast() se debe utilizar principalmente cuando recupera el token Class mediante reflexión. Es más idiomática para escribir

MyObject myObject = (MyObject) object 

en lugar de

MyObject myObject = MyObject.class.cast(object) 

EDIT: comprobaciones de errores en tiempo de compilación

Por encima de todo, Java realiza emitidos en tiempo de ejecución solamente. Sin embargo, el compilador puede emitir un error si puede probar que tales conversiones nunca pueden tener éxito (por ejemplo, lanzar una clase a otra clase que no sea un supertipo y asignar un tipo de clase final a la clase/interfaz que no esté en su jerarquía de tipos). Aquí, dado que Foo y Bar son clases que no están en ninguna otra jerarquía, el reparto nunca podrá tener éxito.

+0

Estoy totalmente de acuerdo en que los moldes deben usarse con moderación, es por eso que tener algo que pueda buscarse fácilmente sería de gran beneficio para la refabricación/mantenimiento del código. Pero el problema es que aunque a primera vista 'Class.cast' parece ajustarse a la factura, crea más problemas de los que resuelve. –

9

Class.cast() rara vez se utiliza en el código de Java. Si se usa, generalmente con tipos que solo se conocen en tiempo de ejecución (es decir, a través de sus respectivos objetos Class y mediante algún parámetro de tipo).Solo es realmente útil en código que usa genéricos (esa es también la razón por la que no se introdujo anteriormente).

Es no similar a reinterpret_cast, porque va a no permitirá romper el sistema de tipos en tiempo de ejecución más que un reparto normal no (es decir, puede "romper" parámetros de tipo genérico, pero no puede "romper" tipos "reales").

Los males del operador de conversión C-estilo generalmente no se aplican a Java. El código de Java que se parece a un elenco de estilo C es muy similar a un dynamic_cast<>() con un tipo de referencia en Java (recuerde: Java tiene la información del tipo de tiempo de ejecución).

Generalmente, la comparación de los operadores de casting de C++ con Java es bastante difícil ya que en Java solo se puede hacer referencia y nunca ocurre conversión alguna a los objetos (solo se pueden convertir valores primitivos usando esta sintaxis).

+0

'dynamic_cast <>()' con un tipo de referencia. –

+0

@Tom: ¿esa edición es correcta? Mi C++ está muy oxidado, tuve que volver a googlear un montón ;-) –

+0

+1 por: "Los males del operador de conversión de estilo C generalmente no se aplican a Java". Muy cierto. Estaba a punto de publicar esas palabras exactas como un comentario sobre la pregunta. –

3

C++ y Java son idiomas diferentes.

El operador de conversión Java C-style es mucho más restringido que la versión C/C++. Efectivamente, el elenco de Java es como el C++ dynamic_cast si el objeto que tienes no se puede convertir a la nueva clase obtendrás una excepción de tiempo de ejecución (o si hay suficiente información en el código una vez en tiempo de compilación). Por lo tanto, la idea de C++ de no utilizar moldes de tipo C no es una buena idea en Java

0

Además de eliminar las advertencias feos de colada como las más mencionadas, Class.cast es en tiempo de ejecución mayormente utilizado con colada genérica, debido a que la información genérica se borrará en tiempo de ejecución y de alguna manera se considerará cada objeto genérico. lleva a no lanzar una ClassCastException temprana.

por ejemplo serviceLoder utiliza este truco al crear los objetos, comprueba S p = service.cast (c.newInstance()); esto lanzará una excepción de lanzamiento de clase cuando S P = (S) c.newInstance(); no y puede mostrar una advertencia 'seguridad Tipo: Desactivada fundido del objeto a S' . (igual que el objeto P = (Object) c.newInstance();)

-simplemente se comprueba que el objeto fundido es una instancia de casting class y usará el operador de conversión para fundir y ocultar la advertencia al suprimirla.

aplicación java para el elenco dinámico:

@SuppressWarnings("unchecked") 
public T cast(Object obj) { 
    if (obj != null && !isInstance(obj)) 
     throw new ClassCastException(cannotCastMsg(obj)); 
    return (T) obj; 
} 




    private S nextService() { 
     if (!hasNextService()) 
      throw new NoSuchElementException(); 
     String cn = nextName; 
     nextName = null; 
     Class<?> c = null; 
     try { 
      c = Class.forName(cn, false, loader); 
     } catch (ClassNotFoundException x) { 
      fail(service, 
       "Provider " + cn + " not found"); 
     } 
     if (!service.isAssignableFrom(c)) { 
      fail(service, 
       "Provider " + cn + " not a subtype"); 
     } 
     try { 
      S p = service.cast(c.newInstance()); 
      providers.put(cn, p); 
      return p; 
     } catch (Throwable x) { 
      fail(service, 
       "Provider " + cn + " could not be instantiated", 
       x); 
     } 
     throw new Error();   // This cannot happen 
    } 
0

Personalmente, he utilizado esto antes de construir un JSON al convertidor POJO.En el caso de que el JSONObject procesado con la función contiene una matriz o anidados JSONObjects (lo que implica que los datos aquí no es de un tipo primitivo o String), intento para invocar el método setter usando class.cast() de esta manera:

public static Object convertResponse(Class<?> clazz, JSONObject readResultObject) { 
    ... 
    for(Method m : clazz.getMethods()) { 
     if(!m.isAnnotationPresent(convertResultIgnore.class) && 
      m.getName().toLowerCase().startsWith("set")) { 
     ... 
     m.invoke(returnObject, m.getParameters()[0].getClass().cast(convertResponse(m.getParameters()[0].getType(), readResultObject.getJSONObject(key)))); 
    } 
    ... 
} 

No estoy seguro si esto es extremadamente útil, pero como dije aquí antes, la reflexión es uno de los pocos casos de uso legítimo de class.cast() que se me ocurre, al menos ahora tiene otro ejemplo.