Lequel des suivants est le meilleur?
a instanceof B
ou
B.class.isAssignableFrom(a.getClass())
La seule différence que je connaisse est que, lorsque "a" est nul, le premier renvoie false, tandis que le second génère une exception. En dehors de cela, donnent-ils toujours le même résultat?
Lorsque vous utilisez instanceof
, vous devez connaître la classe de B
au moment de la compilation. Lorsque vous utilisez isAssignableFrom()
, il peut être dynamique et changer en cours d'exécution.
instanceof
ne peut être utilisé qu'avec des types de référence, pas des types primitifs. isAssignableFrom()
peut être utilisé avec n'importe quel objet de classe:
a instanceof int // syntax error
3 instanceof Foo // syntax error
int.class.isAssignableFrom(int.class) // true
Voir http://Java.Sun.com/javase/6/docs/api/Java/lang/Class.html#isAssignableFrom (Java.lang.Class) .
Parler en termes de performance:
TL; DR
Utilisez isInstance ou instanceof dont les performances sont similaires. isAssignableFrom est légèrement plus lent.
Trié par performance:
Basé sur un repère de 2000 itérations sur Java 8 Windows x64, avec 20 itérations d’échauffement.
En théorie
En utilisant un logiciel tel que visualiseur de 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 pourrait s’attendre à une instance de et à 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.
Le point de repère
Crédit: Comme conseillé par @ aleksandr-dubinsky, et merci à @yura pour avoir fourni le code de base, voici un repère JMH (voir ceci 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 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
instanceof
dans le contexte de votre code pourrait être optimisé plus facilement qu'un 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 on obtient:
Remarque
A l'origine, ce billet faisait son propre test en utilisant une boucle for en Java brut, ce qui donnait des résultats peu fiables, car une optimisation comme Just In Time peut éliminer la boucle. Donc, il s'agissait principalement de mesurer le temps pris par le compilateur JIT pour optimiser la boucle: voir Test de performance indépendant du nombre d'itérations pour plus de détails
Questions connexes
Un équivalent plus direct de a instanceof B
est
B.class.isInstance(a)
Cela fonctionne (renvoie false) lorsque a
est aussi null
.
Outre les différences fondamentales mentionnées ci-dessus, il existe une différence subtile fondamentale entre instance de l'opérateur et la méthode isAssignableFrom dans la classe.
Lisez instanceof
comme “est-ce (la partie gauche) l'instance de ceci ou de l'une de ses sous-classes (la partie droite)” et lisez x.getClass().isAssignableFrom(Y.class)
comme “Puis-je écrire X x = new Y()
”. En d'autres termes, l'opérateur d'instanceof vérifie si l'objet de gauche est la même ou une sous-classe de la classe de droite, tandis que isAssignableFrom
vérifie si nous pouvons affecter un objet du paramètre class (from) à la référence de la classe sur laquelle la méthode est appelée .
Notez que tous les deux considèrent l'instance réelle et non le type de référence.
Prenons un exemple de 3 classes A, B et C où C étend B et B s'étend 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.
Il y a aussi une autre différence:
instance nulle de X est false
peu importe ce que X est
null.getClass (). isAssignableFrom (X) lève une exception NullPointerException
Il y a encore une autre différence. Si le type (Classe) à tester est dynamique, par ex. passé en tant que paramètre de méthode, alors instanceof ne le coupera pas pour vous.
boolean test(Class clazz) {
return (this instanceof clazz); // clazz cannot be resolved to a type.
}
mais vous pouvez faire:
boolean test(Class clazz) {
return (clazz.isAssignableFrom(this.getClass())); // okidoki
}
Oups, je vois que cette réponse est déjà couverte. Peut-être que cet exemple est utile à quelqu'un.
Ce fil de discussion m'a permis de mieux comprendre la différence entre instanceof
et isAssignableFrom
. J'ai donc pensé partager quelque chose de mon cru.
J'ai trouvé qu'utiliser isAssignableFrom
était le seul (probablement pas le seul, mais probablement le plus facile) moyen de se demander si une référence d'une classe peut prendre des instances d'une autre, quand on a des instances de aucune classe à faire la comparaison.
Par conséquent, je n'ai pas trouvé que l'utilisation de l'opérateur instanceof
pour comparer l'assignabilité était une bonne idée lorsque je n'avais que des classes, à moins que je n'envisage de créer une instance à partir de l'une des classes; Je pensais que ce serait bâclé.
Considérez la situation suivante. Supposons que vous souhaitiez vérifier si le type A est une super classe du type obj, vous pouvez aller soit
... A.class.isAssignableFrom (obj.getClass ()) ...
OR
... exemple de A ...
Mais la solution isAssignableFrom nécessite que le type d'obj soit visible ici. Si ce n'est pas le cas (par exemple, le type d'obj peut être d'une classe interne privée), cette option est désactivée. Cependant, l'instance de solution fonctionnerait toujours.
instanceof ne peut pas non plus être utilisé avec des types primitifs ou des types génériques. Comme dans le code suivant:
//Define Class< T > type ...
Object e = new Object();
if(e instanceof T) {
// Do something.
}
L'erreur est la suivante: Impossible d'effectuer une vérification par rapport au paramètre de type T. Utilisez plutôt l'objet d'effacement car d'autres informations de type génériques seront effacées lors de l'exécution.
Ne compile pas car effacement du type supprime la référence d'exécution. Cependant, le code ci-dessous va compiler:
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))
Le pseudo-code ci-dessus est une définition de, si les références de type/classe A sont assignables à partir de références de type/classe B. C'est une définition récursive. Pour certains, cela peut être utile, pour d'autres, cela peut être déroutant. Je l'ajoute au cas où quelqu'un trouverait cela utile. Ceci est juste une tentative de capturer ma compréhension, ce n'est pas la définition officielle. Il est utilisé dans une certaine implémentation Java VM et fonctionne pour de nombreux exemples de programmes. Par conséquent, même si je ne peux pas garantir qu'il capture tous les aspects d'isAssignableFrom, il n'est pas complètement désactivé.
Parler en termes de performance "2" (avec 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();
}
}
Il donne:
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
Pour que nous puissions conclure: instanceof aussi vite que isInstance () et isAssignableFrom () pas très loin (+ 0,9% du temps d'exécution). Donc, pas de réelle différence, quel que soit votre choix