Pergunta

Existe alguma sobrecarga quando convertemos objetos de um tipo para outro?Ou o compilador simplesmente resolve tudo e não há custo em tempo de execução?

Isso é geral ou há casos diferentes?

Por exemplo, suponha que temos um array de Object[], onde cada elemento pode ter um tipo diferente.Mas sempre sabemos com certeza que, digamos, o elemento 0 é um Double, o elemento 1 é uma String.(Eu sei que este é um design errado, mas vamos supor que eu tive que fazer isso.)

As informações de tipo do Java ainda são mantidas em tempo de execução?Ou tudo é esquecido após a compilação, e se fizermos (Double)elements[0], vamos apenas seguir o ponteiro e interpretar esses 8 bytes como um duplo, o que quer que seja?

Não estou certo sobre como os tipos são feitos em Java.Se você tiver alguma recomendação sobre livros ou artigos, obrigado também.

Foi útil?

Solução

Existem 2 tipos de fundição:

Implícito casting, quando você converte de um tipo para um tipo mais amplo, o que é feito automaticamente e não há sobrecarga:

String s = "Cast";
Object o = s; // implicit casting

Explícito fundição, quando você passa de um tipo mais largo para um mais estreito.Para este caso, você deve usar explicitamente a conversão assim:

Object o = someObject;
String s = (String) o; // explicit casting

Neste segundo caso, há sobrecarga em tempo de execução, pois os dois tipos devem ser verificados e caso o casting não seja viável, a JVM deve lançar uma ClassCastException.

Tirado de JavaWorld:O custo da fundição

Fundição é usado para converter entre tipos -- entre tipos de referência em particular, para o tipo de fundição operação na qual estamos interessados aqui.

Elevado operações (também chamadas de ampliando conversões no Java Especificação de Idioma) converter um referência de subclasse a um ancestral referência de classe.Este casting A operação é normalmente automática, uma vez que é sempre seguro e pode ser implementado diretamente pelo compilador.

Abatido operações (também chamadas de estreitando conversões no Java Especificação de Idioma) converter um referência de classe ancestral a uma subclasse referência.Esta operação de fundição cria sobrecarga de execução, já que Java exige que o elenco seja verificado em para garantir que é válido.Se o objeto referenciado não for um instância do tipo de destino para o molde ou uma subclasse desse tipo, a tentativa de elenco não é permitida e deve lançar um java.lang.ClassCastException.

Outras dicas

Para uma implementação razoável de Java:

Cada objeto tem 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). Supondo que o compilador de tempo de execução (geralmente o hotspot no caso da Sun) não possa determinar o tipo estaticamente uma verificação precisa ser executada pelo código da 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 fundir para um tipo de classe, sabe -se exatamente quantas superclasses existem até você bater java.lang.Object, portanto, o tipo pode ser lido em um deslocamento constante do ponteiro do tipo (na verdade, os oito primeiros em hotspot). Novamente, isso é análogo a ler um ponteiro de método para um método virtual.

Em seguida, o valor de leitura precisa apenas 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 braço de 32 bits têm instrução condicional e podem ter o caminho triste passar pelo caminho feliz.

As interfaces são mais difíceis devido à herança múltipla da interface. Geralmente, os dois últimos moldes para interfaces são armazenados em cache no tipo de tempo de execução. Nos primeiros dias (há mais de uma década), as interfaces eram um pouco lentas, mas isso não é mais relevante.

Espero que você possa ver que esse tipo de coisa é amplamente irrelevante para o desempenho. Seu código -fonte é mais importante. Em termos de desempenho, o maior sucesso do seu cenário pode ser o cache errar por perseguir indicadores de objetos por todo o lugar (as informações do tipo serão comuns).

Por exemplo, suponha que tenhamos uma matriz de objeto [], onde cada elemento pode ter um tipo diferente. Mas sempre sabemos ao certo que, digamos, o elemento 0 é um duplo, o elemento 1 é uma string. (Eu sei que este é um design errado, mas vamos apenas assumir que eu tinha que fazer isso.)

O compilador não observa os tipos de elementos individuais de uma matriz. Ele simplesmente verifica se o tipo de expressão de cada elemento é atribuível ao tipo de elemento da matriz.

As informações do tipo Java ainda são mantidas no tempo de execução? Ou tudo é esquecido após a compilação e, se fizermos (duplo) elementos [0], seguiremos o ponteiro e interpretaremos esses 8 bytes como um dobro, seja o que for?

Algumas informações são mantidas no tempo de execução, mas não os tipos estáticos dos elementos individuais. Você pode dizer isso ao olhar para o formato de arquivo da classe.

É teoricamente possível que o compilador JIT possa usar "análise de escape" para eliminar verificações de tipo desnecessárias em algumas tarefas. No entanto, fazer isso na medida que você está sugerindo estaria além dos limites da otimização realista. A recompensa de analisar os tipos de elementos individuais seria muito pequena.

Além disso, as pessoas não devem escrever código de aplicativo como esse de qualquer maneira.

A instrução de código de bytes para executar o elenco em tempo de execução é chamada checkcast. Você pode desmontar o código Java usando javap Para ver quais instruções são geradas.

Para matrizes, o Java mantém as informações do tipo em tempo de execução. Na maioria das vezes, o compilador captura erros do tipo ArrayStoreException Ao tentar armazenar um objeto em uma matriz, mas o tipo não corresponde (e o compilador não o pegou). o Java Language Spec Dá o seguinte exemplo:

class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
    public static void main(String[] args) {
        ColoredPoint[] cpa = new ColoredPoint[10];
        Point[] pa = cpa;
        System.out.println(pa[1] == null);
        try {
            pa[0] = new Point();
        } catch (ArrayStoreException e) {
            System.out.println(e);
        }
    }
}

Point[] pa = cpa é válido desde então ColoredPoint é uma subclasse de ponto, mas pa[0] = new Point() não é válido.

Isso se opõe a tipos genéricos, onde não há informações de tipo mantidas em tempo de execução. O compilador insere checkcast instruções quando necessário.

Essa diferença na digitação para tipos genéricos e matrizes torna muitas vezes inadequados misturar matrizes e tipos genéricos.

Em teoria, há uma sobrecarga introduzida. No entanto, os JVMs modernos são inteligentes. Cada implementação é diferente, mas não é irracional supor que possa existir uma implementação que o JIT otimizasse os cheques de elenco quando isso poderia garantir que nunca haveria um conflito. Quanto a quais JVMs específicos oferecem isso, eu não sabia. Devo admitir que gostaria de conhecer as especificidades da otimização do JIT, mas são para os engenheiros da JVM se preocupar.

A moral da história é escrever um código compreensível primeiro. Se você estiver experimentando desacelerações, perfil e identifique seu problema. As chances são boas de que não seja devido ao elenco. Nunca sacrifique o código limpo e seguro, na tentativa de otimizá -lo até saber que precisa.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top