Découvrir Java

Découvrir Java

Introduction

Dans cette première partie, nous allons découvrir Java sous la forme de petits programmes. Nous n’allons pas tout de suite chercher à faire de programmes complexes, mais nous allons utiliser des mécanismes de la Programmation Orienté Objet et observer leurs effets.

L’idée va être de se familiariser avec différents concepts: classes, instances, interfaces, héritage et même modèles de conception.

Nous allons faire collaborer des objets entre eux afin d’observer des comportements selon la manière dont ils sont assemblés.

Premiers pas

Bases du langage

Dans cette partie, des exemples sont donnés pour chaque élément de base du langage.

La documentation Java fournie par Oracle est disponible ici. Elle reprend l’ensemble des concepts abordés;

Types primitifs

Dupliquer la classe HelloWorld en une classe TypesPrimitifs (essayez un copier-coller).

Y insérer le code suivant:

int entier = 3;
System.out.print("entier: ");
System.out.println(entier);

boolean test = false;
System.out.print("booléen: ");
// Afficher cette valeur avec un System.out.println()

float flottant = 2.8f;
System.out.print("flottant: ");
// Afficher cette valeur avec un System.out.println()

//Voir le tableau ci-dessous

Déterminez pourquoi des erreurs de compilation apparaissent avec les types et certaines valeurs ci-dessous :

Type Valeurs à tester
byte 8 puis 127 puis 128 puis -128 puis -129
short -32768 puis -32769
int déterminez les valeurs possibles à utiliser
long 2L puis déterminez les valeurs possibles à utiliser
float 1.5f puis 1.5 puis déterminez les valeurs possibles à utiliser
double 3.8 puis 9.7d
char '\u0061' puis 'b' puis 'cd'
String "" puis "cd"
boolean false puis true

Pour chacun des types, trouvez quelle est la valeur par défaut.

Si besoin, vous pouvez trouver plus d’information sur les types primitifs ici

Tableaux

En Java, les types tableaux (array) sont indexés à partir de 0.

Ils sont déclarés grâce à des crochets: []

int[] tableauEntiers;

Il est possible de créer des tableaux pour tous les types primitifs, mais aussi pour des objets (par exemple le type String).

Pour créer un nouveau tableau, plusieurs possibilités.

Créez une classe Tableaux dans laquelle vous testerez les codes donnés ci-dessous.

La première, en initialisant le tableau puis chacun de ses éléments:

tableauEntiers = new int[3];
tableauEntiers[0] = 1 ; //premier élément
tableauEntiers[1] = 2 ; //second élément
tableauEntiers[2] = 3 ; //troisième élément

System.out.print("premier élément:");
System.out.println(tableauEntiers[0]);
//...

La seconde, en initialisant le tableau puis en lui attribuant un ensemble de valeur:

int[] tableauEntiers = {1_000, 2_345_678, 3, 4, 500000,
    600_000_000, 7_000_000};
System.out.print("premier élément:");
System.out.println(tableauEntiers[0]);
//...
Longueur d’un tableau

Pour connaître le nombre d’éléments dans un tableau, on utilise sa propriété length :

System.out.print("Nombre d'éléments:");
System.out.println(tableauEntiers.length);

Plus d’informations sur les tableaux sont disponibles ici

Exercice

Définir un tableau à deux dimensions contenant quatre String.

Afficher chacune de ces valeurs séparément.

Opérateurs

Nous allons expérimentez quelques éléments sur les opérateurs dans une nouvelle classe Operateurs (toujours dans le paquet com.example.hello).

Pour une référence plus complète, vous pouvez trouver des informations ici

Assignation

L’assignation de valeur se fait grâce au signe = :

Opérations arithmétiques

Les opérations arithmétiques simples sont résumées ici:

Opérateur Description
+ Addition (ou pour le type String : concaténation)
- Soustraction
* Multiplication
/ Division
% Modulo (reste de la division euclidienne)

Exercice

Testez les codes suivants et vérifiez leur validité (est-ce que le résultat produit est bien le résultat attendu ?)

System.out.println("Hello" + " " + "World");
System.out.println(  1 + 3);
System.out.println("1" + 3);
System.out.println(1.0 + 3);

int dividende = 24;
int diviseur = 5;
System.out.println("Le résultat de " +
    dividende +" % " + diviseur + " est " + dividende % diviseur);
System.out.println("Le résultat de " +
    dividende +" / " + diviseur + " est " + dividende / diviseur);

double dividendeFlottant = 24.0;
System.out.println("Le résultat de " +
    dividendeFlottant +" % " + diviseur + " est " + dividendeFlottant % diviseur);
System.out.println("Le résultat de " +
    dividendeFlottant +" / " + diviseur + " est " + dividendeFlottant / diviseur);
Comparaisons et opérations booléennes

Les opérateurs de comparaisons sont les suivants:

Opérateur Description
== égal
!= non égal (différent de)
> supérieur
>= supérieur ou égal
< inférieur
<= inférieur ou égal

Ils renvoient toujours une valeur booléenne (boolean).

Exercice

Testez les codes suivants et vérifiez leur validité (est-ce que le résultat produit est bien le résultat attendu ?)

int un = 1;
System.out.println("1 == 2 : "     + (1 == 2));
System.out.println("2 == 1 + 1 : " + (2 == 1 + un));
System.out.println("1 != 2 : "     + (1 != 2));
System.out.println("3 > 3 : "     + (3 > 3));
System.out.println("3 >= 3 : "     + (3 >= 3));
System.out.println("4 < 4 : "     + (4 < 4));
System.out.println("4 <= 4 : "     + (4 <= 4));
Opérations booléennes

Les opérations ET, OU et NON se font sur des valeurs booléennes:

Opérateur Description
|| OU
&& ET
! NON
Exercice

Testez les codes suivants et vérifiez leur validité (est-ce que le résultat produit est bien le résultat attendu ?)

int un = 1;
System.out.println("false || false : "     + (false || false));
System.out.println("false || true : "      + (false || true));
System.out.println("true  || true : "      + (true || true));
System.out.println("false && true : "      + (false && true));
System.out.println("true  && true : "      + (true  && true));
System.out.println("2 == 1 + un && true : "+ (2 == 1 + un  || true));
System.out.println("NON (2 == 1 + un && true) : "+ !(2 == 1 + un  || true));

Découverte du débogueur Eclipse

Reprenez chacun des codes précédents et lancez-le en utilisant le déboggueur Eclipse (Menu Run > Debug ou le bouton de la barre d’outils ayant l’icône correspondante)

Acceptez le passage dans la Perspective correspondante au mode Debug

Ajoutez un point d’arrêt sur la première ligne de votre méthode main et relancez le débogueur.

Visualisez les boutons Step into, Step over.

Objets et classes

Programmation Orientée Objet

Java est un langage Orienté Objet (OO). Cela signifie que programmer consiste à définir des entités qui vont contenir des données et des comportements. Le programme indique comment ces entités vont interagir entre elles.

Ces entités sont des objets.

Note: On parle de Programmation Orientée Objet (POO). En anglais: OOP (Object Oriented Programing)

Un peu de vocabulaire

Pour créer un objet, il faut un modèle (un patron, un moule).

Le modèle d’un objet est une classe.

À partir de ce modèle, on en créera un exemplaire : un objet est une instance d’une classe.

Parfois, on utilise le mot type pour désigner une classe. Par exemple, un objet de type Integer désigne une instance de la classe Integer.

Chaque objet possède des membres :

Une classe permet de définir ces membres (attributs et méthodes) pour tous les objets qui seront instanciés sur son modèle.

Une méthode est l’équivalent d’une fonction en programmation procédurale; sauf que cette fonction permet de manipuler les attributs de l’objet, indépendamment de ceux d’un autre objet.

Premières classes

Dans un premier temps, il vous est proposé de créer une fonction mathématique permettant de multiplier deux nombres entiers.

Le résultat de cette fonction sera stocké dans l’attribut resultat de l’objet

Classe Multiplication

Implémenter une méthode (que l’on nommera calculer) dans une classe nommée Multiplication

Voici un exemple à compléter:

package com.example.java;

public class Multiplication {
  int resultat ;
  public void calculer(int a, int b){
    resultat = a * b;
  }
}

Un peu d’UML : ci-dessous la représentation UML de cette classe

Multiplication
Multiplication

Classe principale

Pour tester cette classe, nous allons créer une nouvelle classe principale (qui contient une méthode main) dans laquelle on fera appel à la classe Multiplication.

Cette nouvelle classe se nommera Calculer:

package com.example.java ;

public class Calculer {
    public static void main(String[] args) {
        //Créer une instance de la nouvelle classe avec le mot clé `new`
        Multiplication premierCalcul = new Multiplication();

        // Créer une variable nommée resultat
        premierCalcul.calculer(3, 5); // appel de méthode

        // Ecrire le résultat du calcul dans la sortie standard
        System.out.println("Le résultat de 3 multiplié par 5:");
        System.out.println(premierCalcul.resultat);
    }
}
Instantiation

Dans ce programme, nous avons utilisé le mot clef new qui permet d’instancier un objet à partir d’une classe:

Multiplication premierCalcul = new Multiplication();

Ce mot clef new permet de faire appel au constructeur de la classe. Si aucun constructeur n’est explicitement déclaré dans une classe, le compilateur Java crée un constructeur par défaut, c’est-à-dire sans aucun paramètre.

La ligne ci-dessus stocke un pointeur vers l’instance créée dans la variable premierCalcul.

Cette variable est déclaré avec un type (la classe) Multiplication.

Appel de méthode

L’appel d’une méthode est similaire à l’appel d’une fonction dans un autre langage, à la différence près que la méthode est rattachée à un objet.

La ligne suivante permet donc d’appeler la méthode calculer qui est rattachée à l’instance pointée par la variable premierCalcul.

premierCalcul.calculer(3, 5);
Test du programme

Exécuter votre programme depuis Eclipse

Que constatez-vous ?

Classe Addition

Implémentez l’opération mathématique Addition. Elle permet d’additionner deux nombres entiers dans une classe nommée Addition, sur le même modèle que la classe Multiplication.

Testez-les avec un code qui devrait ressembler à ceci (toujours dans la classe principale Calculer:

Addition secondCalcul = new Addition(); //Instanciation
secondCalcul.calculer(3, 5); //appel de la
System.out.println("Le résultat de 3 ajouté à 5:");
System.out.println(secondCalcul.resultat);

Essayez d’appeler plusieurs fois la méthode additionner sur une même instance et observer l’effet sur l’attribut resultat.

Faites de même sur plusieurs instances différentes (stockées dans des variables troisiemeCalcul, quatriemeCalcul, ou calcul3, calcul4, calcul5, etc).

Que constatez-vous ? Qu’en déduisez-vous ?

Une piste: est-ce que les attributs resultat des différentes instances ont la même valeur ? Quel est l’intérêt de ceci ?

Utilisation du débogueur Eclipse

Reprenez chacun des codes précédents et lancez-le en utilisant le déboggueur Eclipse

Ajoutez un point d’arrêt sur la première ligne de votre méthode calculer et relancez le débogueur.

Visualisez les boutons Step into, Step over et Step return.

Visualisez la zone Variables (en haut à droite)

L’héritage

Une des particularité de la POO est qu’une classe peut hériter d’une autre classe. Cela a plusieurs avantages, notamment celui de permettre la réutilisation de code déjà écrit.

Ainsi, une classe peut hériter des comportements (méthodes) et données (attributs) d’une classe parente.

Création d’une classe parente

Nous allons observer cela en modifiant le programme réalisé précédemment. Pour cela, nous allons créer une classe Calcul qui contiendra un attribut resultat. Cet attribut ne sera pas déclaré une seconde fois dans les classes qui en héritent.

Fichier Calcul.java :

package com.example.java;

public class Calcul {
    int resultat ;
}

Création d’une classe fille

Fichier Multiplication.java :

package com.example.java;

public class Multiplication extends Calcul {
    public void calculer(int a, int b){
        resultat = a * b;
    }
}

En relançant le programme précédent, cela devrait continuer à fonctionner.

Le mot clé extends

Nous observons l’utilisation du mot clé extends qui signifie que la classe Multiplication hérite de la classe Calcul.

On peut dire ceci:

Un peu d’UML : voici un diagramme de classe qui représente cet héritage

Héritage Calcul
Héritage Calcul

On remarque ici que la classe Calcul hérite de la classe Object. La raison est simple: toute classe en Java hérite toujours de la classe Object. C’est logique, puisque tout est objet en Java.


Addition est un Calcul

Adapter la classe Addition pour correspondre au diagramme suivant:

Héritage Calcul Addition
Héritage Calcul Addition

Appel d’une méthode de la classe parente

Nous allons maintenant écrire une classe Soustraction qui va hériter de la classe Addition contenant la méthode suivante :

Il serait logique ici de dire que cette classe hérite d’Addition puisqu’une soustraction consiste à additionner au premier opérande l’opposé du second opérande : resultat = a + (-b)

Il est possible d’appeler une méthode de la classe parente (la classe de niveau supérieur) grâce au mot clé super:

super.calculer(3, -7); //appel de la fonction de la classe parente
  1. Ecrivez cette classe Soustraction et exécuter votre programme.
  2. Dessinez le diagramme UML correspondant.

Un peu d’abstraction

D’une manière générale, on peut considérer que Multiplication, Addition et Soustraction sont des calculs arithmétiques.

Ces classes ont toutes en commun une méthode nommée calculer. D’ailleurs, lors des appels de ces méthodes on avait toujours quelque chose de cette forme:

uneInstanceDeCalcul.calculer(3, 5);

Nous pouvons déclarer cette méthode directement dans la classe Calcul et nous allons la déclarer comme étant abstraite. Calcul sera une abstraction des autres opérations.

Pour cela, nous allons d’abord modifier la classe Calcul:

public abstract class Calcul {
    int resultat ;
    public abstract void calculer(int a, int b);
}

Puis nous allons remplacer les types des variables par le type Calcul dans la classe Calculer:

public class Calculer {
    public static void main(String[] args) {
        Calcul premierCalcul = new Multiplication();
    premierCalcul.calculer(3, 5); // appel de méthode
    System.out.println("Le résultat de 3 multiplié par 5:");
    System.out.println(premierCalcul.resultat);

    //...
    }
}

On constate ici que la compilation passe et que le programme se comporte exactement de la même manière.

Alors quel est l’intérêt de procéder ainsi ? Essayez de remplacer l’un des constructeurs des sous-classes de Calcul par l’un des autres et ré-exécutez le programme pour observer les effets.

Le diagramme de classes ci-dessous représente cette classe abstraite, ajoutez-y la classe Soustraction:

Calcul abstrait
Calcul abstrait
Généralisation, héritage

Dans l’exemple précédent, on fait une généralisation: Calcul généralise les classes Multiplication, Addition et Soustraction.

A l’inverse, les classes Multiplication, Addition et Soustraction héritent de la classe Calcul: on dit aussi que ces classe spécialisent la classe Calcul. Elles sont spécialisées pour une opération donnée.


Nous allons maintenant observer l’héritage dans la documentation de Java.

Découverte de la documentation Javadoc

Ouvrez la documentation JAVA 8 qui se trouve ici : https://docs.oracle.com/javase/8/docs/api/index.html?overview-summary.html

Les chaînes de caractères en Java sont gérées par la classe String.

Essayez de trouver la classe String dans cette documentation.

Faites un diagramme UML représentant cette classe (avec au moins sa classe parente).

Toute classe Java hérite toujours de la classe Object. Cette classe contient deux méthodes intéressantes: getClass() et toString()

Nous allons expérimenter cela dans ce qui suit.

La méthode toString()

Nous allons partir de notre programme Hello World pour lui ajouter quelques fonctionnalités.

Pour cela, nous allons dupliquer cette classe avec notre explorateur de fichier (sans utiliser les fonctions d’Eclipse) et en renommant le fichier Exemple.java (toujours dans le même répertoire).

Une fois cela fait, essayez de compiler et de lancer le programme. Que constatez-vous ?

Vous allez modifier le main(String[]) en remplaçant le system.out.println comme suit:

public static void main(String[] args) {
    Object instance = new Exemple();
    System.out.println(instance); // Affiche
}

Lancez le programme (en sélectionnant la classe Exemple comme classe à exécuter). Que constatez-vous ?

Maintenant, vous allez modifier la classe en ajoutant une méthode nommée toString() :

  public String toString(){
    return "Je suis Exemple:" + super.toString();
  }

Dans cet exemple, nous venons de concaténer deux chaînes de caractères. C’est l’opérateur + qui permet cela : l’instruction "ab" + "cd" renvoie la valeur "abcd".

Relancez le programme. Que constatez-vous ?

À nouveau, vous allez modifier la classe en modifiant la méthode toString() :

  public String toString(){
    return "Je suis Exemple (" + getClass() +"  ):"+ super.toString();
  }

Relancez le programme. Que constatez-vous ?

Avez-vous des questions ? A quoi sert la méthode toString() ?

Un peu d’UML

Faites un diagramme UML représentant cette classe Exemple ainsi que la classe Object. Représentez également la classe String sur ce diagramme


Représentation textuelle d’une Personne

Nous allons définir une classe personne comme suit:

public class Personne {
    String nom;
    String prenom;
    String dateNaissance;
    String adresseMail;
    String telephone;

    public static void main(String[] args) {
        Personne utilisateur = new Personne();

        utilisateur.nom           = "McEnroe";
        utilisateur.prenom        = "John";
        utilisateur.dateNaissance = "10/10/1960";
        utilisateur.adresseMail   = "johnmcenroe@usa.tennis.com";
        utilisateur.telephone     = "+001 203 204 205";

        System.out.println(utilisateur.nom);
        System.out.println(utilisateur.prenom);
        System.out.println(utilisateur);
    }
}

Exécutez ce programme. Que constatez-vous ?

Les attributs de cette classe sont:

Aux attributs de cette classe, on va pouvoir rajouter des méthodes:

public class Personne {
    String nom;
    String prenom;
    String dateNaissance;
    String adresseMail;
    String telephone;

    public static void main(String[] args) {
        Personne utilisateur = new Personne();

        utilisateur.nom           = "McEnroe";
        utilisateur.prenom        = "John";
        utilisateur.dateNaissance = "10/10/1960";
        utilisateur.adresseMail   = "johnmcenroe@usa.tennis.com";
        utilisateur.telephone     = "+001 203 204 205";

        System.out.println(utilisateur.nom);
        System.out.println(utilisateur.prenom);
        System.out.println(utilisateur);
    }
  public String getNomPrenom(){
    return nom+" "+prenom; //Concaténation des chaines nom, " " et prenom
  }

  public String getPrenomNom(){
    return prenom+" "+nom;
  }
}

Ces méthodes sont des méthodes d’instance: elles sont liées à un exemplaire de la classe Personne. On peut les utiliser en ajoutant à la fin de la méthode main(String[]) les lignes suivantes:

System.out.println(utilisateur.getNomPrenom());
System.out.println(utilisateur.getPrenomNom());

Que constatez-vous ?

Nous allons maintenant modifier l’affichage obtenu avec la ligne suivante du main(String[]):

System.out.println(utilisateur); // Dépend de la méthode toString()

Pour cela, nous surchargeons la méthode toString qui était déjà définie dans la classe Object:

public String toString(){
  return nom+" "+prenom+" "+dateNaissance+" "
      +adresseMail+" "+telephone;
}

Que constatez-vous ?

Représentation textuelle d’un calcul

Vous allez maintenant reprendre les classes Calcul et ses sous-classes. Modifiez la méthode toString() de cette classe pour que le code suivant:

Calcul calcul = new Multiplication();
calcul.calculer(4,8);
System.out.println(calcul);

Produise exactement la sortie suivante une fois exécutée:

Le résultat de l'opération Multiplication de 4 et 8 donne 32.

Vous avez la possibilité de rajouter de nouveaux attributs à vos classes afin de stocker (par exemple) les opérandes ou toute information utile.

Manipulation de chaînes de caractères

Dans la classe Personne, nous avons utilisé des chaînes de caractères avec l’opérateur + : nous avons concaténé des chaînes de caractères entre elles.

Nous allons utiliser ici la classe String afin d’en découvrir quelques aspects importants.

Quelques expériences étonnantes

Exécuter le programme suivant:


public class ExerciceChaine {
    public static void main(String[] args) {
        String uneChaine = "Bonjour" ;
        String autreChaine = "Bonjour" ;

        ExerciceChaine ec = new ExerciceChaine();
        System.out.println("-- Deux chaines identiques");
        ec.testChaines(uneChaine, autreChaine);

        System.out.println("-- Une nouvelle instance");
        autreChaine = new String("Bonjour");
        ec.testChaines(uneChaine, autreChaine);

        System.out.println("-- Une concaténation simple");
        autreChaine = "Bon" + "jour" ;
        ec.testChaines(uneChaine, autreChaine);

        System.out.println("-- Une concaténation avec des variables différentes");
        autreChaine = "Bon" ;
        String troisiemeChaine = "jour";
        autreChaine = autreChaine + troisiemeChaine;
        ec.testChaines(uneChaine, autreChaine);
    }
    public void testChaines(String a, String b){
        if(a == b){
            System.out.println("Les chaînes '"+ a + "' et '"+b+ "' sont les mêmes");
        }
        else{
            System.out.println("Les chaînes '"+ a + "' et '"+b+ "' sont différentes");
        }
    }
}

Remplacez la méthode testChaines(String, String) par la suivante:

public void testChaines(String a, String b){
  if(a.equals(b)){
    System.out.println("Les chaînes '"+ a + "' et '"+b+ "' sont les mêmes");
  }
  else{
    System.out.println("Les chaînes '"+ a + "' et '"+b+ "' sont différentes");
  }
}

Que constatez-vous ?

Allez voir dans la Javadoc à quoi correspond la méthode equals(Object).

Pourquoi est-elle utile ?

Quelques méthodes pour utiliser les String

Exécutez le programme suivant:

public static void main(String[] args) {
  String chaine = "Quelques caractères";

  System.out.println(chaine.length());
  System.out.println(chaine.substring(2));
  System.out.println(chaine.substring(5,10));
  System.out.println(chaine.startsWith("Que"));
  System.out.println(chaine.startsWith("que"));
  System.out.println(chaine.endsWith("res"));
  System.out.println(chaine.endsWith("re"));
  System.out.println(chaine.indexOf("ra"));
  System.out.println(chaine.toLowerCase());
  System.out.println(chaine.toUpperCase());
}

Aidez-vous de la Javadoc pour comprendre ce que font chacune de ces méthodes.

En utilisant la chaîne " Je suis une chaîne moi aussi " (attention aux espaces en début et fin de chaîne), testez les effets des méthodes suivantes:

Que constatez-vous ? Est-ce logique ?

Parcours d’une chaîne et boucles

Nous allons utiliser une boucle simple pour récupérer chaque caractère d’une chaîne.

for (int i = 0 ; i < chaine.length(); i++) {
  System.out.println(chaine.charAt(i));
}

Analysez le code ci-dessus et posez des questions si vous ne le comprenez-pas.

Exercice: palindrome

Ecrivez un programme qui détermine si une chaîne entrée au clavier est un palindrome (un mot qui se lit dans les deux sens).

Pour récupérer des chaînes de caractères entrés par un utilisateur voir l’annexe RecuperationDeDonneesDansLaConsole

Créer des objets

La création d’objet en Java se fait avec le mot clef new.

L’opération effectuée avec new est appelée une instantiation: on crée une instance (c’est-à-dire un “exemplaire”) d’une classe.

Cette instance est un exemplaire indépendant des autres objets de cette classe.

Création d’une nouvelle classe

Vous allez implémenter une classe nommée Fleur dans le paquet com.example.java.nature

Cette classe se fera sur le modèle suivant:

Diagramme de classe: Fleur
Diagramme de classe: Fleur

Implémentez cette classe de sorte que toString() renvoie une valeur ressemblant à nomDeLaFleur(3) (avec nom = "nomDeLaFleur" et identifiant = 3

Instanciez cette classe à partir de la méthode main(String[]) de Exemple et affichez cette instance avec un System.out.println(String).

Que constatez-vous ?

Est-on limité par quelque chose ici ?

Constructeur avec paramètres

Pour pouvoir créer la classe Fleur, il existe un constructeur par défaut. Son défaut est qu’il ne permet pas de modifier les valeurs des attributs.

Créer un nouveau constructeur sur le modèle suivant:

public Fleur(String nom, int identifiant){
  this.nom = nom;
  this.identifiant = identifiant;
}

Essayez d’utiliser ce constructeur et d’afficher l’instance depuis la méthode main(String[])

Créez ainsi plusieurs instances de Fleur et affichez-les.

Utilité du constructeur

Un constructeur permet d’instancier un objet.

S’il est doté de paramètres, il permet de définir les valeurs des attributs (par exemple) au moment de l’instantiation.