Quelle est la différence entre instanceof et Class.isAssignableFrom (…)?

458

Lequel des énoncés suivants est le meilleur?

a instanceof B

ou

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

La seule différence que je sache, c'est que lorsque «a» est nul, le premier renvoie faux, tandis que le second lève une exception. A part ça, donnent-ils toujours le même résultat?

Megamug
la source
17
Pour les enregistrements, isInstance () est la méthode la plus pratique pour vérifier si un objet peut être converti en un type de classe (pour plus de détails, voir: tshikatshikaaa.blogspot.nl/2012/07/… )
Jérôme Verstrynge

Réponses:

498

Lors de l'utilisation instanceof, vous devez connaître la classe de Bau moment de la compilation. Lors de son utilisation, isAssignableFrom()il peut être dynamique et changer pendant l'exécution.

Marc Novakowski
la source
12
je ne comprends pas - veuillez expliquer pourquoi nous ne pouvons pas écrire a instanceof Bref.getClass(). comment cela peut-il être la réponse acceptée avec si peu d'explications (ou son absence)?
Eliran Malka
65
La syntaxe a instanceof Brefne l' est pas a instanceof Bref.class. Le deuxième argument de l'opérateur instanceof est un nom de classe, pas une expression se résolvant en une instance d'objet de classe.
Brandon Bloom
2
oui "dynamique" va sans dire :) A part les performances, c'est une vraie différence.
peterk
2
@EliranMalka peut-être que vous pouvez avoir une classe qui a été générée lors de l'exécution. Comme des objets proxy.
Wagner Tsuchiya
Donc, en B.class.isAssignableFrom(a.getClass()), B est connu et a instanceof Best meilleur. Droite?
Florian F
208

instanceofne 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) .

Adam Rosenfield
la source
10
Je ne vois pas l'intérêt d'utiliser instanceof / isAssignableFrom avec des types primitifs.
Jimmy T.
116

Parler en termes de performances:

TL; DR

Utilisez isInstance ou instanceof qui ont des performances similaires. isAssignableFrom est légèrement plus lent.

Trié par performance:

  1. isInstance
  2. instanceof (+ 0,5%)
  3. isAssignableFrom (+ 2,7%)

Basé sur un benchmark de 2000 itérations sur JAVA 8 Windows x64, avec 20 itérations de préchauffage.

En théorie

En utilisant une visionneuse de bytecode douce, 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, nous pouvons nous attendre à ce que instanceof et isInstance soient plus rapides 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 plateforme). Faisons un micro benchmark pour chacun des opérateurs.

La référence

Crédit: Comme conseillé par @ aleksandr-dubinsky, et merci à @yura d'avoir fourni le code de base, voici un benchmark 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

Attention

  • la référence est dépendante de la JVM et de la plateforme. Puisqu'il n'y a pas de différences significatives entre chaque opération, il pourrait être possible d'obtenir un résultat différent (et peut-être un ordre différent!) Sur une version JAVA et / ou des plates-formes différentes comme Solaris, Mac ou Linux.
  • l'indice de référence compare les performances de "est B une instance de A" lorsque "B étend 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 existe un goulot d'étranglement 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, instanceofdans le contexte de votre code pourrait être optimisé plus facilement qu'un isInstancepar 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

Remarque

À l'origine, ce post faisait son propre benchmark en utilisant une boucle for dans JAVA brut, ce qui a donné des résultats peu fiables car une optimisation comme Just In Time peut éliminer la boucle. Il s'agissait donc principalement de mesurer le temps nécessaire au compilateur JIT pour optimiser la boucle: voir Test de performances indépendant du nombre d'itérations pour plus de détails

Questions connexes

JBE
la source
6
Oui, instanceofest un bytecode qui utilise essentiellement la même logique que checkcast(le bytecode derrière le casting). Il sera intrinsèquement plus rapide que les autres options, quel que soit le degré d'optimisation JITC.
Hot Licks
1
Ce qui est logique, tout comme isAssignableFrom()dynamique.
Matthieu
oui, avec JMH les résultats sont complètement différents (même vitesse pour tous).
Yura
Salut, belle référence, vient de tomber dans une situation où isAssignableFrom a été appelé des milliers de fois, changer en instanceof a vraiment fait la différence. Cette réponse vaudrait un billet de blog quelque part ...;)
Martin
33

Un équivalent plus direct a instanceof Best

B.class.isInstance(a)

Cela fonctionne (retourne faux) quand l' aest nullaussi.

user102008
la source
Cool, mais cela ne répond pas à la question et aurait dû être un commentaire.
Madbreaks
23

Outre les différences de base mentionnées ci-dessus, il existe une différence subtile fondamentale entre l'opérateur instanceof et la méthode isAssignableFrom dans Class.

Lire instanceofcomme «est-ce (la partie gauche) l'instance de ceci ou n'importe quelle sous-classe de ceci (la partie droite)» et lire x.getClass().isAssignableFrom(Y.class)comme «Puis-je écrire X x = new Y()». En d'autres termes, l'opérateur instanceof vérifie si l'objet gauche est le même ou la sous-classe de la classe droite, tandis qu'il isAssignableFromvérifie si nous pouvons affecter l'objet de la classe de paramètres (from) à la référence de la classe sur laquelle la méthode est appelée.
Notez que 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 s'é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.
Ashish Arya
la source
3
b instanceof Aest équivalent à A.class.isAssignableFrom(b.getClass())(comme l'OP l'a remarqué). Votre exemple est correct mais non pertinent.
Karu
Puisque new Y()peut ne pas être légal si Yest abstrait ou sans constructeur par défaut public, vous pouvez dire X x = (Y)nullest légal si et seulement si x.getClass().isAssignableFrom(Y.class)est vrai.
Earth Engine
2
Pourquoi 'b.getClass (). IsAssignableFrom (A.class)' dans cet exemple? Je suppose que l'exemple devrait être inversé A.class.isAssignableFrom (b.getClass ()).
loshad vtapkah
14

Il y a aussi une autre différence:

null instanceof X est falsequel que soit X est

null.getClass (). isAssignableFrom (X) lèvera une NullPointerException

S. Ali Tokmen
la source
4
-1, incorrect: null instanceof X(où X est une classe connue au moment de la compilation) retournera toujours false.
Caspar
4
@Caspar alors que vous avez raison, l'idée de base était un bon point. J'ai édité le post pour qu'il soit correct.
erickson
1
c'est utile, le cas de bord est toujours important :).
billions
Pour être équivalente à la première ligne, la deuxième ligne devrait l'être X.class.isAssignableFrom(null.getClass())non? Mais oui, l'appel getClass()à une référence nulle se traduira par NPE.
William Price
Cette réponse manque le point - une déréférence nulle n'est pas pertinente car l'échec se produit en dehors de l'opération (vous devez toujours vérifier la valeur null avant d'utiliser une référence comme celle-ci). En général getClass(), ne doit pas être utilisé avec isAssignableFromen premier lieu - l'opération est destinée à la situation sans objets. Si vous avez la référence de l' objet a, l' utilisation a instanceof SomeClass(si vous faites connaître le type SomeClass) ou someObject.getClass().isInstance(a)(si vous ne connaissez le type de someObject).
AndrewF
12

Il y a encore une autre différence. Si le type (classe) à tester est dynamique, par exemple 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.

tkalmijn
la source
3
en fait, aucune réponse n'est vraiment correcte isAssignableFrom work w / classes, Class.isInstance est l'analogue de 'instanceof'
bestsss
Pour mettre le commentaire correct de @ bestsss dans du code concret: Parce que vous avez un objet ( this), ce clazz.isInstance(this)serait mieux dans votre exemple.
AndrewF
7

Ce fil m'a donné un aperçu de la instanceofdifférence isAssignableFrom, j'ai donc pensé partager quelque chose de moi.

J'ai trouvé que le fait isAssignableFromd'être le seul (probablement pas le seul, mais peut-être le plus simple) à se demander si une référence d'une classe peut prendre des instances d'une autre, quand on a des instances d'aucune classe pour faire la comparaison.

Par conséquent, je n'ai pas trouvé instanceof opérateur pour comparer l'assignation était une bonne idée quand tout ce que j'avais était des classes, à moins que j'envisage de créer une instance à partir d'une des classes; Je pensais que ce serait bâclé.

Owen
la source
5

instanceof ne peut pas non plus être utilisé avec des types primitifs ou 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: Impossible d'effectuer une vérification instanceof par rapport au paramètre de type T. Utilisez plutôt son objet d'effacement car d'autres informations de type génériques seront effacées lors de l'exécution.

Ne compile pas en raison de l'effacement de type supprimant la référence d'exécution. Cependant, le code ci-dessous compilera:

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

Considérez la situation suivante. Supposons que vous vouliez vérifier si le type A est une super classe du type d'obj, vous pouvez aller soit

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

OU

... obj instanceof 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, la solution instanceof fonctionnerait toujours.

algèbre
la source
2
Ce n'est pas vrai. Veuillez voir le commentaire "Adam Rosenfield" stackoverflow.com/questions/496928/…
Maxim Veksler
1
Pourriez-vous élaborer "Ce n'est pas vrai"? Le commentaire auquel vous faites référence n'a rien à voir avec le scénario de mon article. J'ai un code de test qui confirme mon explication.
algèbre
Si vous avez une référence non nulle à une instance d'objet ( objdans cet exemple) de n'importe quel type, vous pouvez appeler la getClass()méthode publique dessus pour obtenir les métadonnées de réflexion de la classe d'implémentation. Cela est vrai même si ce type de classe d'implémentation n'est pas légalement visible à cet emplacement au moment de la compilation. Il est OK lors de l' exécution parce que, pour vous de tenir la objréférence, un certain chemin de code qui a finalement fait avoir un accès légal à la classe créée et a donné une (fuite?) Pour vous.
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))

Le pseudo-code ci-dessus est une définition de, si les références de type / classe A sont attribuables à partir de références de type / classe B. Il s'agit d'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 le trouverait utile. Ce n'est qu'une tentative pour saisir ma compréhension, ce n'est pas la définition officielle. Il est utilisé dans une certaine implémentation de machine virtuelle Java et fonctionne pour de nombreux exemples de programmes, donc même si je ne peux pas garantir qu'il capture tous les aspects de isAssignableFrom, il n'est pas complètement désactivé.

Stephan Korsholm
la source
2
Veuillez expliquer ce que fait ce code et comment il répond à la question.
Fund Monica's Lawsuit
0

Parler en termes de performances "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 loin (+ 0.9% de temps d'exécution). Donc pas de vraie différence quoi que vous choisissiez

Yura
la source
0

Que diriez-vous de quelques exemples pour le montrer en action ...

@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
la source
-2

certains tests que nous avons faits dans notre équipe montrent que cela A.class.isAssignableFrom(B.getClass())fonctionne plus vite que B instanceof A. cela peut être très utile si vous devez vérifier cela sur un grand nombre d'éléments.

Milan
la source
13
Hm, si vous avez un goulot d'étranglement instanceof, je crois que vous avez de sérieux problèmes de conception ...
sleske
1
La réponse de JBE présente une hypothèse qui diffère de votre hypothèse.
Alastor Moody