getclass - reflection java 8
¿Cuál es la diferencia entre instanceof y Class.isAssignableFrom(...)? (13)
¿Cuál de los siguientes es mejor?
a instanceof B
o
B.class.isAssignableFrom(a.getClass())
La única diferencia que conozco es que cuando ''a'' es nulo, el primero devuelve falso, mientras que el segundo arroja una excepción. Aparte de eso, ¿siempre dan el mismo resultado?
Algunas pruebas que hicimos en nuestro equipo muestran que A.class.isAssignableFrom(B.getClass())
funciona más rápido que B instanceof A
Esto puede ser muy útil si necesita verificar esto en una gran cantidad de elementos.
Aparte de las diferencias básicas mencionadas anteriormente, existe una diferencia sutil entre el operador de instancia de instancia y el método Asignable Desde la Clase.
Lea instanceof
como "es esta (la parte izquierda) la instancia de esta o cualquier subclase de esta (la parte derecha)" y lea x.getClass().isAssignableFrom(Y.class)
como "¿Puedo escribir X x = new Y()
". En otras palabras, instanceof operator comprueba si el objeto izquierdo es el mismo o la subclase de la clase correcta, mientras que isAssignableFrom
verifica si podemos asignar el objeto de la clase de parámetro (from) a la referencia de la clase en la que se llama al método.
Tenga en cuenta que ambos consideran que la instancia real no es el tipo de referencia.
Considere un ejemplo de 3 clases A, B y C, donde C se extiende B y B se extiende A.
B b = new C();
System.out.println(b instanceof A); //is b (which is actually class C object) instance of A, yes. This will return true.
System.out.println(b instanceof B); // is b (which is actually class C object) instance of B, yes. This will return true.
System.out.println(b instanceof C); // is b (which is actually class C object) instance of C, yes. This will return true. If the first statement would be B b = new B(), this would have been false.
System.out.println(b.getClass().isAssignableFrom(A.class));//Can I write C c = new A(), no. So this is false.
System.out.println(b.getClass().isAssignableFrom(B.class)); //Can I write C c = new B(), no. So this is false.
System.out.println(b.getClass().isAssignableFrom(C.class)); //Can I write C c = new C(), Yes. So this is true.
Considere la siguiente situación. Supongamos que desea comprobar si el tipo A es una superclase del tipo de obj, puede ir
... A.class.isAssignableFrom (obj.getClass ()) ...
O
... obj instanceof A ...
Pero la solución isAssignableFrom requiere que el tipo de objeto sea visible aquí. Si este no es el caso (por ejemplo, el tipo de obj podría ser de una clase interna privada), esta opción está desactivada. Sin embargo, la solución de ejemplo siempre funcionaría.
Cuando use instanceof
, necesita saber la clase de B
en tiempo de compilación. Cuando se utiliza isAssignableFrom()
, puede ser dinámico y cambiar durante el tiempo de ejecución.
Este hilo me dio una idea de cómo difería la instanceof
de isAssignableFrom
, así que pensé que compartiría algo propio.
Descubrí que usar isAssignableFrom
es la única (probablemente no la única, pero posiblemente la forma más fácil) de preguntarse si una referencia de una clase puede tomar instancias de otra, cuando una no tiene ninguna clase para hacer la comparación.
Por lo tanto, no encontré que usar el operador instanceof
para comparar la asignabilidad fuera una buena idea cuando todo lo que tenía eran clases, a menos que contemplara crear una instancia de una de las clases; Pensé que esto sería descuidado.
Hablando en términos de rendimiento "2" (con JMH):
class A{}
class B extends A{}
public class InstanceOfTest {
public static final Object a = new A();
public static final Object b = new B();
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public boolean testInstanceOf()
{
return b instanceof A;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public boolean testIsInstance()
{
return A.class.isInstance(b);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public boolean testIsAssignableFrom()
{
return A.class.isAssignableFrom(b.getClass());
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(InstanceOfTest.class.getSimpleName())
.warmupIterations(5)
.measurementIterations(5)
.forks(1)
.build();
new Runner(opt).run();
}
}
Da:
Benchmark Mode Cnt Score Error Units
InstanceOfTest.testInstanceOf avgt 5 1,972 ? 0,002 ns/op
InstanceOfTest.testIsAssignableFrom avgt 5 1,991 ? 0,004 ns/op
InstanceOfTest.testIsInstance avgt 5 1,972 ? 0,003 ns/op
Para que podamos concluir: instanceof tan rápido como isInstance () y isAssignableFrom () no muy lejos (+ 0.9% tiempo de ejecución). Así que no hay diferencia real lo que elijas.
Hablando en términos de rendimiento:
TL; DR
Utilice isInstance o instanceof que tienen un rendimiento similar. isAssignableFrom es ligeramente más lento.
Ordenado por rendimiento:
- isInstance
- instanceof (+ 0.5%)
- isAssignableFrom (+ 2.7%)
Basado en una referencia de 2000 iteraciones en JAVA 8 Windows x64, con 20 iteraciones de calentamiento.
En teoria
Usando un visor de código de bytes suave, podemos traducir cada operador en código de bytes.
En el contexto de:
package foo;
public class Benchmark
{
public static final Object a = new A();
public static final Object b = new B();
...
}
JAVA:
b instanceof A;
Bytecode:
getstatic foo/Benchmark.b:java.lang.Object
instanceof foo/A
JAVA:
A.class.isInstance(b);
Bytecode:
ldc Lfoo/A; (org.objectweb.asm.Type)
getstatic foo/Benchmark.b:java.lang.Object
invokevirtual java/lang/Class isInstance((Ljava/lang/Object;)Z);
JAVA:
A.class.isAssignableFrom(b.getClass());
Bytecode:
ldc Lfoo/A; (org.objectweb.asm.Type)
getstatic foo/Benchmark.b:java.lang.Object
invokevirtual java/lang/Object getClass(()Ljava/lang/Class;);
invokevirtual java/lang/Class isAssignableFrom((Ljava/lang/Class;)Z);
Al medir la cantidad de instrucciones de bytecode que usa cada operador, podemos esperar que instanceof y isInstance sean más rápidas que isAssignableFrom . Sin embargo, el rendimiento real NO está determinado por el código de bytes, sino por el código de la máquina (que depende de la plataforma). Hagamos un micro benchmark para cada uno de los operadores.
El punto de referencia
Crédito: según lo aconsejado por @ aleksandr-dubinsky, y gracias a @yura por proporcionar el código base, aquí hay un punto de referencia de JMH (consulte esta guía de ajuste ):
class A {}
class B extends A {}
public class Benchmark {
public static final Object a = new A();
public static final Object b = new B();
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testInstanceOf()
{
return b instanceof A;
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testIsInstance()
{
return A.class.isInstance(b);
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testIsAssignableFrom()
{
return A.class.isAssignableFrom(b.getClass());
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(TestPerf2.class.getSimpleName())
.warmupIterations(20)
.measurementIterations(2000)
.forks(1)
.build();
new Runner(opt).run();
}
}
Obtuvo los siguientes resultados (la puntuación es una serie de operaciones en una unidad de tiempo , por lo que cuanto mayor sea la puntuación, mejor):
Benchmark Mode Cnt Score Error Units
Benchmark.testIsInstance thrpt 2000 373,061 ± 0,115 ops/us
Benchmark.testInstanceOf thrpt 2000 371,047 ± 0,131 ops/us
Benchmark.testIsAssignableFrom thrpt 2000 363,648 ± 0,289 ops/us
Advertencia
- El punto de referencia es JVM y la plataforma depende. Dado que no hay diferencias significativas entre cada operación, podría ser posible obtener un resultado diferente (¡y quizás un orden diferente!) En una versión diferente de JAVA y / o plataformas como Solaris, Mac o Linux.
- el punto de referencia compara el rendimiento de "es B una instancia de A" cuando "B extiende A" directamente. Si la jerarquía de clases es más profunda y compleja (como B extiende X y extiende Y extiende Z y A), los resultados podrían ser diferentes.
- por lo general, se recomienda escribir el código primero seleccionando uno de los operadores (el más conveniente) y luego perfilar su código para verificar si hay un cuello de botella en el rendimiento. Tal vez este operador sea insignificante en el contexto de su código, o tal vez ...
- en relación con el punto anterior, la
instanceof
en el contexto de su código podría optimizarse más fácilmente que unaisInstance
de ejemplo, por ejemplo ...
Para darle un ejemplo, tome el siguiente bucle:
class A{}
class B extends A{}
A b = new B();
boolean execute(){
return A.class.isAssignableFrom(b.getClass());
// return A.class.isInstance(b);
// return b instanceof A;
}
// Warmup the code
for (int i = 0; i < 100; ++i)
execute();
// Time it
int count = 100000;
final long start = System.nanoTime();
for(int i=0; i<count; i++){
execute();
}
final long elapsed = System.nanoTime() - start;
Gracias al JIT, el código está optimizado en algún momento y obtenemos:
- instanceof: 6ms
- IsInstance: 12ms
- isAssignableFrom: 15ms
Nota
Originalmente, esta publicación estaba haciendo su propio punto de referencia utilizando un bucle for en JAVA sin procesar, lo que dio resultados poco confiables, ya que una optimización como Just In Time puede eliminar el bucle. Por lo tanto, fue principalmente la medición de cuánto tiempo tomó el compilador JIT para optimizar el bucle: consulte la Prueba de rendimiento independiente del número de iteraciones para obtener más detalles
Preguntas relacionadas
Hay otra diferencia más. Si el tipo (Clase) con el que se realiza la prueba es dinámico, por ejemplo, se pasa como un parámetro del método, entonces instanceof no lo cortará por usted.
boolean test(Class clazz) {
return (this instanceof clazz); // clazz cannot be resolved to a type.
}
pero puedes hacer
boolean test(Class clazz) {
return (clazz.isAssignableFrom(this.getClass())); // okidoki
}
Vaya, veo que esta respuesta ya está cubierta. Tal vez este ejemplo sea de ayuda para alguien.
También hay otra diferencia:
nulo instanceof X es false
no importa lo que X sea
null.getClass (). isAssignableFrom (X) lanzará una NullPointerException
Un equivalente más directo a a instanceof B
es
B.class.isInstance(a)
Esto funciona (devuelve falso) cuando a
es null
también.
instanceof tampoco puede usarse con tipos primitivos o tipos genéricos. Como en el siguiente código:
//Define Class< T > type ...
Object e = new Object();
if(e instanceof T) {
// Do something.
}
El error es: no se puede realizar la comprobación de la instancia contra el parámetro de tipo T. En su lugar, se debe borrar el Objeto ya que se borrará más información de tipo genérico en el tiempo de ejecución.
No compila debido al borrado de tipo eliminando la referencia de tiempo de ejecución. Sin embargo, el siguiente código compilará:
if( type.isAssignableFrom(e.getClass())){
// Do something.
}
isAssignableFrom(A, B) =
if (A == B) return true
else if (B == java.lang.Object) return false
else return isAssignableFrom(A, getSuperClass(B))
El pseudo código anterior es una definición de, si las referencias de tipo / clase A son asignables de las referencias de tipo / clase B. Es una definición recursiva. Para algunos puede ser útil, para otros puede ser confuso. Lo agrego en caso de que alguien lo encuentre útil. Esto es solo un intento de capturar mi comprensión, no es la definición oficial. Se usa en cierta implementación de Java VM y funciona para muchos programas de ejemplo, así que aunque no puedo garantizar que capture todos los aspectos de isAssignableFrom, no está completamente apagado.