99 votes

Est-ce que le casting en Java introduit des frais généraux? Pourquoi?

Y a-t-il un surcoût lorsque nous lançons des objets d'un type à un autre? Ou est-ce que le compilateur résout tout et qu'il n'y a pas de coût à l'exécution?

Est-ce une chose générale, ou y a-t-il différents cas?

Par exemple, supposons que nous ayons un tableau d'Object[], où chaque élément pourrait avoir un type différent. Mais nous savons toujours avec certitude que, disons, l'élément 0 est un Double, l'élément 1 est une String. (Je sais que c'est une conception incorrecte, mais supposons simplement que je devais le faire.)

Les informations de type de Java sont-elles encore conservées à l'exécution? Ou tout est-il oublié après la compilation, et si nous faisons (Double)elements[0], nous allons simplement suivre le pointeur et interpréter ces 8 octets comme un double, quoi que ce soit?

Je suis très incertain sur la manière dont les types sont gérés en Java. Si vous avez des recommandations de livres ou d'articles, merci aussi.

75voto

Alex Points 3183

Il existe 2 types de casting:

Implicite casting, lorsque vous effectuez un cast d'un type vers un type plus large, ce qui se fait automatiquement et il n'y a pas de surcharge:

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

Explicite casting, lorsque vous passez d'un type plus large à un type plus étroit. Pour ce cas, vous devez explicitement utiliser le casting de cette manière:

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

Dans ce deuxième cas, il y a une surcharge au moment de l'exécution, car les deux types doivent être vérifiés et dans le cas où le casting n'est pas faisable, la JVM doit générer une ClassCastException.

Extrait de JavaWorld: The cost of casting

Le casting est utilisé pour convertir entre les types - entre les types de référence en particulier, pour le type de casting sur lequel nous nous concentrons ici.

Les opérations d'Upcast (également appelées conversions élargies dans la spécification du langage Java) convertissent une référence de sous-classe en une référence de classe ancestrale. Cette opération de casting est normalement automatique, car elle est toujours sûre et peut être implémentée directement par le compilateur.

Les opérations de Downcast (également appelées conversions étroites dans la spécification du langage Java) convertissent une référence de classe ancestrale en une référence de sous-classe. Cette opération de casting crée une surcharge d'exécution, car Java exige que le cast soit vérifié lors de l'exécution pour s'assurer de sa validité. Si l'objet référencé n'est pas une instance du type cible pour le cast ou d'une sous-classe de ce type, le cast tenté n'est pas autorisé et doit générer une java.lang.ClassCastException.

43voto

Pour une implémentation raisonnable de Java :

Chaque objet possède un en-tête contenant, entre autres choses, un pointeur vers le type d'exécution (par exemple Double ou String, mais cela ne pourrait jamais être CharSequence ou AbstractList). En supposant que le compilateur d'exécution (généralement HotSpot dans le cas de Sun) ne peut pas déterminer le type statiquement, quelques vérifications doivent être effectuées par le code machine généré.

Tout d'abord, ce pointeur vers le type d'exécution doit être lu. Ceci est nécessaire pour appeler une méthode virtuelle dans une situation similaire de toute façon.

Pour convertir vers un type de classe, il est exactement connu combien de superclasses il y a jusqu'à ce que vous atteigniez java.lang.Object, donc le type peut être lu à un décalage constant à partir du pointeur du type (en fait les huit premiers dans HotSpot). Encore une fois, cela est analogue à la lecture d'un pointeur de méthode pour une méthode virtuelle.

Ensuite, la valeur lue doit simplement être comparée au type statique attendu de la conversion. Selon l'architecture du jeu d'instructions, une autre instruction devra effectuer une branche (ou une erreur) sur une mauvaise branche. Les jeux d'instructions tels que l'ARM 32 bits ont des instructions conditionnelles et peuvent peut-être permettre à la voie triste de passer par la voie joyeuse.

Les interfaces sont plus difficiles en raison de l'héritage multiple des interfaces. Généralement, les deux dernières conversions vers des interfaces sont mises en cache dans le type d'exécution. Dans les tout premiers jours (il y a plus d'une décennie), les interfaces étaient un peu lentes, mais ce n'est plus pertinent.

En espérant que vous puissiez voir que ce genre de chose est largement sans importance pour les performances. Votre code source est plus important. En termes de performances, le plus grand impact dans votre scénario est susceptible d'être les erreurs de cache en suivant les pointeurs d'objet à plusieurs endroits (les informations de type seront bien sûr communes).

8voto

Stephen C Points 255558

Par exemple, supposons que nous avons un tableau de Object[], où chaque élément pourrait avoir un type différent. Mais nous savons toujours avec certitude que, disons, l'élément 0 est un Double, l'élément 1 est une chaîne de caractères. (Je sais que c'est une conception erronée, mais supposons seulement que j'ai dû le faire.)

Le compilateur ne note pas les types des éléments individuels d'un tableau. Il vérifie simplement que le type de chaque expression d'élément est assignable au type d'élément du tableau.

Les informations de type de Java sont-elles toujours conservées à l'exécution ? Ou tout est-il oublié après la compilation, et si nous faisons (Double)elements[0], nous suivrons simplement le pointeur et interpréterons ces 8 octets comme un double, quoi que ce soit ?

Certaines informations sont conservées à l'exécution, mais pas les types statiques des éléments individuels. Vous pouvez le constater en examinant le format du fichier de classe.

Il est théoriquement possible que le compilateur JIT utilise une "analyse d'échappement" pour éliminer les vérifications de type inutiles dans certaines affectations. Cependant, faire cela dans la mesure que vous suggérez serait au-delà des limites de l'optimisation réaliste. Le bénéfice de l'analyse des types des éléments individuels serait trop faible.

De plus, les gens ne devraient de toute façon pas écrire du code d'application comme ça.

5voto

JesperE Points 34356

L'instruction de bytecode pour effectuer une conversion de type à l'exécution est appelée checkcast. Vous pouvez désassembler le code Java en utilisant javap pour voir quelles instructions sont générées.

Pour les tableaux, Java conserve des informations de type à l'exécution. La plupart du temps, le compilateur détectera les erreurs de type pour vous, mais il existe des cas où vous rencontrerez une ArrayStoreException en essayant de stocker un objet dans un tableau, mais le type ne correspond pas (et le compilateur ne l'a pas détecté). La spécification du langage Java donne l'exemple suivant :

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 est valide car ColoredPoint est une sous-classe de Point, mais pa[0] = new Point() n'est pas valide.

Ceci s'oppose aux types génériques, où aucune information de type n'est conservée à l'exécution. Le compilateur insère des instructions checkcast lorsque nécessaire.

Cette différence de typage pour les types génériques et les tableaux rend souvent inapproprié de mélanger les tableaux et les types génériques.

1voto

HesNotTheStig Points 473

En théorie, il y a une surcharge introduite. Cependant, les JVM modernes sont intelligentes. Chaque implémentation est différente, mais il n'est pas déraisonnable de supposer qu'il pourrait exister une implémentation qui optimise JIT les vérifications de conversion lorsqu'elle pourrait garantir qu'il n'y aurait jamais de conflit. Quant aux JVM spécifiques qui offrent cela, je ne pourrais pas vous le dire. J'avoue que j'aimerais connaître les détails de l'optimisation JIT moi-même, mais cela concerne les ingénieurs JVM.

La morale de l'histoire est d'écrire d'abord du code compréhensible. Si vous rencontrez des ralentissements, profilez et identifiez votre problème. Il est fort probable que ce ne soit pas dû à la conversion. Ne sacrifiez jamais un code propre et sûr dans une tentative de l'optimiser AVANT DE SAVOIR QUE VOUS DEVEZ LE FAIRE.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X