objects - java object to type
Java Class.cast() vs. operador de cast (7)
Después de haber sido enseñado durante mis días de C ++ sobre los males del operador de elenco al estilo C, al principio me complació descubrir que en Java 5 java.lang.Class
había adquirido un método de cast
.
Pensé que finalmente tenemos una forma OO de lidiar con el casting.
Resulta que 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.
- ¿
Class.cast()
ser desterradoClass.cast()
a tierras genéricas? Allí tiene bastantes usos legítimos. - ¿Deberían los compiladores generar errores de compilación cuando se utiliza
Class.cast()
y las condiciones ilegales se pueden determinar en tiempo de compilación? - ¿Debería Java proporcionar un operador de conversión como una construcción de lenguaje similar a C ++?
Además de eliminar las advertencias de lanzamiento feo como la mayoría mencionada, Class.cast es el tiempo de ejecución utilizado en su mayoría con casting genérico, debido a que la información genérica se borrará en el tiempo de ejecución y de alguna manera se considerará que cada genérico es Object. Esto lleva a no lanzar una ClassCastException temprana.
por ejemplo, serviceLoder utiliza este truco al crear los objetos, compruebe S p = service.cast (c.newInstance ()); esto arrojará una excepción de lanzamiento de clase cuando SP = (S) c.newInstance (); no mostrará y puede mostrar una advertencia ''Tipo de seguridad: descarte no comprobado de Objeto a S'' . (igual que el Objeto P = (Objeto) c.nuevaInstancia ();)
-simplemente comprueba que el objeto fundido es instancia de la clase de fundición, entonces usará el operador de conversión para fundir y ocultar la advertencia al suprimirla.
Implementación de Java para lanzamiento 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
}
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 usar moldes de tipo C no es una buena idea en Java
En primer lugar, es muy desalentador hacer casi cualquier lanzamiento, 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()
debe usar principalmente cuando recupera el token de Class
mediante reflexión. Es más idiomático escribir
MyObject myObject = (MyObject) object
más bien que
MyObject myObject = MyObject.class.cast(object)
EDITAR: Errores en el tiempo de compilación
En general, Java realiza comprobaciones de lanzamiento solo en tiempo de ejecución. Sin embargo, el compilador puede emitir un error si puede demostrar que tales conversiones nunca pueden tener éxito (por ejemplo, enviar 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 una jerarquía mutua, el elenco nunca puede tener éxito.
Personalmente, he usado esto antes para construir un convertidor de JSON a POJO. En el caso de que JSONObject procesado con la función contenga una matriz o JSONObjects anidados (lo que implica que los datos aquí no son de un tipo primitivo o String
), intento invocar el método setter utilizando 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ítimos de class.cast()
Puedo pensar, al menos ahora tiene otro ejemplo.
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 lanzamiento en Java, sin importar cómo lo hagas, 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.
Solo he usado Class.cast(Object)
para evitar advertencias en "genéricos de la tierra". A menudo veo métodos haciendo cosas como esta:
@SuppressWarnings("unchecked")
<T> T doSomething() {
Object o;
// snip
return (T) o;
}
A menudo es mejor reemplazarlo 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)
que 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)
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.
Class.cast()
rara vez se usa en el código de Java. Si se usa, entonces generalmente con tipos que solo se conocen en tiempo de ejecución (es decir, a través de sus respectivos objetos de Class
y por 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).
No es similar a reinterpret_cast
, porque no le permitirá romper el sistema de tipos en el tiempo de ejecución más de lo que lo hace un reparto normal (es decir, puede "romper" los parámetros genéricos, pero no puede "romper" los tipos "reales") .
Los males del operador de conversión estilo C 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).
En general, comparar los operadores de casting de C ++ con Java Casting es bastante difícil ya que en Java solo se puede hacer referencia y nunca ocurre ninguna conversión a los objetos (solo los valores primitivos se pueden convertir usando esta sintaxis).