web-dev-qa-db-fra.com

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

Y a-t-il des frais généraux lorsque nous moulons des objets d'un type à un autre? Ou le compilateur résout tout et il n'y a aucun coût au moment de l'exécution?

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

Par exemple, supposons que nous ayons un tableau Object [], où chaque élément peut 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. (Je sais que c'est une mauvaise conception, mais supposons simplement que je devais le faire.)

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

Je ne sais pas trop comment les types sont exécutés en Java. Si vous avez des recommandations sur des livres ou des articles, merci aussi.

94
Phil

Il existe 2 types de casting:

Casting implicite , lorsque vous castez 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; // implicit casting

Casting explicite , lorsque vous passez d'un type plus large à un type plus étroit. Dans ce cas, vous devez utiliser explicitement le cast comme ça:

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

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

Extrait de JavaWorld: The cost of casting

Casting est utilisé pour convertir entre les types - entre les types de référence en particulier, pour le type d'opération de casting qui nous intéresse ici.

Les opérations de conversion ascendante (également appelées élargissement des conversions dans la spécification de langage Java Language) convertissent une référence de sous-classe en référence de classe ancêtre. 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 rétrécissement des conversions dans la spécification de langage Java Language) convertissent une référence de classe ancêtre en référence de sous-classe. Cette opération de conversion crée une surcharge d'exécution, car Java nécessite que la conversion soit vérifiée au moment de l'exécution pour s'assurer qu'elle est valide. Si l'objet référencé n'est pas une instance du type cible pour la conversion ou une sous-classe de ce type, la tentative de transtypage n'est pas autorisée et doit lever une Java.lang.ClassCastException.

72
Alex

Pour une implémentation raisonnable de Java:

Chaque objet a un en-tête contenant, entre autres, un pointeur sur le type d'exécution (par exemple Double ou String, mais il ne pourra 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 statiquement le type qu'une vérification doit être effectuée par le code machine généré.

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

Pour le cast dans un type de classe, on sait exactement combien de superclasses il y a jusqu'à ce que vous frappiez Java.lang.Object, afin que le type puisse être lu avec un décalage constant par rapport au pointeur de 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 a juste besoin d'une comparaison avec le type statique attendu de la distribution. En fonction de l'architecture du jeu d'instructions, une autre instruction devra se ramifier (ou être défaillante) sur une branche incorrecte. Les ISA tels que 32 bits ARM ont une instruction conditionnelle et peuvent être en mesure de faire passer le chemin triste par le chemin heureux).

Les interfaces sont plus difficiles à cause de l'héritage multiple de l'interface. Généralement, les deux derniers transtypages vers les interfaces sont mis en cache dans le type d'exécution. Au tout début (il y a plus de dix ans), les interfaces étaient un peu lentes, mais ce n'est plus pertinent.

J'espère que vous pouvez voir que ce genre de chose n'est pas pertinent pour la performance. Votre code source est plus important. En termes de performances, le plus gros coup dans votre scénario est susceptible d'être des échecs de cache en poursuivant les pointeurs d'objet partout (les informations de type seront bien sûr communes).

43

Par exemple, supposons que nous ayons un tableau Object [], où chaque élément peut 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. (Je sais que c'est une mauvaise conception, mais supposons simplement que je devais 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 attribuable au type d'élément de tableau.

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

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

Il est théoriquement possible que le compilateur JIT utilise "l'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 d'éléments individuels serait trop faible.

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

7
Stephen C

L'instruction de code octet pour effectuer la conversion au moment de l'exécution est appelée checkcast. Vous pouvez démonter Java code en utilisant javap pour voir quelles instructions sont générées.

Pour les tableaux, Java conserve les informations de type lors de l'exécution. La plupart du temps, le compilateur détecte les erreurs de type pour vous, mais dans certains cas, vous rencontrerez un ArrayStoreException lorsque essayant de stocker un objet dans un tableau, mais le type ne correspond pas (et le compilateur ne l'a pas détecté). Le 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 est opposé aux types génériques, où aucune information de type n'est conservée lors de l'exécution. Le compilateur insère des instructions checkcast si nécessaire.

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

5
JesperE

En théorie, des frais généraux sont introduits. Cependant, les machines virtuelles Java 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 que JIT a optimisé les vérifications de coulée à distance alors qu'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. Je dois admettre que j'aimerais connaître moi-même les spécificités de l'optimisation JIT, mais celles-ci doivent inquiéter les ingénieurs JVM.

La morale de l'histoire est d'écrire d'abord du code compréhensible. Si vous rencontrez des ralentissements, identifiez et identifiez votre problème. Les chances sont bonnes que ce ne soit pas dû au casting. Ne sacrifiez jamais un code propre et sûr pour tenter de l'optimiser JUSQU'À CE QUE VOUS SAVIEZ QUE VOUS EN AVEZ BESOIN.

1
HesNotTheStig