En termes de performances :
TL;DR
Utilice isInstance o instanceof qui ont des performances similaires. isAssignableFrom est légèrement plus lent.
Classés par performance :
- isInstance
-
instanceof (+ 0.5%)
-
isAssignableFrom (+ 2.7%)
Basé sur un benchmark de 2000 itérations sur JAVA 8 Windows x64, avec 20 itérations de réchauffement.
En théorie
Utilisation d'un produit doux comme visualisation du bytecode nous pouvons traduire chaque opérateur en bytecode.
Dans le contexte 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);
En mesurant le nombre d'instructions de bytecode utilisées par chaque opérateur, on peut s'attendre à ce que instanceof y isInstance pour être plus rapide que isAssignableFrom . Cependant, les performances réelles ne sont PAS déterminées par le bytecode mais par le code machine (qui dépend de la plate-forme). Faisons un micro benchmark pour chacun des opérateurs.
L'indice de référence
Crédit : Comme conseillé par @aleksandr-dubinsky, et grâce à @yura qui a fourni le code de base, voici une JMH (voir ce guide de réglage ):
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();
}
}
A donné les résultats suivants (le score est un nombre d'opérations dans une unité de temps (donc plus le score est élevé, mieux c'est) :
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
Avertissement
- le benchmark est dépendant de la JVM et de la plateforme. Comme il n'y a pas de différences significatives entre chaque opération, il est possible d'obtenir un résultat différent (et peut-être un ordre différent !) sur une version différente de JAVA et/ou sur des plateformes différentes comme Solaris, Mac ou Linux.
- le benchmark compare les performances de "is B an instance of A" quand "B extends A" directement. Si la hiérarchie des classes est plus profonde et plus complexe (comme B étend X qui étend Y qui étend Z qui étend A), les résultats peuvent être différents.
- il est généralement conseillé d'écrire le code en choisissant d'abord l'un des opérateurs (le plus pratique), puis de profiler votre code pour vérifier s'il y a un goulot d'étranglement au niveau des performances. Peut-être que cet opérateur est négligeable dans le contexte de votre code, ou peut-être...
- par rapport au point précédent,
instanceof
dans le contexte de votre code pourrait être optimisé plus facilement qu'une isInstance
par exemple...
Pour vous donner un exemple, prenez la boucle suivante :
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;
Grâce au JIT, le code est optimisé à un moment donné et nous obtenons :
- instanceof : 6ms
- isInstance : 12ms
- isAssignableFrom : 15ms
Note
À l'origine, ce post faisait son propre benchmark en utilisant un pour en JAVA brut, ce qui donnait des résultats peu fiables car certaines optimisations comme Just In Time peuvent éliminer la boucle. Il s'agissait donc surtout de mesurer combien de temps le compilateur JIT mettait à optimiser la boucle : cf. Test de performance indépendant du nombre d'itérations pour plus de détails
Questions connexes
17 votes
Pour les enregistrements, isInstance() est la méthode la plus pratique pour vérifier si un objet peut être casté dans un type de classe (pour plus de détails, voir : tshikatshikaaa.blogspot.nl/2012/07/ )
2 votes
Pour contrer ce que @JérômeVerstrynge a suggéré : La construction instanceof est un moyen privilégié de vérifier si une variable peut être castée vers un certain type de manière statique. car une erreur de compilation se produira en cas de types incompatibles. La méthode isInstance() de java.lang.Class fonctionne différemment et ne vérifie le type qu'au moment de l'exécution. Les types incompatibles ne seront donc pas détectés au début du développement, ce qui peut entraîner un code mort. La méthode isInstance() ne doit être utilisée que dans les cas dynamiques où l'opérateur instanceof ne peut pas être utilisé.