IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Java Discussion :

Gestion mémoire, héritage et cast


Sujet :

Langage Java

  1. #1
    Membre à l'essai
    Profil pro
    Inscrit en
    Décembre 2007
    Messages
    25
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Décembre 2007
    Messages : 25
    Points : 12
    Points
    12
    Par défaut Gestion mémoire, héritage et cast
    Bonjour,

    J'ai un petit soucis pour me représenter ce qu'il se passe réellement en mémoire, et je commence sérieusement à m’emmêler les pinceaux malgré les lectures de ci de là sur le net. Aussi, je me tourne vers vous afin d’éclairer ma lanterne.

    Mettons que j'ai les classes suivantes :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Mere
    {
       public void doSmth(){ 
          System.out.println("Mere: doSmth");  
       }
    }
     
    public class Enfant extends Mere
    {
       public void doSmth() { 
          System.out.println("Enfant: doSmth"); 
       }
       public void onlyDefinedInChild() { 
          System.out.println("Enfant: onlyDefinedInChild"); 
       }
    }
    Nous sommes d'accord sur les implications suivantes :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Mere m = new Mere(); m.doSmth(); // print: Mere: doSmth 
    Enfant e = new Enfant(); e.doSmth(); // print: Enfant: doSmth (si doSmth() pas defini dans Enfant alors on aurait eu Mere: doSmth )
    Maintenant, le cast:
    1) je reprends ce post :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    Mere derivedInstance = new Enfant();
    Mere baseInstance  = new Mere();
       
    Enfant good = (Enfant) derivedInstance; // OK
    Enfant fail = (Enfant) baseInstance;    // Throws InvalidCastException
    
    derivedInstance.doSmth(); // print: Enfant: doSmth
    Je ne comprends pas ce qu'il se passe en mémoire : pourquoi derivedInstance.doSmth(); appelle correctement une méthode de Enfant par overriding (mécanisme qui me semble somme logique du fait du new Child() ), alors que je ne peux pas appeler une méthode uniquement définie dans Enfant (derivedInstance.onlyDefinedInChild() me renvoie une erreur) ?
    Pourtant, si j'imprime la taille de mes objets, derivedInstance semble avoir l'espace mémoire d'un Enfant (plus grand que son parent baseInstance).
    Pourquoi pouvoir appeler certaines méthodes de l'Enfant et pas d'autres ?

    2) Deuxième constat que je ne m'explique pas, je pense que j'ai loupé un principe pourtant crucial pour bloquer là dessus :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Enfant c = new Enfant();
    Mere p = (Mere) c;
    p.doSmth(); // print:  Enfant: doSmth
    Je m'attends après un cast à avoir comme print: Mere: doSmth. Pourquoi diantre est-ce le doSmth() de l'enfant qui est appelé ?
    Que se passe-t-il en mémoire ?


    Merci par avance de m’éclairer sur ces points.

  2. #2
    Membre émérite
    Avatar de olivier.pitton
    Homme Profil pro
    Développeur Java
    Inscrit en
    Juin 2012
    Messages
    355
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Développeur Java
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2012
    Messages : 355
    Points : 2 814
    Points
    2 814
    Par défaut
    Plop,

    1°) C'est le principe du polymorphisme. Le polymorphisme est l'exécution dynamique d'une méthode du type réel de l'objet. Ainsi, si tu écris :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    Mere derivedInstance = new Enfant();
    derivedInstance.doSmth(); // print: Enfant: doSmth
    Tu appelles la méthode du type réel de l'objet, soit Enfant.

    Maintenant ta variable derivedInstance est de type Mère. Donc tu ne peux qu'appeler les méthodes de la classe "Mère". Donc pas celle de "Enfant".

    Basiquement en reprenant le code ci-dessous :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Mere derivedInstance = new Enfant();
    derivedInstance est officiellement de type Mère, donc je ne peux appeler que des méthodes de la classe "Mère". D'aucune autre classes. Ce qui explique pourquoi tu ne peux pas appeler "onlyDefinedInChild". Lors de l'appel à "doSmth", grâce au polymorphisme, Java choisit la bonne méthode à appeler, donc celle de Enfant.

    2°) Il ne se passe absolument rien.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    Enfant c = new Enfant();
    Mere p = (Mere) c;
    p.doSmth(); // print:  Enfant: doSmth
    Pour comprendre tout ce qui est cast etc... il faut juste te poser la question "Quel est le type réel de mon objet, pas ce qu'il y a de déclarer". Ici la variable "c" est Enfant et sera TOUJOURS Enfant, puisqu'on ne change pas la référence dans le code ci-dessus. Donc il pointera toujours sur Enfant, alors on appelera les méthodes de la classe Enfant. Le cast effectué ici est totalement inutile.

    Tu as deux manières de caster (upcasting et downcasting) :
    - Classe fille -> classe mère : Tu veux protéger l'implémentation en affichant l'interface (ou la classe la plus haute de la hiérarchie, sous Object). L'avantage est que tu peux changer d'implémentation très facilement.
    - Classe mère -> classe fille : Peut lancer une erreur si le cast est faux. L'intérêt est de récupérer des attributs / méthodes propres à la classe fille dont on a besoin. La mère ne possède réellement pas les variables d'instance / méthodes (comme "onlyDefinedInChild") donc on est obligé d'avoir le vrai type de l'objet pour appeler la méthode.

  3. #3
    Membre à l'essai
    Profil pro
    Inscrit en
    Décembre 2007
    Messages
    25
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Décembre 2007
    Messages : 25
    Points : 12
    Points
    12
    Par défaut
    Bonjour,

    Merci de ta réponse. Ça commence à s’éclaircir !

    1)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Mere derivedInstance = new Enfant();
    derivedInstance.doSmth(); // print: Enfant: doSmth
    Ok donc il y a "deux types" :
    - le type déclaré, celui à qui on affecte l'objet
    - le type réel, celui derrière le new
    A la compilation, il vérifie la validité des instructions via le type déclaré (d'où le compilateur et son haut le coeur quand j’écris derivedInstance.onlyDefinedInChild() ). A l’exécution, il choisit via le polymorphisme les méthodes du type réel si elles sont déclarées, sinon il cherche la première déclaration dans la hiérarchie de classe en remontant.
    Est-ce juste ?

    2)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Enfant c = new Enfant();
    Mere p = (Mere) c;
    p.doSmth(); // print:  Enfant: doSmth
    Ok, arrête moi si je me trompe, on se retrouve en fait exactement dans la même situation qu'au point 1 : p a pour type déclaré Mere et pour type réel Enfant ?
    Les 2 premières lignes sont donc équivalentes à : Mere derivedInstance = new Enfant();

    Si je souhaite que, pour une raison X ou Y, ça print Parent: doSmth, il faut que je passe via le constructeur du parent en faisant un deep cloning, et définir un constructeur dans le parent ayant pour signature :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    public Mere (Mere m)
    {
       ...
    }

    Merci par avance pour les dernières précisions.

  4. #4
    Modérateur

    Profil pro
    Inscrit en
    Septembre 2004
    Messages
    12 551
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2004
    Messages : 12 551
    Points : 21 607
    Points
    21 607
    Par défaut
    Citation Envoyé par Nemix Voir le message
    1) Est-ce juste ?
    Oui.
    Le type déclaré se rapporte aux variables, (et aussi aux expressions,) on appelle effectivement ça le type.
    Le type réel se rapporte aux objets eux-même et pas aux variables qui y font référence. On appelle ça la classe de l'objet.

    Le type, concernant des variables, est effectif dès la compilation. La classe, concernant des objets, donc des instances de classe, n'a de sens que pendant l'exécution.

    Citation Envoyé par Nemix Voir le message
    2) Ok, arrête moi si je me trompe, on se retrouve en fait exactement dans la même situation qu'au point 1 : p a pour type déclaré Mere et pour type réel Enfant ?
    Oui.

    Citation Envoyé par Nemix Voir le message
    Les 2 premières lignes sont donc équivalentes à : Mere derivedInstance = new Enfant();
    Ben non vu qu'elles déclarent deux variables au lieu d'une, et qu'elles s'appellent c et p et non pas derivedInstance.

    Mais tu as compris le principe, elles sont équivalentes à:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Enfant c = new Enfant();
    Mere p = c;
    Un upcast est toujours implicite, ça ne sert à rien de l'écrire.

    Citation Envoyé par Nemix Voir le message
    Si je souhaite que, pour une raison X ou Y, ça print Parent: doSmth, il faut que je passe via le constructeur du parent en faisant un deep cloning, et définir un constructeur dans le parent ayant pour signature :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    public Mere (Mere m)
    {
       ...
    }
    Il y a une infinité de manières d'aborder ce besoin. La bonne est celle qui t'arrange. Mais ce que tu nous montres là n'a pas l'air bon.

    En voici une plus adaptée :
    si tu as besoin de ça, alors tu as besoin de deux méthodes pour ta classe Mere :
    - une qui s'appelle doSmth() et qui fait quelque chose. Elle peut être redéfinie par Enfant.
    - une autre qui s'appelle doSmthLikeMereDidIt(), et tu peux la rendre final pour que les enfants ne la redéfinissent pas. Ou alors tu peux laisser les enfants décider s'ils la redéfinissent ou pas, auquel cas il faudra trouver un autre nom.
    Bien sûr, dans le cas de la classe Mere, doSmth() peut simplement appeler doSmthLikeMereDidIt().
    N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  5. #5
    Membre à l'essai
    Profil pro
    Inscrit en
    Décembre 2007
    Messages
    25
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Décembre 2007
    Messages : 25
    Points : 12
    Points
    12
    Par défaut
    Bonjour,

    Merci de ta réponse. Elle vient compléter à merveille la précédente !

    Si je puis me permettre de revenir sur la dernière partie, tu dis :
    Il y a une infinité de manières d'aborder ce besoin. La bonne est celle qui t'arrange. Mais ce que tu nous montres là n'a pas l'air bon.
    Je ne saisis pas pourquoi définir un constructeur dans la classe Mere pour être sure de transformer mon objet en le type + classe Mere n'est pas une manière convenable ?

    Il est vrai que mon explication n’était pas claire sur ce que je souhaitais faire avec mon objet, sorry.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Enfant c = new Enfant();
    Mere p = c;
    Je souhaite que p ait pour type et classe Mere.

    Arrête moi je me trompe, j'ai l'impression que ce que tu me proposes ensuite est dans le but de garder le type Mere et la class Enfant de l'objet :
    En voici une plus adaptée :
    si tu as besoin de ça, alors tu as besoin de deux méthodes pour ta classe Mere :
    - une qui s'appelle doSmth() et qui fait quelque chose. Elle peut être redéfinie par Enfant.
    - une autre qui s'appelle doSmthLikeMereDidIt(), et tu peux la rendre final pour que les enfants ne la redéfinissent pas. Ou alors tu peux laisser les enfants décider s'ils la redéfinissent ou pas, auquel cas il faudra trouver un autre nom.
    Bien sûr, dans le cas de la classe Mere, doSmth() peut simplement appeler doSmthLikeMereDidIt().

  6. #6
    Membre émérite
    Avatar de olivier.pitton
    Homme Profil pro
    Développeur Java
    Inscrit en
    Juin 2012
    Messages
    355
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Développeur Java
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2012
    Messages : 355
    Points : 2 814
    Points
    2 814
    Par défaut
    Plop,

    En fait, quand on dit qu'il y a "deux types", ce n'est pas vrai. C'est juste une notion de visiblité. Tu ne peux qu'appeler le code tel que définit dans le "contrat" de la classe. Si la classe Mère ne définit pas une méthode, comment peux-tu l'appeler ? Si tu décides de caster une Mère en Enfant, qu'est ce qui te dit que tu n'as pas juste créer une Mère ? Donc que tu ne peux pas appeler la méthode spécifique à Enfant.

    Il y a ce que tu vois et ce qui est purement et simplement.

    Object ob = new String()
    Tu verras Object, mais le type est String. C'est juste masqué.

    Je ne saisis pas pourquoi définir un constructeur dans la classe Mere pour être sure de transformer mon objet en le type + classe Mere n'est pas une manière convenable ?
    Commt dit précédemment, le deep cloning permet simplement de copier les attributs d'une classe dans une autre classe. Tout comme tu peux passer directement des valeurs, tu passes une instance. Tu ne peux pas avoir "2 types", tu n'en as qu'un seul.

    Arrête moi je me trompe, j'ai l'impression que ce que tu me proposes ensuite est dans le but de garder le type Mere et la class Enfant de l'objet :
    Oui.

    Toi ce que tu cherches à faire, si j'ai bien compris, c'est de rendre visible une méthode propre à Enfant, lorsque tu utilises Mère. Et la solution proposée est, de mon point de vue, la meilleure.

    Si Mère possède la méthode alors tu peux faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    Mère p = new Enfant();
    p.onlyDefinedInChild();
    En utilisant le polymorphisme, tu peux masquer tes implémentations aux yeux des autres classes qui l'utilisent.

    Imagine le scénario suivant en entreprise :
    - L'équipe A développe un module A
    - L'équipe B développe un module B, qui a besoin de A.
    - L'équipe B doit finir avant l'équipe A, pour des raisons lambda.

    Si l'équipe A définit une interface générale, l'équipe B peut l'utiliser dans tout son code, sans se soucier de savoir comment cela fonctionne. Le temps que l'équipe A finisse, B pourra continuer à avancer, même si leur code ne fonctionne pas. Il fonctionnera quand l'équipe A aura fini (c'est de la théorie hein). Lorsque A aura fini son travail, elle remplacera une implémentation bidon (appelée bouchon ou mock) utilisée par B pour avancer par la vraie implémentation, la réalisation de leur travail. Et comme B ne manipule que des interfaces (ou des classes mères de haut niveau), il suffira de changer les endroits, dans le module A où les new sont faits, et on n'impactera pas le module B car la classe aura disparue.

  7. #7
    Membre à l'essai
    Profil pro
    Inscrit en
    Décembre 2007
    Messages
    25
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Décembre 2007
    Messages : 25
    Points : 12
    Points
    12
    Par défaut
    Bonjour,

    Merci de ta réponse ! Ces explications sont grandement appréciées.

    Tu ne peux pas avoir "2 types", tu n'en as qu'un seul.
    Le type et la classe comme expliqué par thelvin. En résumé, la classe est relative à ce qu'il y avait derrière le new (qu'on obtient d'ailleurs avec monObject.getClass()), et le type est celui à gauche de l'affectation (Mere m = new Enfant(); ).


    Toi ce que tu cherches à faire, si j'ai bien compris, c'est de rendre visible une méthode propre à Enfant, lorsque tu utilises Mère. Et la solution proposée est, de mon point de vue, la meilleure.
    Non non, l’idée serait d'effectuer une opération avec la variable de classe Enfant pour que le la classe de la nouvelle variable soit celle de Mere (la terminologie de "cast" n'est de fait pas celle appropriée pour cette opération). Bon là de suite je n'ai pas d'exemple particulier pour illustrer pourquoi je voudrais pouvoir faire ça, c'est simplement par curiosité et bien comprendre les tenants et aboutissants de ces mécanismes.
    Par exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Enfant c = new Enfant();
    Mere p = // on fait quelque chose avec l'objet c ;
    et qu'en sortie p.getClass() retourne Mere et non Enfant.

  8. #8
    Modérateur

    Profil pro
    Inscrit en
    Septembre 2004
    Messages
    12 551
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2004
    Messages : 12 551
    Points : 21 607
    Points
    21 607
    Par défaut
    Citation Envoyé par Nemix Voir le message
    Non non, l’idée serait d'effectuer une opération avec la variable de classe Enfant pour que le la classe de la nouvelle variable soit celle de Mere (la terminologie de "cast" n'est de fait pas celle appropriée pour cette opération). Bon là de suite je n'ai pas d'exemple particulier pour illustrer pourquoi je voudrais pouvoir faire ça, c'est simplement par curiosité et bien comprendre les tenants et aboutissants de ces mécanismes.
    Par exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Enfant c = new Enfant();
    Mere p = // on fait quelque chose avec l'objet c ;
    et qu'en sortie p.getClass() retourne Mere et non Enfant.
    Ton problème c'est justement que tu ne sais pas à quoi ça va te servir. Tant que ce sera le cas, ça ne sert à rien et il n'y a donc pas moyen de le faire.

    On ne peut pas changer la classe d'un objet. Donc pour obtenir un objet de classe Mere à partir d'un objet de classe Enfant, tu as forcément besoin d'obtenir un autre objet à partir du premier objet.
    Cela peut se faire en construisant ce nouvel objet, donc en créant une instance de la classe Mere, ce qui implique forcément d'appeler son constructeur à un moment ou à un autre.
    Mais il est possible aussi qu'il n'existe que très peu d'objet Mere au comportement différent, et que tu les ai déjà tous construits au démarrage du programme. Dans ce cas tu n'as pas besoin d'en construire un nouveau, tu dois juste sélectionner lequel convient le mieux pour représenter l'Enfant concerné.
    Il existe aussi le mélange des deux : un ensemble d'objets Mere pré-existants pour représenter les cas les plus courants, mais pour les autres cas il faudra construire un nouvel objet Mere. La classe Integer, par exemple, fonctionne comme cela.
    N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  9. #9
    Membre émérite
    Avatar de olivier.pitton
    Homme Profil pro
    Développeur Java
    Inscrit en
    Juin 2012
    Messages
    355
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Développeur Java
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2012
    Messages : 355
    Points : 2 814
    Points
    2 814
    Par défaut
    Effectivement c'est impossible. Nous n'avons pas de réel contrôle sur la mémoire, et nous ne pouvons pas dire quelque chose comme : "J'alloue X octets de mémoire pour mettre quelque chose, et finalement je le change par un truc complètement différent".

    En Java chaque objet contient une référence vers sa classe, et il est impossible de modifier cette référence.

  10. #10
    Membre à l'essai
    Profil pro
    Inscrit en
    Décembre 2007
    Messages
    25
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Décembre 2007
    Messages : 25
    Points : 12
    Points
    12
    Par défaut
    Bonjour à vous deux,

    Merci beaucoup pour tous ces éclaircissements.

    Un "Résolu" s'impose après tant de précisions et de patience dans vos réponses.

    Bonne journée.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [D7] Tableau dynamique et Gestion mémoire
    Par Cl@udius dans le forum Langage
    Réponses: 7
    Dernier message: 13/03/2006, 15h16
  2. héritage et casting
    Par dinver dans le forum Langage
    Réponses: 3
    Dernier message: 04/12/2005, 23h23
  3. [Gestion mémoire] SetLength sur TDoubleDynArray
    Par MD Software dans le forum Langage
    Réponses: 14
    Dernier message: 24/04/2005, 21h11
  4. [DP][héritage]sous-casting dynamique
    Par Le prophete dans le forum Général Java
    Réponses: 4
    Dernier message: 20/08/2004, 11h56
  5. Gestion mémoire des Meshes (LPD3DXMESH)
    Par [Hideki] dans le forum DirectX
    Réponses: 1
    Dernier message: 08/07/2003, 20h34

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo