Para uma implementação razoável de Java:
Cada objeto possui um cabeçalho contendo, entre outras coisas, um ponteiro para o tipo de tempo de execução (por exemplo Double
ou String
, mas nunca poderia ser CharSequence
ou AbstractList
). Assumindo que o compilador de tempo de execução (geralmente HotSpot no caso da Sun) não pode determinar o tipo estaticamente, algumas verificações precisam ser realizadas pelo código de máquina gerado.
Primeiro, esse ponteiro para o tipo de tempo de execução precisa ser lido. De qualquer forma, isso é necessário para chamar um método virtual em uma situação semelhante.
Para lançar para um tipo de classe, sabe-se exatamente quantas superclasses existem até você atingir java.lang.Object
, então o tipo pode ser lido em um deslocamento constante do ponteiro de tipo (na verdade, os primeiros oito no HotSpot). Novamente, isso é análogo a ler um ponteiro de método para um método virtual.
Então, o valor lido só precisa de uma comparação com o tipo estático esperado do elenco. Dependendo da arquitetura do conjunto de instruções, outra instrução precisará ramificar (ou falhar) em uma ramificação incorreta. ISAs, como o ARM de 32 bits, têm instrução condicional e podem fazer com que o caminho triste passe pelo caminho feliz.
As interfaces são mais difíceis devido à herança múltipla da interface. Geralmente, as duas últimas conversões para interfaces são armazenadas em cache no tipo de tempo de execução. Nos primeiros dias (mais de uma década atrás), as interfaces eram um pouco lentas, mas isso não é mais relevante.
Esperançosamente, você pode ver que esse tipo de coisa é irrelevante para o desempenho. Seu código-fonte é mais importante. Em termos de desempenho, o maior acerto em seu cenário é passível de perdas de cache por perseguir ponteiros de objeto em todo o lugar (as informações de tipo serão, obviamente, comuns).