Question

Ce n’est pas une question de savoir ce que sont la boxe et le déballage, c’est plutôt pourquoi pourquoi des langages comme Java et C # en ont besoin?

Je connais très bien C ++, STL et Boost.

En C ++, je pourrais écrire très facilement quelque chose comme ceci,

std::vector<double> dummy;

J'ai de l'expérience avec Java, mais j'ai été vraiment surpris de devoir écrire quelque chose comme ça,

ArrayList<Double> dummy = new ArrayList<Double>();

Ma question, pourquoi devrait-il s'agir d'un objet, qu'est-ce qui est si difficile techniquement d'inclure des types primitifs lorsque l'on parle de génériques?

Était-ce utile?

La solution

  

Qu'est-ce qui est techniquement si difficile à inclure des types primitifs quand on parle de génériques?

Dans le cas de Java, cela tient à la façon dont fonctionnent les génériques. En Java, les génériques sont une astuce de compilation qui vous empêche de placer un objet Image dans un ArrayList<String>. Cependant, les génériques Java sont implémentés avec l'effacement de type: les informations de type génériques sont perdues lors de l'exécution. C'était pour des raisons de compatibilité, car les génériques avaient été ajoutés assez tard dans la vie de Java. Cela signifie qu’au moment de l’exécution, un ArrayList<Object> est en réalité un ArrayList (ou mieux: seulement Object qui attend et retourne String dans toutes ses méthodes) et qui passe automatiquement à int lorsque vous récupérez un fichier. valeur.

Mais étant donné que Integer ne dérive pas de List<int>, vous ne pouvez pas le placer dans une liste de tableaux qui attend (au moment de l'exécution) object et vous ne pouvez pas non plus transtyper un object obj = 2 vers <=> . Cela signifie que la primitive <=> doit être encapsulée dans un type héritant de <=>, comme <=>.

C # par exemple, fonctionne différemment. Les génériques en C # sont également appliqués au moment de l'exécution et aucune boxe n'est requise avec un <=>. La boxe en C # ne se produit que lorsque vous essayez de stocker un type de valeur tel que <=> dans une variable de type référence telle que <=>. Etant donné que <=> en C # hérite de <=> en C #, l'écriture <=> est parfaitement valide, cependant, l'int sera encaissé, ce qui est fait automatiquement par le compilateur (aucun type de référence <=> n'est exposé à l'utilisateur ou quoi que ce soit). ).

Autres conseils

La boxe et le déballage sont une nécessité née de la manière dont les langages (comme C # et Java) mettent en œuvre leurs stratégies d’allocation de mémoire.

Certains types sont alloués sur la pile et d’autres sur le tas. Afin de traiter un type alloué par pile comme un type alloué par tas, la mise en boîte est nécessaire pour déplacer le type alloué par pile sur le tas. Unboxing est le processus inverse.

En C #, les types attribués à la pile sont appelés types à valeur (par exemple, System.Int32 et System.DateTime ) et les types attribués à la pile sont types de référence (par exemple, System.Stream et System.String ).

Dans certains cas, il est avantageux de pouvoir traiter un type de valeur comme un type de référence (la réflexion en est un exemple), mais dans la plupart des cas, il est préférable d’éviter la boxe et le décoffrage.

Je pense que c'est également dû au fait que les primitives n'héritent pas de Object. Supposons que vous ayez une méthode qui veuille tout accepter comme paramètre, par exemple.

class Printer {
    public void print(Object o) {
        ...
    }
}

Vous devrez peut-être passer une valeur primitive simple à cette méthode, par exemple:

printer.print(5);

Vous pourrez le faire sans boxing / unboxing, car 5 est une primitive et n'est pas un objet. Vous pouvez surcharger la méthode d’impression pour chaque type de primitive afin d’activer une telle fonctionnalité, mais c’est pénible.

Je ne peux que vous dire pour Java pourquoi il ne prend pas en charge les types primitifs dans les génériques.

Premièrement, il y avait le problème que la question à l'appui de cette question soulevait à chaque fois le débat sur le fait de savoir si Java devait même avoir des types primitifs. Ce qui bien sûr a empêché la discussion de la question réelle.

Deuxièmement, la principale raison de ne pas l'inclure était qu'ils souhaitaient une compatibilité ascendante binaire afin que le produit ne soit pas modifié sur une machine virtuelle ne connaissant pas les génériques. C’est aussi pour cette raison de compatibilité ascendante / migration que l’API de Collections prend en charge les génériques et est restée identique. Il n’existe pas (comme en C # lorsqu’ils ont introduit les génériques) un nouvel ensemble complet d’une API de collection générique.

La compatibilité a été réalisée à l'aide d'ersure (les informations de paramètre de type générique ont été supprimées lors de la compilation), ce qui explique également le nombre important d'avertissements de distribution non vérifiés en java.

Vous pouvez toujours ajouter des génériques réifiés, mais ce n’est pas si facile. Ajouter simplement le type d’installation à la place de l’enlever au lieu de le supprimer ne fonctionnera pas, car cela briserait la source compatibilité binaire (vous ne pouvez pas continuer à utiliser des types bruts et vous ne pouvez pas appeler du code compilé existant car ils ne disposent pas des méthodes correspondantes).

L’autre approche est celle choisie par C #: voir ci-dessus

Et l'autoboxing / unboxing automatisé n'était pas pris en charge pour ce cas d'utilisation, car l'autoboxing coûte trop cher.

Théorie et pratique de Java: pièges génériques

En Java et en C # (contrairement au C ++), tout est étendu dans Object, de sorte que les classes de collection comme ArrayList peuvent contenir Object ou l'un de ses descendants (à peu près n'importe quoi).

Toutefois, pour des raisons de performances, les primitives en java ou les types de valeur en C # ont reçu un statut spécial. Ils ne sont pas objet. Vous ne pouvez pas faire quelque chose comme (en Java):

 7.toString()

Même si toString est une méthode sur Object. Afin de relier ce nœud à la performance, des objets équivalents ont été créés. AutoBoxing supprime le code habituel du fait de devoir mettre une primitive dans sa classe wrapper et de la retirer à nouveau, ce qui rend le code plus lisible.

La différence entre les types de valeur et les objets en C # est plus grise. Consultez ici pour en savoir plus sur leur différence.

Chaque objet non-chaîne non-tableau stocké sur le segment contient un en-tête de 8 ou 16 octets (tailles pour les systèmes 32/64 bits), suivi du contenu des champs public et privé de cet objet. Les tableaux et les chaînes ont l'en-tête ci-dessus, plus quelques octets définissant la longueur du tableau et la taille de chaque élément (et éventuellement le nombre de dimensions, la longueur de chaque dimension supplémentaire, etc.), suivis de tous les champs du premier. élément, puis tous les champs de la seconde, etc. Avec une référence à un objet, le système peut facilement examiner l’en-tête et déterminer son type.

Les emplacements de stockage de type référence contiennent une valeur de quatre ou huit octets qui identifie de manière unique un objet stocké sur le segment de mémoire. Dans les implémentations actuelles, cette valeur est un pointeur, mais il est plus facile (et sémantiquement équivalent) de la considérer comme un "ID d'objet".

Les emplacements de stockage de type valeur contiennent le contenu des champs du type valeur mais n'ont pas d'en-tête associé. Si code déclare une variable de type Int32 , il n'est pas nécessaire de stocker des informations avec ce Int32 indiquant ce que c'est. Le fait que cet emplacement contienne un Int32 est effectivement stocké en tant que partie du programme et ne doit donc pas nécessairement être stocké dans l'emplacement lui-même. Ceci représente une économie importante si, par exemple, on a un million d'objets ayant chacun un champ de type Int32 . Chacun des objets contenant le Int32 a un en-tête qui identifie la classe qui peut le faire fonctionner. Puisqu'une copie de ce code de classe peut fonctionner sur n'importe quel million d'instances, le fait que le champ soit un Int32 fait partie du code, ce qui est beaucoup plus efficace que d'avoir le stockage pour chacune de celles-ci. les champs incluent des informations sur ce que c'est.

La boxe est nécessaire lorsqu'une demande est faite pour transmettre le contenu d'un emplacement de stockage de type valeur au code qui ne sait pas s'attendre à ce type de valeur particulier. Le code qui attend des objets de type inconnu peut accepter une référence à un objet stocké sur le tas. Chaque objet stocké dans le tas ayant un en-tête identifiant son type, le code peut utiliser cet en-tête chaque fois qu'il est nécessaire d'utiliser un objet de manière à en connaître le type.

Notez que dans .net, il est possible de déclarer ce qu'on appelle des classes et méthodes génériques. Chaque déclaration génère automatiquement une famille de classes ou de méthodes identiques à l’exception du type d’objet sur lequel elles s’attendent. Si on passe un Int32 à une routine DoSomething < T > (T param) , cela générera automatiquement une version de la routine dans laquelle chaque instance de type T est effectivement remplacé par Int32 . Cette version de la routine saura que chaque emplacement de stockage déclaré comme type T contient un Int32 , de la même manière que dans le cas où une routine était codée en dur pour utiliser un < code> Int32 , il ne sera pas nécessaire de stocker les informations de type avec ces emplacements eux-mêmes.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top