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

458

¿Cuál de los siguientes es mejor?

a instanceof B

o

B.class.isAssignableFrom(a.getClass())

La única diferencia que sé 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?

Megamug
fuente
17
Para los registros, isInstance () es el método más conveniente para verificar si un objeto se puede convertir en un tipo de clase (para más detalles, consulte: tshikatshikaaa.blogspot.nl/2012/07/… )
Jérôme Verstrynge

Respuestas:

498

Cuando se utiliza instanceof, debe conocer la clase de Ben tiempo de compilación. Cuando se usa isAssignableFrom()puede ser dinámico y cambiar durante el tiempo de ejecución.

Marc Novakowski
fuente
12
no lo entiendo, explique por qué no podemos escribir a instanceof Bref.getClass(). ¿Cómo puede ser esta la respuesta aceptada con tan poca explicación (o su falta de ella)?
Eliran Malka
65
La sintaxis a instanceof Brefno es a instanceof Bref.class. El segundo argumento para el operador instanceof es un nombre de clase, no una expresión que se resuelve en una instancia de objeto de clase.
Brandon Bloom
2
sí "dinámico" no hace falta decir :) Aparte del rendimiento, esta es una verdadera diferencia.
Peter
2
@EliranMalka tal vez pueda tener una clase que se generó en tiempo de ejecución. Al igual que los objetos proxy.
Wagner Tsuchiya
Entonces, en B.class.isAssignableFrom(a.getClass()), B es conocido y a instanceof Bes mejor. ¿Derecha?
Florian F
116

Hablando en términos de rendimiento:

TL; DR

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

Ordenado por rendimiento:

  1. isInstance
  2. instancia de (+ 0.5%)
  3. isAssignableFrom (+ 2.7%)

Basado en un punto de 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 cuántas instrucciones de bytecode utiliza cada operador, podríamos esperar que instancia de e isInstance sea ​​más rápido 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();
    }
}

Obtuve los siguientes resultados (el puntaje es una cantidad de operaciones en una unidad de tiempo , por lo tanto, cuanto mayor sea el puntaje, 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 depende de la plataforma. Dado que no hay diferencias significativas entre cada operación, es posible obtener un resultado diferente (¡y tal vez un orden diferente!) En una versión y / o plataformas JAVA diferentes 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 que extiende Y que extiende Z que extiende A), los resultados pueden 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, instanceofen el contexto de su código podría optimizarse más fácilmente que, isInstancepor ejemplo, ...

Para darle un ejemplo, tome el siguiente ciclo:

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 se optimiza en algún momento y obtenemos:

  • instancia de: 6 ms
  • isInstance: 12ms
  • isAssignableFrom: 15ms

Nota

Originalmente, esta publicación estaba haciendo su propio punto de referencia utilizando un bucle for en Java sin procesar, que dio resultados poco confiables ya que algunas optimizaciones como Just In Time pueden eliminar el bucle. Por lo tanto, medía principalmente cuánto tiempo tardó el compilador JIT en optimizar el ciclo: consulte Prueba de rendimiento independiente del número de iteraciones para obtener más detalles

Preguntas relacionadas

JBE
fuente
66
Sí, instanceofes un código de bytes que utiliza esencialmente la misma lógica que checkcast(el código de bytes detrás de la transmisión). Inherentemente será más rápido que las otras opciones, independientemente del grado de optimización de JITC.
Hot Licks
1
Lo cual tiene sentido, ya que isAssignableFrom()es dinámico.
Matthieu
sí, con los resultados de JMH son completamente diferentes (la misma velocidad para todos).
Yura
Hola, buen punto de referencia, acabo de encontrarme con una situación en la que isAssignableFrom se llamó miles de veces, y cambiar a instanciaof realmente marcó la diferencia. Esta respuesta valdría la pena una publicación de blog en algún lugar ...;)
Martin
33

Un equivalente más directo a a instanceof Bes

B.class.isInstance(a)

Esto funciona (devuelve falso) cuando aes nulldemasiado.

usuario102008
fuente
Genial, pero esto no responde la pregunta y debería haber sido un comentario.
Madbreaks
23

Además de las diferencias básicas mencionadas anteriormente, existe una diferencia sutil central entre la instancia del operador y el método AsignableFrom en la clase.

Lea instanceofcomo “es esta (la parte izquierda) la instancia de esta o cualquier subclase de esto (la parte derecha)” y lea x.getClass().isAssignableFrom(Y.class)como “¿Puedo escribir?X x = new Y() ". En otras palabras, el operador instanceof verifica si el objeto izquierdo es el mismo o la subclase de la clase derecha, mientras que isAssignableFromverifica si podemos asignar el objeto de la clase de parámetro (from) a la referencia de la clase en la que se llama el método.
Tenga en cuenta que ambos consideran la instancia real, no 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.
Ashish Arya
fuente
3
b instanceof Aes equivalente a A.class.isAssignableFrom(b.getClass())(como notó el OP). Su ejemplo es correcto pero irrelevante.
Karu
Como new Y()puede no ser legal si Yes abstracto o sin constructor público predeterminado, puede decir que X x = (Y)nulles legal si y solo si x.getClass().isAssignableFrom(Y.class)es verdadero.
Earth Engine
2
¿Por qué 'b.getClass (). IsAssignableFrom (A.class)' en este ejemplo? Supongo que el ejemplo debería ser inverso A.class.isAssignableFrom (b.getClass ()).
loshad vtapkah
14

También hay otra diferencia:

instancia nula de X es false no importa lo que sea X

null.getClass (). isAssignableFrom (X) arrojará una NullPointerException

S. Ali Tokmen
fuente
44
-1, incorrecto: null instanceof X(donde X es una clase conocida en tiempo de compilación) siempre volverá false.
Caspar
44
@Caspar mientras tengas razón, la idea básica era un buen punto. Edité la publicación para que sea correcta.
erickson el
1
Esto es útil, el caso límite siempre es importante :).
billones de
Para ser equivalente a la primera línea, la segunda línea debería ser, X.class.isAssignableFrom(null.getClass())¿no? Pero sí, solicitar getClass()una referencia nula dará como resultado NPE.
William Price
Esta respuesta pierde el punto: una desreferencia nula no es relevante porque la falla ocurre fuera de la operación (siempre debe verificar si hay nula antes de usar una referencia como esa). En general getClass(), no se debe usar isAssignableFromen primer lugar: la operación está destinada a la situación de no tener objetos. Si usted tiene la referencia de objeto a, el uso a instanceof SomeClass(si lo hace saber el tipo SomeClass) o someObject.getClass().isInstance(a)(si no conoce el tipo de someObject).
AndrewF
12

Hay otra diferencia más. Si el tipo (Clase) para probar es dinámico, por ejemplo, se pasa como un parámetro de 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. Quizás este ejemplo sea útil para alguien.

tkalmijn
fuente
3
En realidad, ninguna respuesta es realmente correcta isAssignableDe trabajo con clases, Class.isInstance es el análogo de 'instanceof'
bestsss
Para poner el comentario correcto de @ bestsss en código concreto: Debido a que tiene un objeto ( this), clazz.isInstance(this)sería mejor en su ejemplo.
AndrewF
7

Este hilo me dio una idea de cómo instanceofdifería isAssignableFrom, así que pensé en compartir algo propio.

He encontrado que usando isAssignableFrom es la única forma (probablemente no la única, pero posiblemente la más fácil) de preguntarse si una referencia de una clase puede tomar instancias de otra, cuando uno tiene instancias de ninguna clase para hacer la comparación.

Por lo tanto, no encontré que usar el instanceofoperador 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.

Owen
fuente
5

instanceof tampoco se puede usar con tipos primitivos o 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 una instancia de comprobación con el parámetro de tipo T. Utilice en su lugar el objeto de borrado, ya que se borrará más información de tipo genérico en tiempo de ejecución.

No se compila debido a que la eliminación de tipo elimina la referencia de tiempo de ejecución. Sin embargo, el siguiente código compilará:

if( type.isAssignableFrom(e.getClass())){
  // Do something.
}
James Drinkard
fuente
4

Considere la siguiente situación. Supongamos que desea verificar si el tipo A es una superclase del tipo de obj, puede ir

... A.class.isAssignableFrom (obj.getClass ()) ...

O

... obj instancia de A ...

Pero la solución isAssignableFrom requiere que el tipo de obj sea visible aquí. Si este no es el caso (p. Ej., El tipo de obj podría ser de una clase interna privada), esta opción está desactivada. Sin embargo, la instancia de solución siempre funcionaría.

álgebra
fuente
2
Eso no es verdad. Vea el comentario de "Adam Rosenfield" stackoverflow.com/questions/496928/…
Maxim Veksler
1
¿Podrías elaborar "Eso no es verdad"? El comentario al que hace referencia no tiene nada que ver con el escenario en mi publicación. Tengo un código de prueba que respalda mi explicación.
álgebra
Si tiene una referencia no nula a una instancia de objeto ( objen este ejemplo) de cualquier tipo , puede llamar al getClass()método público para obtener los metadatos de reflexión para la clase implementadora. Esto es cierto incluso si ese tipo de clase de implementación no sería legalmente visible en esa ubicación en el momento de la compilación. OK Es en tiempo de ejecución, ya que, para que usted mantenga la objreferencia, alguna ruta de código que finalmente hizo tener acceso legal a la clase creó uno y dio (filtrado?) A usted.
William Price
0
isAssignableFrom(A, B) =

if (A == B) return true
else if (B == java.lang.Object) return false
else return isAssignableFrom(A, getSuperClass(B))

El pseudocódigo anterior es una definición de, si las referencias de tipo / clase A son asignables a partir de 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 utiliza en una determinada implementación de Java VM y funciona para muchos programas de ejemplo, por lo que, aunque no puedo garantizar que capture todos los aspectos de isAssignableFrom, no está completamente desactivado.

Stephan Korsholm
fuente
2
Explique qué hace este código y cómo responde la pregunta.
Fund Monica's Lawsuit
0

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: instancia de tan rápido como isInstance () y isAssignableFrom () no muy lejos (+ 0.9% en tiempo de ejecución). Así que no hay diferencia real sea lo que sea que elijas

Yura
fuente
0

¿Qué tal algunos ejemplos para mostrarlo en acción ...

@Test
public void isInstanceOf() {
    Exception anEx1 = new Exception("ex");
    Exception anEx2 = new RuntimeException("ex");
    RuntimeException anEx3 = new RuntimeException("ex");

    //Base case, handles inheritance
    Assert.assertTrue(anEx1 instanceof Exception);
    Assert.assertTrue(anEx2 instanceof Exception);
    Assert.assertTrue(anEx3 instanceof Exception);

    //Other cases
    Assert.assertFalse(anEx1 instanceof RuntimeException);
    Assert.assertTrue(anEx2 instanceof RuntimeException);
    Assert.assertTrue(anEx3 instanceof RuntimeException);
}

@Test
public void isAssignableFrom() {
    Exception anEx1 = new Exception("ex");
    Exception anEx2 = new RuntimeException("ex");
    RuntimeException anEx3 = new RuntimeException("ex");

    //Correct usage = The base class goes first
    Assert.assertTrue(Exception.class.isAssignableFrom(anEx1.getClass()));
    Assert.assertTrue(Exception.class.isAssignableFrom(anEx2.getClass()));
    Assert.assertTrue(Exception.class.isAssignableFrom(anEx3.getClass()));

    //Incorrect usage = Method parameter is used in the wrong order
    Assert.assertTrue(anEx1.getClass().isAssignableFrom(Exception.class));
    Assert.assertFalse(anEx2.getClass().isAssignableFrom(Exception.class));
    Assert.assertFalse(anEx3.getClass().isAssignableFrom(Exception.class));
}
Sagan
fuente
-2

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.

Milán
fuente
13
Hm, si usted tiene un cuello de botella en el instanceof, creo que usted tiene serios problemas de diseño ...
sleske
1
La respuesta de JBE presenta una hipótesis que difiere de su hipótesis.
Alastor Moody