method - reflection java stack overflow




¿Cuál es la diferencia entre instanceof y Class.isAssignableFrom(...)? (9)

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.

¿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?


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.

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:

TL; DR

Utilice isInstance o instanceof que tienen un rendimiento similar. isAssignableFrom es ligeramente más lento.

Ordenado por rendimiento:

  1. isInstance
  2. instanceof (+ 0.5%)
  3. 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 una isInstance 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.


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.





reflection