Initiation au Java

Tags :
  • MON
  • 2023-2024
  • temps 3
  • programmation
  • Java
Auteurs :
  • Agathe Rabachou

Ce MON a pour but de m'initier aux bases du langage de programmation Java.

Niveau

  • Débutant en Java
  • Quelques bases en Programmation Orientée Objet dans d'autres langages

1. Introduction

A travers ce MON, je cherche à me familiariser avec le langage de programmation Java, couramment utilisé en entreprise, afin de faciliter de potentiels futurs échanges avec des équipes de développeurs (mon but n'étant pas dans devenir un, mais sûrement de travailler en contact avec eux).

J'ai donc commencé par regarder ce qui avait déjà été fait sur le sujet, et on peut recenser le MON de Nicolas, le MON de Vladimir et le POK de Sarah. Tous les trois on commencé en s'appuyant sur la même formation OpenClassroom intitulée Apprenez à programmer en Java, qu'ils recommandent. J'ai donc suivi cette formation en un peu moins des 10h annoncées, et je vais essayer de la synthétiser ci-dessous afin de pouvoir réutiliser ce MON comme fiche mémoire au besoin.

J'ai ensuite utilisé le temps restant pour lire le MON de Vladimir, qui a suivi une formation plus avancée sur Codecademy, disponible à ce lien. Je n'ai pas suivi la formation moi-même par manque de temps, et le résumé de Valdimir m'a donc été très utile.

2. Les variables

Une variable est une sorte de conteneur qui renferme une donnée utilisée par un programme. Cette donnée s'appelle alors valeur, et il est de rigueur de donner à la variable associée un nom qui reflète la signification de cette valeur afin de rendre le code plus compréhensible. Pour déclarer une variable, on utilise un mot clé qui dépend du type de la variable. On écrire par exemple :

int age = 38; // pour un entier
double taille = 1.77; // pour un décimal (on peut également utiliser float, mais moins précis)
String text = "bonjour"; // pour du texte
boolean isItTrue = true; // pour un booléen
int count; // pour un entier dont on ne connaît pas encore la valeur

Attention à ne pas oublier le ; pour indiquer la fin d'une instruction.

On peut ensuite modifier ces variables grâce aux opérateurs mathématiques classiques, et utiliser les raccourcis courants tels que += pour simplifier les affectations. Pour des chaînes de caractère, on peut réaliser une concaténation grâce au +, et on utilise ! placé juste devant un booléen pour l'inverser.

Les variables qui ne sont pas destinées à être modifiées sont appelées constantes. Il est très recommandé de les utiliser plutôt que des variables classiques dès que possibles, déjà pour éviter des modifications accidentelles de leur valeur, mais aussi car elles améliorent la rapidité d'exécution des programmes. On les déclare comme suit :

final int AGE = 38;
final String MESSAGE = "bonjour";
// C'est un bonne pratique commune de les nommer en majuscules

Lorsque l'on réalise une opération mathématique sur des variables, il faut faire attention à la façon dont les types se mélangent. Par exemple, la division d'un entier par un entier donnera forcément un résultat entier, même si celui-ci est faux, et cela peut être à l'origine de nombreuses erreurs. Il faut donc bien penser à déclarer au moins l'un des entiers initiaux comme décimal pour que le résultat soit correct. Il existe une autre méthode pour corriger ce problème, qui consiste à changer le type d'une variable déjà déclarée directement dans l'opération dans laquelle on l'utilise. On écrit alors :

int a = 5;
int b = 2;
float c = (float) a / b;

C'est ce que l'on appelle le cast.

3. Les classes et les fonctions

Pour rappel, en Programmation Orientée Objet, une classe est un ensemble de variables (que l'on appelle attributs) et de fonctions (nommées méthodes) qui utilisent ces variables. En Java, l'ensemble du code doit se trouver à l'intérieur d'une classe, qui elle même est contenue dans un package.

Lorsque l'on lance un programme en Java, c'est la fonction appelée main qui se lance. On dit que c'est le point d'entrée. Voici un programme très simple pour découvrir la syntaxe de base :

package hello;                                      // déclare le package hello

public class HelloWorld {                           // déclare la classe HelloWorld qui contiendra le code

  public static void main(String[] args) {          // morceau de code recherché par l'interpréteur pour lancer le programme

    System.out.println("Hello World!");             // affiche dans la console le message passé entre guillemets

    }
}

Je ne me suis pas attardée sur cette partie car j'utilise un environnement de développement qui fait cela tout seul, mais à ce moment du cours sont présentées les différentes étapes et commandes pour exécuter un programme Java à partir du terminal. Je laisse cependant le lien ici pour les lecteurs intéressés.

Une autre bonne pratique en Java et en programmation de manière générale, est d'avoir un code organisé pour que la fonction main soit la plus courte possible et appelle uniquement les méthodes utiles, toutes écrites séparément. Pour aider à cette organisation, il existe deux types de classes :

Lorsque l'on crée une classe, il est courant de la documenter pour expliquer son utilité et son fonctionnement. On le fait alors entre les indicateurs /** et */, ce qui crée automatiquement une page HTML avec la documentation, que l'on appelle Javadoc.

Pour appeler une méthode à agir sur une variable, on utilise la syntaxe suivante :

variable = variable.thode()     // on met les arguments nécessaires à la fonction entre les parenthèses le cas échéant

Une variable ne peut être utilisée que dans l'environnement dans laquelle elle a été déclarée (une boucle, une fonction, une classe...). C'est ce que l'on appelle la portée de la variable. En général, on repère cet environnement grâce à des indicateurs d'ouverture et de fermeture. En Java, ce sont des accolades. Voici un schéma tiré de la formation suivie qui explique très bien ce principe : Cependant, une variable déclarée dans une classe peut par défaut être utilisée en dehors de celle-ci. Pour empêcher cela, on peut la restreindre en la précédant de la commande private (le contraire est le mot clé public). On peut faire de même avec une fonction pour empêcher son accès depuis une autre classe que celle dans laquelle elle a été déclarée. Il existe d'autres commandes pour contrôler de manière plus précise l'accès aux données.

4. Les boucles et les conditions

Comme dans la plupart des langages de programmation, on construit et structure les fonctions avec des boucles et des conditions. Voici la syntaxe en Java de ces outils :

for (int i=0; i<10; i++) {               // ou en général for (initialisation, terminaison, incrément)
  // instruction qui sera répétée 10 fois
};

Attention à bien choisir la condition de terminaison pour ne pas créer de boucles infinies !

for (int number: monTableau) {
  // instruction 
};
while (condition) {
  // instruction
};
do {
  // instruction
} while (condition);
if(k == 3 || k == 7) {
  continue;
}
Symbole Signification
== égal à
=! différent de
< inférieur à
<= inférieur ou égal à
> supérieur à
>= supérieur ou égal à
equals identique à (pour une chaîne de caractères)
&& ET logique
|| OU logique
! NON logique
if(condition1) {
  //instructions
}
else if(condition2) {
  //instructions
}
...
else {
  //instructions
}

On peut utiliser les arguments rentrés en paramètre de la fonction pour créer les conditions. A noter que ces arguments sont en réalité sous forme d'un tableau d'arguments, il faut donc les appeler en conséquence.

Il existe deux outils qui permettent dans certains cas de simplifier la rédaction des conditions. Premièrement, la commande switch. Imaginons que l'on veuille par exemple agir différemment selon le nombre d'arguments entrés. On pourrait écrire avec la méthode basique présentée ci-dessus :

public static void main(String[] args) {
    if (args.length==0) {
        //instructions
    } 
    else if (args.length==1) {
        //instructions
    } 
    else if (args.length==2) {
        //instructions
    } 
    else {
        //instructions
    }
}

On peut également utiliser la méthode switch, plus claire, pour n'évaluer qu'une fois la longueur du tableau d'arguments et faire une disjonction de cas. On écrit :

public static void main(String[] args) {
    switch(args.length) {
        case 0:
            //instructions
            break;
        case 1:
            //instructions
            break;
        case 2:
            //instructions
            break;
        default:
            //instructions
    }
}

Deuxièmement, il existe le type énumération, qui permet de lister des cas prédéfinis et de les utiliser dans un switch, à nouveau à fin de simplifier et de clarifier le code. Voici un exemple parlant tiré du cours :

public class myDirection {

    enum Direction {
        north, east, south, west;       //liste des cas possibles
    }

    public static void main(String[] args) {
        Direction direction = Direction.north;
        switch (direction) {
            case north:
                //instructions
                break;
            case east:
                //instructions
                break;
            case south:
                //instructions
                break;
            case west:
                //instructions
                break;
        }
    }
}

Tous les cas sont couverts, il n'est donc pas nécessaire d'avoir un cas par défaut. Si maintenant on considère qu'il n'y a une instruction spécifique que pour la direction nord, on peut remplacer la fonction par :

public static void main(String[] args) {
    Direction direction = Direction.north;
    switch (direction) {
        case north:
            //instructions
            break;
        default:
            //instructions
    }
}

5. La Programmation Orientée Objet en Java

Pour comprendre le principe global de la POO, je vous renvoie vers mon MON 1.2 : Programmation orientée objet. Ici, on va s'intéresser plus particulièrement à son implémentation en Java. Voici le format d'une déclaration de classe en Java :

class ExempleClasse {
  String attribut1;
  String attribut2;
  int attribut3;
  // autres propriétés de la classe
}

Une fois que la classe existe, on peut y faire une instanciation, c'est-à-dire créer un objet du type de cette classe. Pour cela, il faut donner une valeur à chaque champ de la classe, en commençant par initialiser ces champs grâce à un constructeur. C'est une sorte d'objet référence, à partir duquel on pourra ensuite modifier les attributs. A noter que si un champ est identique pour tous les objets de la classe, il peut être rempli directement dans la déclaration de la classe. Prenons un exemple pour mieux comprendre, qui pourrait servir dans le cas d'une bibliothèque en ligne par exemple :

class Book {            // déclaration de la classe
      String title;
      String author;
      int numberOfPages;
      String publisher;
      Book(String title, String author, int numberOfPages, String publisher) {          // constructeur
            this.title = title;
            this.author = author;
            this.numberOfPages = numberOfPages;
            this.publisher = publisher;
      }
}

On initialise toujours les attributs du constructeur grâce au mot-clé this. De plus, il est possible de déclarer plusieurs constructeurs pour une même classe, par exemple dans le cas où l'un des attributs est identique pour un grand nombre d'éléments de la classe. Afin d'éviter les répétitions, on déclare alors un constructeur principal, et un secondaire dans lequel l'attribut en question est déjà renseigné et n'est plus une variable des objets.

Une fois que tout cela est fait, on peut alors créer un objet de notre classe. On écrit, si l'on reprend l'exemple de classe précédent :

Book monLivre = new Book("Harry Potter et le prisonnier d'Azkaban", "J.K. Rowling", 444, "folio")

Enfin, il est possible d'accéder aux champs de notre objet pour les modifier en utilisant le point :

monLivre.numberOfPages = 446

6. L'héritage et le polymorphisme

Il existe deux notions importantes pour spécialiser des classes, que l'on va expliciter dans cette partie.

public class ClasseFille extends ClasseMere {
    // propriétés spécifiques à la classe fille
}

Plusieurs points sont importants : une classe fille ne peut hériter que d'une seule classe mère (l'inverse est faux); tous les champs de la classe mère sont hérités, et ils peuvent être manipulés selon leur accessibilité (comme présenté plus haut); on peut également appeler le constructeur de la classe mère dans la classe fille grâce au mot clé super.

class Saluer {
  void saluer() {
    System.out.println("Bonjour");
  }
}

class SaluerAmi extends Saluer {
  void saluer() {
    System.out.println("Salut");
  }
}

class SaluerEtranger extends Saluer {
  void saluer() {
    System.out.println("Hello");
  }
}

On peut alors appeler la méthode saluer sur toutes les classes et elle s'adapte à l'objet pour lequel on l'utilise.

Grâce au mot clé super, on peut appeler la méthode parente sans la modifier pour y ajouter autre chose ensuite. Cela donne par exemple :

class SaluerProfesseur extends Saluer {
  void saluer() {
    super.saluer();
    System.out.println("Monsieur");
  }
}

7. Les collections

Cette partie présente les principaux moyens de stocker des piles de données.

int[] notesElevesDoIT;       // création du tableau

notesElevesDoIT=new int[23];         // instanciation

int[] notesElevesDoIt = new int [23];          // les deux lignes précédentes en une

notesELevesDoIt[7]=16;       // attribue 16 au 8ème élève

System.out.println(notesElevesDoIt[12]);         // affiche la note du 13ème élève

Il est également possible de remplir le tableau directement à sa création en ajoutant ses valeurs entre accolades, ou de créer des tableaux multidimensionnels en ajoutant des paires de crochets (pour faire des matrices par exemple).

List<Integer>maListe = new ArrayList<Integer>();

Ensuite, on peut y effectuer plusieurs opérations :

maListe.add(7); // ajoute l'élément 7
maListe.set(0,4); // change l'élément de rang 0 en un 4
maListe.remove(0); // retire le premier élément
System.out.println(maListe.size()); // affiche la taille de la liste
Set<String>nomsPrenomsEleves = new HashSet<String>(); // déclaration de l'ensemble
nomsPrenomsEleves.add("AgatheRabachou"); // ajoute l'élève AgatheRabachou à l'ensemble
nomsPrenomsEleves.remove("AgatheRabachou"); // retire l'élève AgatheRabachou de l'ensemble
System.out.println(nomsPrenomsEleves()); // compte les éléments de l'ensemble

8. Les paramètres et les retours

Lorsque l'on définit une méthode, elle peut prendre en arguments des paramètres, que l'on déclare entre parenthèses lors de la définition par leur nom précédé de leur type. Si l'on veut que la fonction renvoie une valeur afin de pouvoir la réutiliser ailleurs, il faut le préciser avec le mot-clé return. Ensuite, on utilise ce retour en appelant la fonction avec les paramètres choisis. Un exemple simple :

public static int sum(int a, int b) {
  int c = a + b;
  return c;
  // ou return a + b pour écrire les deux lignes en une seule
}

// on peut alors utiliser le résultat :
int resultat = sum(2,4);

Attention, il existe deux types pour les paramètres, et la différence est fondamentale. Si l'on copie un objet de type valeur, comme un entier, d'une variable dans une autre, les deux variables sont indépendantes, on peut en modifier une sans changer l'autre. A l'inverse, dans le cas d'un objet de type référence, comme une classe, lorsque l'on en crée une copie on crée simplement un autre pointeur qui renvoie en fait vers le même élément. Une modification sur cette copie modifie donc la classe originale. Ainsi, il faut faire très attention à ce que l'on fait des éléments dans une fonction, et notamment des éléments de types référence, au risque de faire des modifications plus larges sans le vouloir.

9. La récursivité

Pour rappel, une méthode est dite récursive lorsqu'elle fonctionne en continue en s'appelant elle-même. Cela s'illustre très bien avec la fonction qui calcule la factorielle de n (n!) :

class Recursion {
  public static int factoriel(int n) {
    if (n == 1) return 1;             // Attention à ne pas oublier la condition d'arrêt !!
    else return n * factoriel (n-1);
  }
}

10. Pour aller plus loin

La méthode try/catch est une méthode qui permet de gérer des exceptions pour éviter les bugs. L'idée est d'essayer d'exécuter un morceau de code, et d'en prévoir un autre qui s'exécute dans le cas où le premier génère une erreur. On écrit :

try {
  // code principal, contenant une fonction qui peut potentiellement générer une erreur
}
catch (Exception e) {
  // code à lancer si la partie try renvoie une erreur
}

Il est possible de faire intervenir des fichiers extérieurs dans notre code Java. Plusieurs outils pour cela :

FileReader fileReader = new FileReader("/path/to/file") // lit le fichier situé au chemin donné

BufferedReader reader = new BufferedReader(fileReader); // utilise le fileReader

line = reader.readLine(); //lit la prochaine ligne du fichier (à mettre dans une boucle pour lire le fichier en entier)

FileWriter fileWriter = new FileWriter("/path/to/file", false); // trouve le fichier au chemin donné

BufferedWriter writer = new BufferedWriter(fileWriter); // prépare le fichier pour écrire dedans

writer.write("text") // ajoute du texte

writer.newLine(); // revient à la ligne

Enfin, le MON de Vladimir présente d'autres notions avancées en Java, comme les input et output (que j'ai en partie utilisé plus haut sans réellement expliquer le concept), la sérialisation et les génériques, que je ne vais pas expliquer ici car il l'a déjà très bien fait.

11. Retours

Comme les quelques cours OpenClassroom que j'avais déjà suivi auparavant, le gros point fort est pour moi la partie mise en pratique, avec des exercices interactifs tout au long de la formation pour pouvoir appliquer les notions de manière plus concrète au fur et à mesure de l'apprentissage (bien que je les ai trouvés ici légèrement trop peu nombreux par rapport à d'autres cours que j'avais pu faire). Sur cette formation, ils étaient à faire sur la plateforme Replit, que je ne connaissais pas mais qui est gratuite et simple d'utilisation. Par ailleurs, le cours est très clair, adapté aux débutants en Java (de préférences avec quelques notions globales en informatique quand même), et permet je trouve une bonne prise en main des concepts de base du langage et de la programmation orientée objet.

Pour ce qui est du résumé fait par Vladimir de la formation Codecademy qu'il a suivie, je l'ai également trouvé très clair et présentant des notions différentes de la formation OpenClassroom (qui s'intéressait surtout aux bases et à la syntaxe), c'était donc un très bon complément.

12. Organisation du temps

Durée Action
3h45 Cours OpenClassroom partie 1 : Gestion des variables
2h45 Cours OpenClassroom partie 2 : Programmation Orientée Objet
2h Cours OpenClassroom partie 3 : Quelques principes avancés
1h30 MON de Vladimir