Partage
  • Partager sur Facebook
  • Partager sur Twitter

valeur d'objet dans une class qui varie

    26 septembre 2021 à 11:44:36

    Bonjour,

    Je suis débutant en Java et je constate un fonctionnement au niveau des classes que je ne comprends pas :

    je créer un vecteur v1 (0, 0)

    j'utilise une méthode set pour affecte à v1[0] la valeur a (par exemple 3)

    j'affiche v1, j'obtiens bien 3 et 0

    maintenant je change la valeur de a en faisant a=b*2 (avec b= 1/2 par exemple)

    je réaffiche v1 et j'obtiens (1,0) au lieu de mon 3,0 que j'avais affecté auparavant.

    voilà le code de ma class :

    public class Vector{
        private Rational[] vec;
    
        public Vector (int size){
            this.vec= new Rational[size];
        }
    
        public void setVec(Rational r, int i){
            if (i>vec.length) {
                throw new ArithmeticException("i is more than the length of this vector...");
            }
            else{
                vec[i]=r;
            }
    
        }
    
        public Rational[] getVec(){
            return this.vec;
        }
    
        public Rational getValue(int i){
            return this.vec[i];
        }
        
        public String toString(){
            String str="";
            for (int i=0; i<vec.length;i++){
                str = str + this.getValue(i) + ", " ;
            }
            return str;
        }
    }
    public class TestVector{
    
         public static void main(String[] args) {
            Vector v1 = new Vector(2);
            System.out.println("v1 = " + v1);
    
            Rational r = new Rational(2, 3);
            v1.setVec(r, 0);
            System.out.println(v1.toString());
            Rational r2 = new Rational(3, 2);
            r.mult(r2);
            System.out.println(v1.toString());
        }
    }


    Et le résultat sur la console :

    >java TestVector.java
    v1 = null, null,
    2 / 3, null,
    1, null,

    Je constate le même comportement lors de l'utilisation de mon getVec :

    import jdk.dynalink.linker.GuardingTypeConverterFactory;
    
    public class TestVector{
    
         public static void main(String[] args) {
            Vector v1 = new Vector(2);
            System.out.println("v1 = " + v1);
    
            Rational r = new Rational(2, 3);
            v1.setVec(r, 0);
            System.out.println(v1.toString());
            Rational r2 = new Rational(3, 2);
            r.mult(r2);
            System.out.println(v1.toString());
    
    
            Vector v2=new Vector(2);
            Rational r1 = new Rational(1,2);
            Rational r3 = new Rational(1,2);
            v2.setVec(r1, 0);
            v2.setVec(r3, 1);
            System.out.println(v2.toString());
    
            Rational a=v2.getValue(0);
            a.mult(r3);
            System.out.println(v2.toString());
    
        }
    }

    >java TestVector.java

    v1 = null, null,
    2 / 3, null,
    1, null,
    1 / 2, 1 / 2,
    1 / 4, 1 / 2,

    Il semblerait qu'un lien soit créé entre les variables...

    Auriez-vous des pistes à me donner pour me dire si ce comportement est normal ? Et si cela ne l'est pas, comment le gérer dans ma class ?

    Merci d'avance,

    -
    Edité par An-noix 26 septembre 2021 à 11:53:20

    • Partager sur Facebook
    • Partager sur Twitter
      26 septembre 2021 à 14:42:58

      Bonjour,

      Ton problème vient du fait que la classe Rationale est mutable. C'est à dire que ses objets sont modifiables.

      Dans ton tableau vec, tu mets une référence vers un objet r. Si tu modifies r à l'extérieur de v1, comme vec contient la référence vers r, tu vas voir cette modification.

      Ca s'appelle un effet de bord et c'est une source importante de bugs.

      Pour éviter ces effets de bords, tu as plusieurs solutions

      • soit rendre la classe Rationale immuable,
      • soit copier les fractions avant de les mettre dans le tableau.

      La solution immuable étant la plus sûre.

      Comme d'habitude en informatique on a un choix à faire. Ici, le trade-of, c'est entre la performance et la sécurité. La version mutable consommera moins de mémoire et sera plus rapide mais le risque de bugs sera élevé si elle est mal utilisée. La version immuable sera plus gourmande en mémoire mais éliminera les effets de bord.

      Perso, je préfère la solution immuable.

      • Partager sur Facebook
      • Partager sur Twitter
        26 septembre 2021 à 23:13:49

        Avec les objets mutables, ton  Rationnel ne represente pas la valeur d' un rationnel,  mais un "contenant" où on peut mettre/changer des rationnels.

        Ça sent le cours de c++ traduit bêtement en java au siècle précédent.

        Ligne 10, les indices valides vont de 0 à size-1

        -
        Edité par michelbillaud 26 septembre 2021 à 23:18:33

        • Partager sur Facebook
        • Partager sur Twitter
          28 septembre 2021 à 6:23:43

          Bonjour,

          Merci beaucoup pour vos explications. Je comprends bien pour la notion de copie des valeurs. Je vais me renseigner pour comprendre comment rendre une classe immuable.

          Bonne journée

          • Partager sur Facebook
          • Partager sur Twitter
            28 septembre 2021 à 7:58:04

            EDIT : oups, c'était trop tôt, et je me suis mélangé les pinceaux dans les approches Java et C++

            C'est simple :

            • Les champs de l'objet sont déclarés const
            • Ils sont initialisée par le constructeur
            • Il y a des assesseurs (get) mais pas de mutateurs (set)
            • Toutes les méthodes sont  const

            Donc dans toute sa vie, le contenu d'un objet reste inchangé.

            Il n'y a donc pas de méthode mult qui multiplie un rationnel par quelque chose. Si on a besoin d'une multiplication, c'est une opération qui a partir de 2 objets en fabrique un troisieme

            class Rationnel {
            
              Rationnel produit(const Rationnel &autre) const {
                 ...
              }
            };



            Reprenons :

            En C++, si on définit un type Rationnel, c'est pour avoir des VARIABLES qui contiennent des DONNEES qui représentent des rationnels.

            • En C++, les variables, il est souhaitable de pouvoir y affecter des valeurs, donc si on écrit
            Rationnel a{1,2}; //   1/2
            Rationnel b{1,3}; //   1/3
            
            a = b.plus(b);  // 2/3
            
            ça change le contenu de la variable a, en changeant la donnée contenue. Ce qui exclut que les champs d'un rationnel soient const.
            • en Java le problème se pose différemment, parce que les variables FONT REFERENCE à une donnée. Bref.

            reprenons calmement

            On veut encapsuler proprement la notion de rationnel. Faut se rappeler qu'un rationnel, c'est pas une paire de nombres (numérateur et dénominateur), c'est en fait une CLASSE de paires de nombres, ces paires représentant les même valeurs.

            Exemple  2/4, c'est le même rationnel que -3/-6.  Et le dénominateur du rationnel c'est 2, parce que la forme simplifiée (canonique) c'est 1/2.

            Bref, faut pas confondre rationnel et une écriture possible de ce rationnel sous forme de fraction. [Un aspect qui est est complètement salopé dans tous les cours de C++ qui prennent les rationnels comme exemple...]

            Bref, ça n'a pas trop de sens d'avoir des fonctions qui modifient indépendamment le numérateur ou le dénominateur., parce que ça peut changer l'autre. Si je modifie le numérateur de 1/6 en 12, non seulement le dénominateur devient 1 mais le numérateur que je viens de "mettre à 12" devient 3. C'est débile. Donc à la poubelle l'idée d'avoir des mutateurs genre setNumerateur setDenominateur.

            Après toutes ces précautions qui ne sont pas du luxe, revenons sur terre. Le fichier d'entête nous montre ce dont on a besoin de faire avec les rationnels;

            #ifndef RATIONNEL_HEADER_INCLUDED
            #define RATIONNEL_HEADER_INCLUDED
            
            class ExceptionConstructionRationnel {};
            
            class Rationnel
            {
            private:
                int my_num;
                unsigned int my_den;
            public:
                Rationnel();
                Rationnel(int num, int den);
                int numerateur() const;
                unsigned int denominateur() const;
                Rationnel plus(const Rationnel &autre) const;
            };
            
            #endif
            

            L'exception sera retourné quand on essaie de construire un rationnel avec dénominateur nul.

            Et c'est pas du luxe de commencer par écrire un programme de test unitaire pour les fonctionnalités attendues.

            #include <iostream>
            #include "Rationnel.hh"
            
            #include <cassert>
            
            void test_delegation(void)
            {
                std::cout << "# test délégation de constructeur" << std::endl;
                Rationnel a;
                assert(a.numerateur() == 0);
                assert(a.denominateur() == 1);
            }
            
            void test_normalisation(void)
            {
                std::cout << "# test normalisation" << std::endl;
                Rationnel a{-20, -30};
                assert(a.numerateur()  == 2);
                assert(a.denominateur() == 3);
            }
            
            void test_affectation(void)
            {
                std::cout << "# test affectation" << std::endl;
                Rationnel a{1, 2};
            
                assert(a.numerateur()  == 1);
                assert(a.denominateur() == 2);
            
                Rationnel b{2, 3};
            
                a = b;
            
                assert(a.numerateur()  == 2);
                assert(a.denominateur() == 3);
            }
            
            void test_somme(void)
            {
                std::cout << "# test somme" << std::endl;
                Rationnel a{1, 6};
                Rationnel b{1, 2};
            
                Rationnel c;
                c = a.plus(b);
            
                // resultat
                assert(c.numerateur()  == 2);
                assert(c.denominateur() == 3);
            
                // a et b inchangés (garanti par le const)
                assert(a.numerateur()  == 1);
                assert(a.denominateur() == 6);
            
                assert(b.numerateur()  == 1);
                assert(b.denominateur() == 2);
            }
            
            int main()
            {
                std::cout << "Debut tests..." << std::endl;
                test_delegation();
                test_normalisation();
                test_affectation();
                test_somme();
                std::cout << "Everything is OK!" << std::endl;
                return 0;
            }
            


            Reste plus qu'à se farcir l'implémentation. Le mieux est de se définir une fonction pgcd pour faire la simplification

            #include <string>
            #include "Rationnel.hh"
            #include "util.hh"
            
            
            Rationnel::Rationnel(int num, int den)
            {
                if (den == 0) {
                    throw ExceptionConstructionRationnel{};
                }
                // normalisation des signes
                if (den < 0) {  // normalisation signe
                    num = -num;
                    den = -den;
                }
                unsigned int div = pgcd(abs(num), den);
                my_num = num / div;
                my_den = den / div;
            }
            

            Le constructeur par défaut est une occasion de parler de la délégation de constructeurs, mais on pourrait s'en passer

            Rationnel::Rationnel()
                : Rationnel{0,1}
            {
            }
            
            // alternative
            
            /*
            Rationnel::Rationnel()
                : my_num{0}
                , my_den{1}
            {}
            */
            
            // ou encore si on ne connaît pas les listes
            // d'initialisation
            
            /*
            Rationnel::Rationnel()
            {
                my_num = 0;
                my_den = 1;
            }
            */
            
            
            





            -
            Edité par michelbillaud 28 septembre 2021 à 11:59:41

            • Partager sur Facebook
            • Partager sur Twitter

            valeur d'objet dans une class qui varie

            × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
            • Editeur
            • Markdown