Compléments pour bien démarrer

Compléments pour bien démarrer #

Classes utilitaires, classes exécutables #

Nous parlons de classes “utilitaires” lorsqu’elles offrent des fonctionnalités statiques. La classe Math de Java en est un bon exemple.

1
2
3
4
Math.sin(0.5); // ==> 0.479425538604203
Math.min(1, 3);  // ==> 1
Math.min(1.0, 3.0);  // ==> 1.0
Math.pow(2, 10); // ==> 1024.0

Nous avons vu l’exemple du HelloWorld qui permettait d’exécuter un programme Java. Une application est généralement composée de packages1, classes, interfaces, … Une convention veut qu’il n’existe qu’un type public (visible à l’utilisateur2) par fichier. Parmi ceux-ci, il est nécessaire d’avoir une classe qui réalise l’amorce du programme, appelé classe principale ou programme principal : le main. Celui-ci est également appelé ainsi dans d’autres langages.

En Java, le programme principal est une classe. Pour indiquer qu’elle est exécutable, elle doit comporter une méthode main dont la signature3 est donné ci-dessous. Nous avons nommé cette classe MainApp. Depuis celle-ci, nous appelons nos fonctionnalités écrite dans un autre fichier se situant dans le même répertoire appelée MathUtil (pour ne pas rentrer en conflit avec la classe Math existante).

La classe utilitaire:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// fichier MathUtil.java
public class MathUtil {

  public static int doubleThat(int i) { 
    return i*2;
  }

  public static int square(int i) { 
    return i*i;
  }
}

La classe exécutable:

1
2
3
4
5
6
7
// fichier MainApp.java
public class MainApp {
  public static void main(String[] args) {
    int result = MathUtil.doubleThat(MathUtil.square(10));
    System.out.println( result ); // Affiche 200
  }
}

Vous remarquez que toutes les méthodes sont statiques puisqu’elles sont rattachées à la classe MathUtil et non à un objet instancié. Comme nous l’avons déjà mentionné ici , cela permet d’écrire des fonctions sans faire de POO.

Compiler et exécuter (les dépendances se compileront automatiquement):

1
2
javac MainApp.java
java MainApp

Lecture et affichage dans le terminal #

Affichage dans le terminal #

La classe System met à disposition le flux de sortie out et fournit plusieurs méthodes

  • print: affichage sans retour à la ligne
  • println: avec retour à la ligne
  • format: correspond au printf du C
  • et plein d’autres4
1
2
3
4
System.out.print("Affiche l'argument sans retour à la ligne");
System.out.println("Affiche avec un retour à la ligne");
System.out.println("Concatenation: " + 42 + " !");
System.out.format("Valeur de l'argument: %d", 2);

Lecture d’une entrée utilisateur #

La lecture d’une entrée utilisateur se fait à l’aide de la classe Scanner et du flux d’entrée Scanner.in. La méthode nextInt() peut retourner une exception si l’utilisateur entre une valeur non entière.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import java.util.Scanner;

Scanner scanner = new Scanner(System.in);

System.out.print("Enter your name: ");
String name = scanner.next();

System.out.print("Enter your age: ");
int age = scanner.nextInt(); /* lève une exception si 
                                la valeur n'est pas entière */

scanner.close();

System.out.println("Bonjour " + name + ", vous avez " + age + " ans");

Classes enveloppes #

Chaque type primitif a son équivalent en terme de classe.

type primitif Classe associée (classe enveloppe)
boolean Boolean
byte Byte
short Short
char Character
int Integer
long Long
float Float
double Double

L’autoboxing est la conversion automatique d’un type primitif en un objet. Le “unboxing” est l’opération inverse.

// Autoboxing:
Integer i = 3; // au lieu de Integer i = new Integer(3); (déprécié)

// Unboxing
int j = i;

Attention

Préférez toujours les types primitifs. Rappelez-vous que les paramètres d’un type générique ne peuvent pas être un type primitif (List<Integer> et non List<int>). C’est souvent la seule situation où les classes envoloppes sont employées.

Les classes enveloppes se trouvent sur le tas, elles sont donc moins performantes que les types primitifs qui se trouvent sur la pile. De plus, un byte possède 256 valeurs possibles alors que l’équivalent Byte en possède 257 (256 + null puisqu’il s’agit d’une référence).

// Danger
jshell> Byte b; 
==> null  // et non 0 ! 

Il existe cependant des méthodes statiques intéressantes. En voici quelques exemples:

int i = Integer.parseInt("42"); /* ==> retourne une exception si la chaîne
                                       comporte des caractères non numériques */

short s = Short.MAX_VALUE; // ==> 32767
boolean isDigit = Character.isDigit('.'); // ==> false

Nombres à précision arbitraire #

Nous avons déjà vu un exemple de la classe BigDecimal qui permet une représentation arbitraire pour les nombres décimaux. Une telle classe existe pour les entiers : BigInteger.

Exceptions (notions de base) #

Une exception est un mécanisme de gestion d’erreurs qui peut être levée lorsqu’un appel de méthode ou une expression est dans un état instable. Les arguments passés en paramètres de la fonction ne respectent pas le contrat, le programme tente de lire un fichier inexistant, récupération d’un élément dans une liste à un indice plus grand que la taille de cette liste, …

1
2
3
4
List.of(0,1,2).get(3); // ==> ArrayIndexOutOfBoundsException
3 / 0; // ==> java.lang.ArithmeticException
String s; // s référence null !!!
s.toUpperCase(); // ==> NullPointerException (70% des exceptions)

Un chapitre sera dédié aux exceptions et à l’absence de valeur. Pour l’instant, cette section explique quelques notions de bases.

Provoquer une exception #

Pour l’instant, si vous réalisez une fonctionnalité qui ne peut pas retourner un résultat cohérent, vous pouvez retourner l’exception RuntimeException avec un message, précédé du mot-clé throw. Ne retournez pas l’exception Exception.

1
2
3
4
5
6
7
8
public static int countPeople() {
  if( !isDatabaseConnected() ) {
    throw new RuntimeException("Inaccessible database");
  }

  ...

}

Traiter une exception #

Il est possible de contrôler une exception. Pour l’instant, si une méthode vous oblige à traiter l’exception, vous pouvez indiquer que la méthode et la méthode main peut lever une exception avec le mot-clé throws dans la signature desdites méthodes.

L’exemple ci-dessous permet de lire une entrée de l’utilisateur, de l’écrire dans un fichier, puis d’afficher le contenu lu dans le fichier.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Scanner;

 
public class WriteToFile {

   public static void main(String[] args) throws IOException {

       final Scanner scanner = new Scanner(System.in);

       System.out.print("Enter your name: ");
       final String name = scanner.next();

       final Path fileName = Path.of("text.txt");
       final String content  = "Hello " + name + " !";

       Files.writeString(fileName, content);
        
       final String actual = Files.readString(fileName);
       System.out.println(actual);

       assert actual.equals(content);

       scanner.close();
   }
}

Le mot-clé assert permet de vérifier qu’une condition soit toujours vraie. Une assertion fausse termine le programme avec l’exception AssertionErrors qui ne peut pas être traitée. Son utilisation se retrouvera dans beaucoup d’exemples.

Les assertions ne sont pas vérifiées par défaut. Un paramètre spécifique lors de l’exécution est nécessaire:

javac WriteToFile.java
java -ea WriteToFile

Une deuxième façon, plus traditionnelle, consiste à gérer l’exception dans un bloc dédié à cet effet : Le bloc try ... catch:

try {
  int i = scanner.nextInt(); /* l'utilisateur ne rentre pas forcément en entier
                                ==> exception InputMismatchException est retournée */
  System.out.println(i);
} catch (InputMismatchException e) { // traitée ici
  System.err.println(e.toString());  // affichage de l'erreur sur stderr
} finally { // finally = "dans tous les cas..."
  scanner.close(); // ... il est nécessaire de fermer le scanner
}

Rappelons qu’un chapitre sera dédié aux exceptions.


  1. regroupement de fonctionnalités au sein de modules ↩︎

  2. utilisateur de notre API ↩︎

  3. la signature d’une fonction/méthode doit décrire les types des arguments et le type de retour ↩︎

  4. docs.oracle.com: System ↩︎












comments powered by Disqus