Date de première publication : 2024/08/28
La première séance est une séance d'introduction au langage avec :
- une introduction aux outils et un "hello world"
- la manipulation de variables non objet (primitive)
- la manipulation de variables objet
- la création de votre première classe personnalisée
Bonjour monde !!
Préalable
Il existe différentes versions de Java, les versions Long Term Support (8, 11, 17 et 21 - la prochaine doit sortir en 2025) et les versions intermédiaires, plus fréquentes (la derniere version en date est la 22 sortie en mars 2024 et la prochaine, la 23, sort le 17 septembre)...
Pour pouvoir travailler, il faut vérifier que vous avez un environnement correctement installé, on peut vérifier cela en tapant en ligne de commande les instructions suivantes :
$ javac -version
$ java -version
Vous devez avoir des versions compatibles et s'il vous manque javac
il faudra installer un JDK Standard Edition.
Les debians de l'ISIMA embarquent la LTS 17.0.6 d'openJDK.
La documentation des classes prédéfinies est consultable en ligne. Il faut juste choisir la bonne version et placez celle-ci dans vos favoris de navigation :
- Documentation Java 8
- Documentation Java 17 (version installée à l'ISIMA)
- Documentation Java 21
- Tutoriel Java 8
Le dernier lien est un tutoriel qui explique pas mal de choses sur Java et l'objet. Seul bémol, il est resté en version 8.
Premier programme
Créez le fichier texte suivant (l'extension est importante) :
$ code prems.java
et mettez le code suivant :
/* ma première classe */
public class Exemple1 {
public static void main(String[] argv) {
// afficher un message
System.out.println("Bonjour les ZZ2 !");
}
}
Vous l'avez compris, l'idée est de créer une classe Exemple
avec un méthode main()
qui affiche un petit bonjour sur la console !
Le langage Java n'est pas un langage de script. Le fichier n'est donc pas directement "interprétable" comme python ou bash. Pour compiler, il faut taper :
javac prems.java
Vous avez votre première erreur de compilation java ! Le fichier doit obligatoirement porter le nom de la classe
javac Exemple1.java
Cette fois, il n'y a plus d'erreur, jetez donc un coup d'œil à votre répertoire.
Un nouveau fichier apparaît d'extension .class
. Vous pouvez essayer d'exécuter ce fichier :
$ ls
$ ./Exemple1.class
Vous avez besoin d'un deuxième programme pour exécuter du code Java. Il faut faire appel à la machine virtuelle Java (JVM) :
java Exemple1
Vous pouvez réaliser les choses suivantes :
- modifier le texte affiché
- faire différents appels à la méthode
System.out.println()
. Essayer de mettre du code en dehors de la méthodemain()
- renommer la méthode
main()
enmain2()
par exemple - enlever le mot-clé
static
demain()
- enlever le mot-clé
public
demain()
- enlever le paramètre de
main()
Manipulation de variable de type non objet
On crée un nouveau fichier/classe pour manipuler une variable non objet de type int
:
public class Exemple2 {
public static void main(String[] argv) {
int i = 0;
i = i + 1;
i += 1;
i *= 2;
System.out.println(i);
System.out.println(++i);
System.out.println(i);
System.out.println(i++);
System.out.println(i);
i = (int) 10.6;
System.out.println(i);
i = 50;
while (i>0) {
System.out.println(i);
i %=2;
}
for(int j = 3; j <10 ; j+=2)
System.out.println(j);
// if (!i)
// System.out.println("i est nulle");
}
}
Vous pouvez constater que la manipulation d'une variable entière est très similaire à celle du C. Il y a un petite différence que vous allez rencontrer lorsque vous décommenterez les deux dernières lignes.
En Java, il y a un type boolean
pour gérer les valeurs booléennes true
ou false
. Il faut donc écrire explicitement les tests pour que cela marche.
Manipulation d'objets de classes prédéfinies
StringBuffer
Il y a plus de 4400 classes prédéfinies en Java. Pour découvrir le langage, on va s'intéresser
à l'une d'entre elles : StringBuffer
.
Cette classe permet de manipuler une chaîne de caractères modifiable.
Exécutez le code suivant :
public class Exemple3 {
public static void main(String[] argv) {
StringBuffer s1 = new StringBuffer("Bonjour");
System.out.println(s1.toString());
System.out.println(s1.length());
System.out.println(s1.capacity());
s1.append(" les ZZ");
s1.append(2);
System.out.println(s1.toString());
System.out.println(s1.length());
System.out.println(s1.capacity());
StringBuffer s2 = s1;
s2.append("!");
System.out.println(s1.toString());
System.out.println(s1); // Alors ?
// s2 = new StringBuffer("plus rien à voir");
// System.out.println(s1);
// System.out.println(s2);
// inverser et afficher la chaine s1
}
}
La ligne 4 est complexe car elle fait trois opérations distinctes :
- déclaration de la référence
s1
. Ce n'est pas un objet même s'il on le dit souvent par abus de langage. Cela ressemblerait plutôt à un pointeur si on devait le caractériser. - l'opérateur
new
crée un objet de typeStringBuffer
avec un paramètre. - l'objet est ensuite affecté à la référence
s1
.
La ligne 6 permet d'afficher l'objet référencé par s1
sur la sortie standard. On verra plus tard ce qui se passe exactement.
length()
,capacity()
et append()
sont des méthodes que l'on peut appeler sur l'objet référencé par s1
.
Les méthodes sont appelées grâce à l'opérateur .
Ainsi s1.length()
renvoie le nombre de caractères utilisés de l'objet référencé par s1
La ligne 17 crée un alias pour l'ojet référencé par s1
. Cela permettra de manipuler le même objet
par un nom ou par un autre. La ligne 20 permet de vérifier que c'est bien le cas.
Il ne vous reste plus qu'à décommenter la fin du code pour voir ce qui se passe.
Trouvez maintenant dans la documentation officielle comment inverser la chaîne de caractères s1
et affichez-là !
Référence non utilisée
On vient de voir qu'en Java, on ne manipule pas d'objet directement mais qu'on le fait grâce à une référence.
La référence n'est pas toujours associée à un objet en mémoire, il y a donc une valeur spéciale pour cela : null
.
Modifiez le début du programme précédent comme suit :
public class Exemple3 {
public static void main(String[] argv) {
StringBuffer s1 = null;
System.out.println(s1.toString());
s1 = new StringBuffer("Bonjour");
System.out.println(s1);
// ...
}
}
Ce code génère toutefois une erreur à l'exécution car, à l'instar des pointeurs, on ne peut appeler une méthode sur une référence nulle : s1.toString()
n'a pas de sens.
On obtient alors l'équivalent d'un segmentation fault cher au C
Corrigez l'erreur en testant la valeur de la référence pour éviter l'appel si cette référence est nulle.
if (s1 != null) System.out.println(s1.toString());
La référence en tant que variable locale doit toujours être initialisée. Dans le cas contraire, cela ne compilera pas.
Enlevez l'initialisation à la ligne 4 pour voir ce que cela fait : StringBuffer s1;
Une référence peut être mise à null
à tout moment. Cela signifie simplement que l'on n'a plus besoin de l'objet anciennement référencé
String
La classe String
est très utilisée car elle permet de manipuler des chaines de caractères mais celles-ci
ne sont plus modifiables après leur création. Ne cherchez pas les méthodes pour les modifer, ce n'est pas possible.
Vérifions tout cela :
public class Exemple4 {
public static void main(String[] argv) {
String s1 = new String("essai");
System.out.println(s1.concat(" de concatenation"));
System.out.println(s1);
String s2 = new String("essai");
System.out.println(s2.replace('s', 'Z'));
System.out.println(s2);
// s2 = new String("changement valide");
// System.out.println(s2);
}
}
Les lignes 6 et 10 montrent respectivement que les chaines référencées par s1
et s2
ne sont pas modifées et que les lignes précédentes ont généré des "copies".
Décommentez les lignes 12 et 13. À votre avis, pourquoi ces lignes sont-elles valides ? Que se passe-t-il réellement ?
L'objet pointé par s2
est bien immuable une fois qu'il est créé mais s2
n'est pas un objet, donc s2
est modifiable
Création de votre première classe
Vous allez créer (par étapes) et instancier votre première classe "maison" dont le diagramme UML est le suivant :
Joueur |
- nom : chaine - score : entier - actif : booléen - compteur : entier |
+ constructeur(s) + afficher() + les getters + les setters + getCompteur() : entier |
class Joueur {
int score;
}
public class Exemple5 {
public static void main(String[] argv) {
// instanciation d'un joueur
}
}
- Récupérez et compilez le code ci-dessus. Listez votre répertoire. Que remarquez-vous ?
- Ajoutez un attribut
nom
de typeString
- Instanciez un objet
joueur1
dansmain()
- Affichez les attributs de
joueur1
dansmain()
- Faites la même chose avec un deuxième objet
joueur2
Les attributs ont des valeurs par défaut quand les objets sont instanciés en JAVA (les variables locales ne sont pas initialisées par défaut)
Encapsulation
- Mettez les attributs en
public
, compilez, exécutez - Mettez maintenant les attributs en
private
, compilez, exécutez - Pour corriger, créez deux méthodes
getScore()
etgetNom()
qui renvoient les valeurs des attributs concernés. Adaptez le code dansmain()
- Nous allons permettre de changer les valeurs des attributs. Créez deux méthodes
setScore()
etsetNom()
- Vérifier que le changement de valeur des attributs est possible pour les joueurs.
Mettre les attributs en privé et écrire les getters et les setters, si c'est pertinent, permet de respecter le principe d'encapsulation.
Il est un peu pénible d'écrire de répéter les appels aux différents attributs.
- Ajoutez un méthode publique
afficher()
à la classeJoueur
qui affiche sur la sortie standard l'état de l'objet sous la forme "nom [ score ]""
Constructeur
On va maintenant initialiser les attributs à la construction de l'objet en dotant la classe d'un constructeur.
- Ajoutez un constructeur qui prend un argument : le nom du joueur. C'est une méthode publique qui porte le nom de la classe et qui n'a pas de type de retour.
- Compilez et exécutez
Le code ne compile plus. En effet, main()
fait appel à un constructeur qui n'existe pas
ou plutot n'existe plus. Le compilateur Java fournit un constructeur sans argument par défaut si celui-ci n'est pas fourni.
Ce n'est plus le cas dès qu'un constructeur - quel qu'il soit - est fourni. Si on en a également besoin, il faut le fournir.
- Modifiez les instanciations de
joueur1
et dejoueur2
pour que le code soit fonctionnel. - Ajoutez un deuxième constructeur - celui qui n'a pas de paramètre - et vérifiez que les attributs sont correctement initialisés avec un
joueur3
initialisé avec ce constructeur. - Ajoutez un nouvel attribut
actif
de typeboolean
. Cet attribut est fixé àtrue
à l'initialisation des joueurs - Ajoutez les accesseurs
setActif()
etisActif()
pour permettre la lecture et l'écriture de cet attribut. Adaptez également la méthodeafficher()
Vous avez probablement dupliqué l'initialisation de l'attribut dans les différents constructeurs. Nous allons maintenant éviter celle-ci en utilisant le fait qu'un constructeur puisse en appeler un autre.
Si l'appel est fait correctement, il n'y a pas de deuxième instanciation. C'est une facilité de syntaxe.
class Joueur {
// constructeur sans argument
public Joueur() {
this("noname"); // appel du constructeur avec paramètre
// l'appel doit être la premiere instruction du constructeur
}
// constructeur qui fait le boulot
public Joueur(String nom) {
// ... travail à faire
}
}
Attribut de classe
Nous allons maintenant nous intéresser à un attribut classique : il s'agit de mémoriser le nombre de joueurs instanciés.
- Ajoutez un attribut de classe privé
compteur
à la classeJoueur
. Ce type d'atttribut est en général initialisé à la déclaration (même si là aussi il y a une initialisation avec valeur par défaut). - Ajoutez une méthode de classe publique
getCompteur()
pour lire la valeur du compteur. - Affichez ce compteur entre chaque instanciation de la classe
Joueur
. Que constatez-vous ? - Corrigez le code pour avoir le comportement attendu.
- Modifiez le code de la méthode
afficher()
pour que ce nombre soit également affiché !
Un attribut de classe peut être utilisé dans une méthode classique ou dans une méthode de classe. Un attribut classique ne peut être utilisé dans une méthode de classe.
- 1
- 2
- 3
- 4
- 5
- 6
- 7