Qual é a diferença entre instanceof e Class.isAssignableFrom (…)?


458

Qual das alternativas a seguir é melhor?

a instanceof B

ou

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

A única diferença que eu sei é que, quando 'a' é nulo, o primeiro retorna falso, enquanto o segundo gera uma exceção. Fora isso, eles sempre dão o mesmo resultado?


17
Para os registros, isinstance () é o método mais conveniente para verificar se um objeto pode ser escalado em um tipo de classe (para mais detalhes, consulte: tshikatshikaaa.blogspot.nl/2012/07/... )
Jérôme Verstrynge

Respostas:


498

Ao usar instanceof, você precisa conhecer a classe Bem tempo de compilação. Ao usá- isAssignableFrom()lo, pode ser dinâmico e mudar durante o tempo de execução.


12
eu não entendo - por favor, explique por que não podemos escrever a instanceof Bref.getClass(). como essa pode ser a resposta aceita com tão pouca explicação (ou a falta dela)?
Eliran Malka 28/03

65
A sintaxe a instanceof Brefnão é a instanceof Bref.class. O segundo argumento para o operador instanceof é um nome de classe, não uma expressão resolvida para uma instância de objeto de classe.
Brandon Bloom

2
sim "dinâmico" é óbvio :) Além do desempenho, essa é uma verdadeira diferença.
Peterk

2
@EliranMalka talvez você possa ter uma classe que foi gerada em tempo de execução. Como objetos de proxies.
Wagner Tsuchiya

Então, em B.class.isAssignableFrom(a.getClass()), B é conhecido e a instanceof Bé melhor. Direita?
Florian F


116

Falando em termos de desempenho:

TL; DR

Use Instância ou instância com desempenho semelhante. isAssignableFrom é um pouco mais lento.

Ordenado por desempenho:

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

Com base em uma referência de 2000 iterações no JAVA 8 Windows x64, com 20 iterações de aquecimento.

Em teoria

Usando um visualizador de código de byte suave , podemos converter cada operador em bytecode.

No 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);

Medindo quantas instruções de código de byte são usadas por cada operador, podemos esperar que instanceof e isInstance sejam mais rápidos que isAssignableFrom . No entanto, o desempenho real NÃO é determinado pelo bytecode, mas pelo código da máquina (que depende da plataforma). Vamos fazer um micro benchmark para cada um dos operadores.

A referência

Crédito: Conforme recomendado por @ aleksandr-dubinsky, e obrigado a @yura por fornecer o código base, aqui está uma referência da JMH (consulte este guia de ajuste ):

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();
    }
}

Forneceu os seguintes resultados (a pontuação é um número de operações em uma unidade de tempo ; portanto, quanto maior a pontuação, melhor):

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

Atenção

  • o benchmark depende da JVM e da plataforma. Como não há diferenças significativas entre cada operação, pode ser possível obter um resultado diferente (e talvez uma ordem diferente!) Em uma versão JAVA diferente e / ou plataformas como Solaris, Mac ou Linux.
  • o benchmark compara o desempenho de "é B uma instância de A" quando "B estende A" diretamente. Se a hierarquia de classes for mais profunda e complexa (como B estende X, que estende Y, que estende Z, que estende A), os resultados podem ser diferentes.
  • geralmente é recomendável escrever o código primeiro escolhendo um dos operadores (o mais conveniente) e depois criar um perfil do código para verificar se há um gargalo de desempenho. Talvez esse operador seja insignificante no contexto do seu código ou talvez ...
  • em relação ao ponto anterior, instanceofno contexto do seu código pode ser otimizado mais facilmente do que um isInstancepor exemplo ...

Para dar um exemplo, faça o seguinte loop:

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;

Graças ao JIT, o código é otimizado em algum momento e obtemos:

  • instanceof: 6ms
  • isInstance: 12ms
  • isAssignableFrom: 15ms

Nota

Originalmente, este post estava fazendo seu próprio benchmark usando um loop for em JAVA bruto, o que forneceu resultados não confiáveis, pois algumas otimizações como Just In Time podem eliminar o loop. Portanto, ele media principalmente quanto tempo o compilador JIT levou para otimizar o loop: consulte Teste de desempenho independente do número de iterações para obter mais detalhes

Perguntas relacionadas


6
Sim, instanceofé um bytecode que usa essencialmente a mesma lógica que checkcast(o bytecode por trás da conversão). É inerentemente mais rápido do que as outras opções, independentemente do grau de otimização do JITC.
Hot Licks

1
O que faz sentido, como isAssignableFrom()é dinâmico.
Matthieu

sim, com os resultados do JMH são completamente diferentes (a mesma velocidade para todos).
Yura

Olá, bom benchmark, acabei de encontrar uma situação em que AssignableFrom foi chamado milhares de vezes, mudar para a instância de realmente fez a diferença. Esta resposta valeria a pena em um blog em algum lugar ...;) #
Martin

33

Um equivalente mais direto a a instanceof Bé

B.class.isInstance(a)

Isso funciona (retorna falso) quando aestá nulltambém.


Legal, mas isso não responde à pergunta e deveria ter sido um comentário.
Madbreaks 18/09/19

23

Além das diferenças básicas mencionadas acima, há uma diferença sutil principal entre instanceof operator e o métodoAssignableFrom em Class.

Leia instanceofcomo "esta (a parte esquerda) é a instância desta ou de qualquer subclasse desta (a parte direita)" e leia x.getClass().isAssignableFrom(Y.class)como "Posso escrever X x = new Y()". Em outras palavras, instanceof operator verifica se o objeto esquerdo é o mesmo ou subclasse da classe direita, enquanto isAssignableFromverifica se podemos atribuir objeto da classe de parâmetro (from) à referência da classe na qual o método é chamado.
Observe que ambos consideram a instância real e não o tipo de referência.

Considere um exemplo de 3 classes A, B e C em que C estende B e B estende 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.

3
b instanceof Aé equivalente a A.class.isAssignableFrom(b.getClass())(como o OP notou). Seu exemplo é correto, mas irrelevante.
Karu

Como new Y()pode não ser legal se Yfor abstrato ou sem construtor público padrão, você pode dizer que X x = (Y)nullé legal se e somente se x.getClass().isAssignableFrom(Y.class)for verdadeiro.
Earth Engine

2
Por que 'b.getClass (). IsAssignableFrom (A.class)' neste exemplo? Eu acho que o exemplo deve ser reverso A.class.isAssignableFrom (b.getClass ()).
Loshad vtapkah

14

Há também outra diferença:

instância nula de X falsenão importa o que seja X

null.getClass (). isAssignableFrom (X) lançará uma NullPointerException


4
-1, incorreto: null instanceof X(onde X é alguma classe conhecida em tempo de compilação) sempre retornará false.
Caspar

4
@ Caspar, enquanto você estiver correto, a idéia básica foi um bom argumento. Eu editei a postagem para que ela esteja correta.
Erickson

1
isso é útil, o caso da borda é sempre importante :).
trilhões

Para ser equivalente à primeira linha, a segunda linha deve ser X.class.isAssignableFrom(null.getClass()), não deveria? Mas sim, chamar getClass()uma referência nula resultará em NPE.
William Price

Esta resposta está errada - uma desreferência nula não é relevante porque a falha ocorre fora da operação (você sempre precisa verificar se é nulo antes de usar uma referência como essa). Em geral getClass(), não deve ser usado isAssignableFromem primeiro lugar - a operação é destinada à situação de não ter objetos. Se você tem a referência do objeto a, o uso a instanceof SomeClass(se fazer conhecer o tipo SomeClass) ou someObject.getClass().isInstance(a)(se você não sabe o tipo de someObject).
AndrewF

12

Há ainda outra diferença. Se o tipo (Classe) a ser testado for dinâmico, por exemplo, passado como um parâmetro do método, instanceof não o cortará para você.

boolean test(Class clazz) {
   return (this instanceof clazz); // clazz cannot be resolved to a type.
}

mas você pode fazer:

boolean test(Class clazz) {
   return (clazz.isAssignableFrom(this.getClass())); // okidoki
}

Ops, vejo que esta resposta já está coberta. Talvez este exemplo seja útil para alguém.


3
na verdade, nenhuma resposta está realmente correta isAssignableFrom trabalho com classes, Class.isInstance é o análogo de 'instanceof'
bestsss

Para colocar o comentário correto de @ bestsss em código concreto: Como você tem um objeto ( this), clazz.isInstance(this)seria melhor no seu exemplo.
AndrewF

7

Esse tópico me deu uma ideia de como era instanceofdiferente isAssignableFrom, então pensei em compartilhar algo de minha autoria.

Eu descobri que usando isAssignableFrom a única (provavelmente não a única, mas possivelmente a mais fácil) maneira de perguntar a si mesmo se uma referência de uma classe pode ter instâncias de outra, quando alguém tem instâncias de nenhuma das classes para fazer a comparação.

Portanto, não achei que usar o instanceofoperador para comparar atribuibilidade fosse uma boa idéia quando tudo o que eu tinha eram classes, a menos que eu pensasse em criar uma instância a partir de uma das classes; Eu pensei que isso seria desleixado.


5

instanceof também não pode ser usado com tipos primitivos ou genéricos. Como no código a seguir:

//Define Class< T > type ... 

Object e = new Object();

if(e instanceof T) {
  // Do something.
}

O erro é: Não é possível executar a verificação da instância no parâmetro T. Use o objeto apagado, pois outras informações genéricas do tipo serão apagadas no tempo de execução.

Não é compilado devido ao apagamento do tipo, removendo a referência de tempo de execução. No entanto, o código abaixo será compilado:

if( type.isAssignableFrom(e.getClass())){
  // Do something.
}

4

Considere a seguinte situação. Suponha que você queira verificar se o tipo A é uma superclasse do tipo de obj, você pode ir

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

OU

... obj instance de A ...

Mas a solução isAssignableFrom exige que o tipo de obj seja visível aqui. Se esse não for o caso (por exemplo, o tipo de obj pode ser de uma classe interna privada), essa opção está desativada. No entanto, a instância da solução sempre funcionaria.


2
Isso não é verdade. Por favor, veja o comentário de "Adam Rosenfield" stackoverflow.com/questions/496928/…
Maxim Veksler

1
Você poderia elaborar "Isso não é verdade"? O comentário a que você se refere não tem nada a ver com o cenário no meu post. Eu tenho algum código de teste que faz backup da minha explicação.
álgebra

Se você tiver uma referência não nula a uma instância de objeto ( objneste exemplo) de qualquer tipo , poderá chamar o getClass()método público para obter os metadados de reflexão para a classe de implementação. Isso é verdade mesmo se esse tipo de classe de implementação não estivesse legalmente visível nesse local no momento da compilação. Está tudo bem no tempo de execução porque, para você manter a objreferência, algum caminho de código que finalmente teve acesso legal à classe criou um e deu (vazou?) Para você.
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))

O pseudo-código acima é uma definição de, se referências do tipo / classe A são atribuíveis a partir de referências do tipo / classe B. É uma definição recursiva. Para alguns, pode ser útil, para outros, pode ser confuso. Eu o adiciono no caso de alguém achar útil. Esta é apenas uma tentativa de capturar meu entendimento, não é a definição oficial. Ele é usado em uma certa implementação de Java VM e funciona para muitos programas de exemplo, portanto, embora eu não possa garantir que ele capture todos os aspectos de isAssignableFrom, não está completamente desativado.


2
Por favor, explique o que esse código faz e como ele responde à pergunta.
Fund Monica's Lawsuit

0

Falando em termos de desempenho "2" (com 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();
}
}

Dá:

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

Para que possamos concluir: instanceof tão rápido quanto isInstance () e isAssignableFrom () não muito longe (+ 0,9% do tempo de execução). Portanto, não há diferença real, o que você escolher


0

Que tal alguns exemplos para mostrá-lo em ação ...

@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));
}

-2

alguns testes que fizemos em nossa equipe mostram que A.class.isAssignableFrom(B.getClass())funciona mais rápido que B instanceof A. isso pode ser muito útil se você precisar verificar isso em um grande número de elementos.


13
Hm, se você tem um gargalo no instanceof, eu acredito que você tem problemas de design graves ...
sleske

1
A resposta do JBE apresenta uma hipótese que difere da sua hipótese.
Alastor Moody 15/05
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.