2010-10-16 16 views
6

(Por favor, no se aconseja que debe abstracta X más y añadir otro método para ello.)java: instanceof y cast combinados?

En C++, cuando tengo una variable x de tipo y quiero hacer algo específico si también es de tipo Y* (Y ser una subclase de X), estoy escribiendo esto: (¿o es)

if(Y* y = dynamic_cast<Y*>(x)) { 
    // now do sth with y 
} 

lo mismo no parece posible en Java.

He leído el código de Java en su lugar:

if(x instanceof Y) { 
    Y y = (Y) x; 
    // ... 
} 

A veces, cuando usted no tiene una variable x pero es una expresión más compleja en su lugar, simplemente debido a este problema, se necesita una variable ficticia en Java:

X x = something(); 
if(x instanceof Y) { 
    Y y = (Y) x; 
    // ... 
} 
// x not needed here anymore 

(cosa común es que something() es iterator.next() Y hay que ver que también no se puede llamar a que sólo dos veces usted realmente necesita la variable ficticia...)

Realmente no necesita x en absoluto aquí - solo lo tiene porque no puede hacer el control instanceof de una vez con el yeso. Comparación de nuevo al código bastante común C++:

if(Y* y = dynamic_cast<Y*>(something())) { 
    // ... 
} 

Debido a esto, he introducido una función castOrNull que hace que sea posible para evitar la variable ficticia x. Puedo escribir esto ahora:

Y y = castOrNull(something(), Y.class); 
if(y != null) { 
    // ... 
} 

Implementación de castOrNull:

public static <T> T castOrNull(Object obj, Class<T> clazz) { 
    try { 
     return clazz.cast(obj); 
    } catch (ClassCastException exc) { 
     return null; 
    } 
} 

Ahora, me dijeron que using this castOrNull function in that way is an evil thing do to. ¿Porqué es eso? (O dicho de la cuestión más general: ¿Está de acuerdo y creo que esto es malo Si es así, ¿por qué tan ¿O usted piensa que esto es un tal vez rara caso válido() utiliza???)


Como se ha dicho , No quiero una discusión sobre si el uso de dicho downcast es una buena idea. Pero quiero aclarar en breve por eso que a veces lo uso:

  1. A veces me meto en el caso en que tengo que elegir entre añadir otro nuevo método para una cosa muy específica (que sólo se aplicará por una sola subclase en una caso específico) o usando tal cheque instanceof. Básicamente, tengo la opción entre agregar una función doSomethingVeryVerySpecificIfIAmY() o hacer la verificación instanceof. Y en tales casos, siento que este último es más limpio.

  2. A veces tengo una colección de alguna clase de interfaz/base y para todas las entradas de tipo Y, quiero hacer algo y luego eliminarlos de la colección. (Por ejemplo que tenía el caso en que tenía una estructura de árbol y quería borrar todos los del niño que son hojas vacías.)

+1

me gustaría utilizar en lugar de _instanceOf_ _class.cast_ para evitar excepciones innecesarias en _castOrNull_, pero aparte de eso parece que tienes ayudante perfectamente normal. Deje que ** Tom ** aclare lo que quiso decir. –

+0

Si Y es una _superclass_ de X, no necesita ningún tipo de conversión, ¿quiso decir _subclass_? –

+0

Si bien esto no es de una sola línea en Java, el rendimiento de instanceof y casting es bastante bueno. Publicando algunos tiempos en Java7 sobre diferentes enfoques del problema aquí: http://stackoverflow.com/questions/16320014/java-optimization-nitpick-is-it-faster-to-cast-something-and-let-it- throw-excep/28858680 # 28858680 – Wheezil

Respuesta

8

Ahora, me dijeron que usar esta función castOrNull de esa manera es una cosa malvada. ¿Porqué es eso?

Se me ocurren un par de razones:

  • Es una manera oscura y difícil de hacer algo muy simple. El código oscuro y difícil es difícil de leer, difícil de mantener, una fuente potencial de errores (cuando alguien no lo entiende) y por lo tanto malvado.

  • La manera oscura y complicada en que funciona el método castOrNull probablemente no pueda ser optimizada por el compilador JIT. Terminará con al menos 3 llamadas a métodos adicionales, además de un montón de código adicional para hacer la verificación de tipo y emitir reflexivamente. El uso innecesario de la reflexión es malo.

(Por el contrario, la forma más sencilla (con instanceof seguido por un elenco clase) utiliza códigos de bytes específicos para instanceof y la clase de colada. Las secuencias de código de bytes pueden casi seguro que serán optimizados de manera que no hay más de un nulo comprobar y no más de una prueba del tipo del objeto en el código nativo. Este es un patrón común que debería ser fácil de detectar y optimizar para el compilador JIT)

Por supuesto, "mal" es solo otra forma de diciendo que REALMENTE no deberías hacer esto.

Ninguno de sus dos ejemplos adicionales, haga uso de un método castOrNull ya sea necesario o deseable. OMI, la "manera simple" es mejor tanto desde la legibilidad y las perspectivas de rendimiento.

+0

¡Ah, gracias por esa información! ¿Entonces también dirías que no debería usar 'castOrNull'? ¿O cómo valorarías esos contra puntos en contra de mis puntos de ventaja dados? (Porque después de todo, evitar una variable ficticia también puede conducir a un código más legible y más corto.) – Albert

+1

El único punto a favor que puedo discernir es que se evita el uso de una variable temporal en algunos casos. Para mí, ese es un problema puramente cosmético. El uso de un temporal no hace que el código sea menos fácil de leer IMO, y el código más corto tampoco es una ventaja de legibilidad. (Y por cierto, no es una variable "ficticia" porque se usa). –

2

No sé exactamente por qué la persona que dijo que era el mal.Sin embargo, una posibilidad para su razonamiento fue el hecho de que usted estaba atrapando una excepción después en vez de verificar antes de emitir. Esta es una manera de hacer eso.

public static <T> T castOrNull(Object obj, Class<T> clazz) { 
    if (clazz.isAssignableFrom(obj.getClass())) { 
     return clazz.cast(obj); 
    } else { 
     return null; 
    } 
} 
+0

Dudo que lo haya dicho en serio porque pedí un código mejor y el código que publiqué aquí fue su propia sugerencia. – Albert

+0

Una alternativa similar se discutió en los comentarios a la respuesta vinculada, y Tom no la prefirió sobre la variante anterior. –

+0

@ Albert: se perdió eso ... ya que ese es el caso, diría que lo más probable es que sea como dice la respuesta de Péter, una cuestión de diseño subóptimo. Pero, en realidad, para descubrir exactamente por qué cree que es incorrecto hacerlo, tienes que esperar su respuesta (si alguna vez te responde). – Thomas

4

En mi opinión castOrNull no es malo, solo inútil. Parece obsesionado con deshacerse de una variable temporal y una línea de código, mientras que para mí la pregunta más grande es ¿por qué necesita tantos downcasts en su código? En OO, esto es casi siempre un síntoma de diseño subóptimo. Y preferiría resolver la causa raíz en lugar de tratar el síntoma.

+1

Bueno, hay muchas bibliotecas en Java que no diseñamos, pero tenemos que usar. (El iterador mencionado es el único ejemplo) –

+0

@Nikita, es justo, si utiliza una biblioteca de Java mal diseñada pero al mismo tiempo indispensable, apenas tiene opciones. Mi suposición personal es que tales bibliotecas deberían ser muy raras. Si una biblioteca es tan incómoda de usar, entonces nadie la usa, o tarde o temprano alguien comienza a desarrollar una mejor alternativa. –

+0

He extendido mi pregunta un poco para mostrar 2 casos en los que a veces la uso. En realidad, no me gustan las reglas estrictas para ** nunca ** usar algo y creo que mis dos casos provistos son casos válidos en los que puedes hacerlo así. Podrías ser más extremista sobre esto que yo, pero supongo que tampoco serías tan extremo como para nunca usar un código como este. Y luego, si tienes un caso así, siempre me gusta tener la solución más limpia y eso es lo que estoy preguntando aquí. :) – Albert

5

En la mayoría de los códigos Java bien escritos/diseñados, el uso de instanceof y los lanzamientos nunca ocurre. Con la adición de genéricos, no se necesitan muchos casos de yesos (y por lo tanto casos). Lo hacen, en ocasiones aún ocurren.

El método castOrNull es malo ya que hace que el código Java se vea "antinatural". El mayor problema cuando se cambia de un idioma a otro es adoptar las convenciones del nuevo idioma. Las variables temporales están bien en Java. De hecho, todo lo que su método está haciendo es realmente ocultar la variable temporal.

Si observa que está escribiendo muchos moldes, debería examinar su código y ver por qué, y buscar la forma de eliminarlos. Por ejemplo, en el caso de que menciones agregar un método "getNumberOfChildren" te permitiría verificar si un nodo está vacío y así poder podarlo sin lanzarlo (eso es una suposición, podría no funcionar para ti en este caso).

En general, los moldes son "malvados" en Java porque generalmente no son necesarios. Su método es más "malo" porque no está escrito de la manera en que la mayoría de la gente esperaría que se escribiera Java.

Dicho esto, si quieres hacerlo, adelante. En realidad, no es "malo" simplemente no es una forma "correcta" de hacerlo en Java.

0

Las excepciones de Java son lentas. Si intenta optimizar su rendimiento evitando un doble molde, se está disparando en el pie al usar excepciones en lugar de lógicas. Nunca confíe en atrapar una excepción por algo que razonablemente pueda verificar y corregir (exactamente lo que está haciendo).

How slow are Java exceptions?