Hablando en términos de rendimiento:
TL; DR
Uso isInstance o instanceof que tienen un rendimiento similar. isAssignableFrom es un poco más lento.
Ordenado por rendimiento:
- isInstance
- instanceof (+ 0,5%)
- isAssignableFrom (+ 2,7%)
Basado en un punto de referencia de 2000 iteraciones en JAVA 8 Windows x64, con 20 iteraciones de calentamiento.
En teoría
El uso de un suave como bytecode viewer podemos traducir cada operador en bytecode.
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);
medición cuántas instrucciones de código de bytes es utilizado por cada operador, se puede esperar instanceof y isInstance a ser más rápido que isAssignableFrom. Sin embargo, el rendimiento real NO está determinado por el bytecode, sino por el código 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 las indicaciones de Aleksandr @-Dubinsky, y gracias a @yura para proporcionar el código base, aquí hay una JMH de referencia (ver este tuning guide):
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();
}
}
Dio 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 dependiente de la plataforma. Como no hay diferencias significativas entre cada operación, es posible obtener un resultado diferente (¡y tal vez diferente!) En una versión y/o plataforma JAVA diferente como Solaris, Mac o Linux.
- el índice 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 más compleja (como B extiende X que se extiende Y que se extiende Z que se extiende A), los resultados pueden ser diferentes.
- generalmente se aconseja 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 de rendimiento. Tal vez este operador es insignificante en el contexto de su código, o tal vez ...
- en relación con el punto anterior,
instanceof
en el contexto de su código podría optimizarse más fácilmente que un isInstance
por ejemplo ...
Para dar un ejemplo, tomar 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 se obtiene:
- instanceof: 6 ms
- isInstance: 12ms
- isAssignableDe: 15ms
Nota
Originalmente este post estaba haciendo su propio punto de referencia usando un bucle de en JAVA prima, que dio resultados poco fiables ya que algunos de optimización como Just In Time puede eliminar el bucle. Por lo que era en su mayoría mide el tiempo que hizo el compilador JIT tomar para optimizar el bucle: ver Performance test independent of the number of iterations para más detalles
preguntas relacionadas
Para los registros, isInstance() es el método más conveniente para verificar si un objeto puede convertirse en un tipo de clase (para obtener más detalles, consulte: http://tshikatshikaaa.blogspot.nl /2012/07/java-instanceof-isassignablefrom-or.html) – JVerstry