Java pour les nuls (moi, donc) - JD and Co

Java pour les nuls (moi, donc)

Non non, je ne fais pas une crise d’infériorité, c’est juste que là… je comprend pas. J’aime bien donner des cours, ça permet de se poser des questions que jamais on ne se poserait en prod, et de tomber sur des trucs tout bêtes, limite de débutants, mais qui bloquent bien ! J’ai deux cas a soumettre à votre sagacité, des trucs tous cons, et j’avoue que.. je n’arrive pas à les expliquer… Si vous avez des idées, surtout n’hésitez pas !

1) Le mystère des comparaisons de String

La classe la plus banale de Java : String. Dans la Javadoc, ils précisent bien que

s=”coucou”;

et

s=new String(“coucou”);

sont équivalents.

Eh ben…regardez :

String s1=”bob”;
String s2=”bob”;
String s3=new String(“bob”);
if(s1==s2)
System.out.println(“ca marche sans le new”);
if(s1==s3)
System.out.println(“ca marche avec le new”);

Le premier test fonctionne, et pas le second ! Je sais qu’il ne faut pas faire l’erreur d’utiliser ==, mais bien .equals, mais là il s’agit de comparer les pointeurs.. Je m’étonne en fait que le premier test fonctionne.

2) L’héritage perdu

Soit une classe mère :

public class Mere {
public Mere()
{
System.out.println(“mere sans param”);
}

public Mere(String s)
{
System.out.println(“mere “+s);
}
}

et une classe Fille…vide :

public class Fille extends Mere
{
}

Testez :

Mere m=new Mere();
Mere m2=new Mere(“bla”);
Fille f=new Fille();
//Fille f2=new Fille(“bli”);

La dernière ligne est en commentaire, et pour cause : elle provoque une erreur du compilateur ! “Constructeur inexistant”. En revanche, la ligne “Fille f=new Fille()” appelle bien le constructeur de la classe mère, et pas un constructeur implicite. Pourquoi hériter d’un constructeur et pas de l’autre ?

Alors, vos avis ?

Update : j’ai compris pour le cas 2 ! Merci à Jib pour m’avoir aiguillé sur la voie de la rédemption !

Alors, je vous explique : ce qui me paraissait super bizarre, c’est que Fille sache bien exploiter le constructeur sans paramètre de la classe mère, mais pas l’autre ! C’était quoi, de la ségrégation ?

Je ne voyais pas en quoi le principe du “constructeur implicite” jouait , puisqu’on héritait bien de quelque chose, il n’y avait rien d’implicite. Jib a toutefois bien fait d’insister. Suivez le raisonnement exact de Java :

  1. On n’hérite PAS des constructeurs lors d’un mécanisme d’héritage. Je l’avoue, je l’avais complètement oublié. Mais ce qui m’a aidé dans cet oubli, c’est le fait que la classe Fille “hérite” quand même d’un des constructeurs de la classe mère, du moins en apparence. Mais…
  2. …vu que la classe fille n’hérite d’aucun constructeur, et qu’elle n’en possède pas, la règle rappelée par Jib s’applique : un constructeur implicite est mis en place.
  3. Deuxième effet kiss-cool : le constructeur implicite, contrairement à ce que je croyais, ne reste pas sans rien faire. En fait, il a un rôle bien particulier : il appelle, implicitement, le constructeur sans paramètre de la classe mère.
  4. Vu que la classe mère a quant à elle un constructeur sans paramètre bien réel, ce dernier est appelé. CQFD.

Voilà pourquoi ma classe Fille se retrouve “héritant” du constructeur sans paramètre de ma classe Mere, et pas de l’autre. Tout simplement parce qu’il n’y a pas d’héritage direct des constructeurs, mais une construction implicite qui appelle implicitement un constructeur parent. Pffiou !

Update 2 : Vu que, quand yen a marre, ya Malabar, j’ai employé les grands moyens pour comprendre cette histoire d’affectation de String, et ça a été l’occasion pour moi de faire un truc que je voulais faire depuis longtemps : décompiler du Bytecode !

On utilise pour cela l’instruction javap, qui prend en entrée un fichier .class, et qui vous le “désassemble” pour obtenir un byte code plus ou moins lisible.

Tentons de désassembler le code suivant :

String s1=”bob”;
String s2=”bob”;
String s3=new String(“bob”);

Cela donne le code suivant (j’ai fait trois paragraphes correspondant aux 3 instructions) :

0: ldc #16; //String bob
2: astore_1

3: ldc #16; //String bob
5: astore_2

6: new #18; //class java/lang/String
9: dup
10: ldc #16; //String bob
12: invokespecial #20; //Method java/lang/String.”<init>”
15: astore_3

On a donc la réponse : les deux chaînes s1 et s2 pointent bel et bien sur le même espace mémoire (#16) tout simplement parce que le compilateur Java a su détecter que la chaîne utilisée était bien la même dans les deux cas. Pour preuve, en ligne 10, c’est à nouveau cette adresse qui est chargée dans le registre pour pouvoir appeler le constructeur de String (j’ai essayé de faire 3 tonnes d’affectations entre la déclaration de s1 et s2, ça marche pareil, il retombe sur ses pattes).

Autre enseignement de ces quelques lignes : dans les affectations de type s=”toto”, il n’y a pas de construction explicite de l’objet String qui est effectuée (pas d’appel à “new”), mais simplement le chargement d’une référence vers un espace mémoire représentant ce String, et qui va être partagé par tous ceux qui contiennent la même chaine.

Pour les développeurs débutants, c’est super piégeux, car le fait que s1==s2 donne le résultat attendu peut faire croire qu’on est dans le bon, alors que ça ne marchera plus dès lors qu’on manipulera des String générés par des éléments externes (une lecture dans une base, par exemple).

Pour les développeurs un peu plus aguerris, ça nous amène à la réflexion suivante : en fait, les objets String ne sont pas immuables par une volonté particulière, mais simplement par nécessité : il serait super risqué de donner la possibilité de modifier ces “espaces mémoires mis en commun”.

Reste une question : est ce que ces espaces communs sont des instances de String à part entière, ou bien des simulâcres de classes pour donner une cohérence à l’organisation “100% classes” de String ? Je ne sais pas encore…

Update 3 : une doc intéressante fournie par un de mes stagiaires, merci Michel !

On y apprend deux choses :

  • String est vraiment une classe à part, elle est considérée comme étant un “literal”, au même titre qu’un type scalaire (int…), son comportement est donc assez différent d’une classe classique que l’on instancie
  • L’article m’a aiguillé sur la méthode intern() de la classe String qui confirme ce que je pensais : Java gère en interne un “pool” de chaînes de caractères afin de repérer les chaînes similaires et éviter les doublons d’instance.

A noter qu’apparemment, C# fonctionne de la même manière.

5 réflexions au sujet de “Java pour les nuls (moi, donc)”

  1. Pour le premier cas:
    s1 et s2 sont équivalentes pour ta jvm (java va capter que s2 est la même chose que s1 et donc ne pas instancier lui même un nouvel objet)
    s3 est un objet a part entière

    l’opérateur == ne compare pas DU TOUT la chaîne de caractère mais bien l’objet, s1 == s2 et s1.equals(s3) mais pas s1 == s3

    Euh j’ai été assez clair ? (a condition que je ne raconte pas n’importe quoi, mais je crois pas 😉 )

    Pour le point 2:
    je dirais que java sait toujours instancier une classe avec un constructeur vide (ça lui vient de Object), mais dans les cas d’héritage il faut préciser que tu veux instancier la classe parente avec super() ou dans ce cas super(s)

    Ouala, en espérant ne pas avoir raconté n’importe quoi non plus (mais je ne crois toujours pas 😉 )

    ++

  2. Merci Jib mais tu as dit n’importe quoi 😀

    je plaisante, toutes les remarques sont utiles !! Pour le premier cas, je sais bien que s1==s2 teste la valeur des pointeurs, donc la position en mémoire de l’objet et pas son contenu, mais ca n’explique pas tout.. en fait, c’est plus le fait que le premier test soit a “true” qui m’étonne.

    tu as peut être raison, la jvm doit optimiser l’occupation mémoire et capter que vu que le contenu est le même, on peut économiser une instance. mais dans ce cas la jvm est super super forte car meme si je met un gros traitement entre la déclaration de s1 et celle de s2, ça marche kan meme !

    pour le point 2, en revanche, je ne pense pas que ton explication soit la bonne, car en utilisant les println, je m’aperçois que l’on hérite bien du constructeur de la classe mère, il ne s’agit pas d’un constructeur implicite qui ne ferait rien. du coup, pourquoi hériter d’un constructeur et pas de l’autre ?

  3. Re,
    je me suis un peu documenté sorti du boulot histoire de plus raconter n’importe quoi 😀

    alors pour le point 2:
    “You don’t have to provide any constructors for your class, but you must be careful when doing this. The compiler automatically provides a no-argument, default constructor for any class without constructors. This default constructor will call the no-argument constructor of the superclass. In this situation, the compiler will complain if the superclass doesn’t have a no-argument constructor so you must verify that it does. If your class has no explicit superclass, then it has an implicit superclass of Object, which does have a no-argument constructor.”

    depuis http://java.sun.com/docs/books/tutorial/java/javaOO/constructors.html

    et surtout:
    “If a constructor does not explicitly invoke a superclass constructor, the Java compiler automatically inserts a call to the no-argument constructor of the superclass. If the super class does not have a no-argument constructor, you will get a compile-time error. Object does have such a constructor, so if Object is the only superclass, there is no problem.”

    depuis http://java.sun.com/docs/books/tutorial/java/IandI/super.html

    Et pour le point 1: bon je trouve pas la mais je pense pas être très loin, avec son type immutable et les restes de cours de compilation je pense qu’il doit pouvoir garder une référence vers les chaines définies a la mano (un peu comme si c’était statique grosso modo), c’est intéressant je vais tenter de trouver la doc qui va bien…

    A bientot 🙂

  4. Bah voila ça c’est fait !

    Je suis content de ne pas avoir raconté n’importe quoi, et j’ai vraiment appréciée ton explication pour le point 1 (j’étais pas dans le faux mais tu as formalisé pile poil ce que je n’arrivais pas à mettre sur papier !

    Autant je n’aime pas les sudokus mais je suis partant pour une prochaine énigme javaesque…

Laisser un commentaire