web-dev-qa-db-fra.com

Quelle est la difference entre instanceof et Class.isAssignableFrom (...)?

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?

432
Megamug

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.

471
Marc Novakowski

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

201
Adam Rosenfield

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:

  1. isInstance
  2. instance de (+ 0.5%)
  3. isAssignableFrom (+ 2,7%)

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

  • la référence dépend de la machine virtuelle et de la plate-forme. Comme il n'y a pas de différences significatives entre chaque opération, il peut ê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, telles que Solaris, Mac ou Linux.
  • la référence compare la performance 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 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. 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'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:

  • instance de: 6ms
  • isInstance: 12ms
  • isAssignableDe: 15ms

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

107
JBE

Un équivalent plus direct de a instanceof B est

B.class.isInstance(a)

Cela fonctionne (renvoie false) lorsque a est aussi null.

33
user102008

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.
22
Ashish Arya

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

15
S. Ali Tokmen

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.

11
tkalmijn

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é.

7
Owen

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.

4
algebra

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.
}
4
James Drinkard
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é.

0
Stephan Korsholm

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

0
Yura