MFormations
Modern Java Engineering

Chapitre 1

Chapitre 01 : Fondamentaux Java

Chapitre 01 : Fondamentaux Java

Cours : Fondamentaux Java

1. Types Primitifs vs Wrapper

1.1 Les 8 Types Primitifs

Java possède 8 types primitifs, divisés en 4 catégories :

TypeTailleValeur minValeur maxDéfautExemple
byte8 bits-1281270byte b = 42;
short16 bits-32 76832 7670short s = 1000;
int32 bits-2³¹2³¹-10int i = 42;
long64 bits-2⁶³2⁶³-10Llong l = 42L;
float32 bits±1.4E-45±3.4E+380.0ffloat f = 3.14f;
double64 bits±4.9E-324±1.7E+3080.0ddouble d = 3.14;
char16 bits065 535'\u0000'char c = 'A';
boolean~1 bit--falseboolean b = true;

1.2 Classes Wrapper

Chaque type primitif a une classe wrapper dans java.lang :

PrimitifWrapperExemple d'autoboxing
byteByteByte b = 42;
shortShortShort s = 1000;
intIntegerInteger i = 42;
longLongLong l = 42L;
floatFloatFloat f = 3.14f;
doubleDoubleDouble d = 3.14;
charCharacterCharacter c = 'A';
booleanBooleanBoolean b = true;

Autoboxing et Unboxing

Integer a = 42;          // Autoboxing : int → Integer
int b = a;               // Unboxing : Integer → int
Integer c = null;
int d = c;               // NullPointerException ! (unboxing de null)

Cache des Wrappers

Integer x = 127;
Integer y = 127;
System.out.println(x == y);     // true (cache [-128;127])

Integer a = 128;
Integer b = 128;
System.out.println(a == b);     // false (hors cache)
System.out.println(a.equals(b)); // true (toujours comparer avec equals)

1.3 Primitifs vs Wrapper : Quand utiliser quoi ?

SituationPrimitifWrapper
Calculs, opérations arithmétiques❌ (performance)
Collections (List, Set, Map)
Génériques (List<Integer>)
Optional (OptionalInt)
Nullabilité❌ (jamais null)
API tierces (JSON, JPA)
Flag booléen simple

Performance

// Lourd : autoboxing à chaque itération
Long sum = 0L;
for (long i = 0; i < 1_000_000; i++) {
    sum += i;  // 1M d'objets Long créés !
}

// Léger : primitif
long sum = 0L;
for (long i = 0; i < 1_000_000; i++) {
    sum += i;
}

2. String : Pool et Immutabilité

2.1 Immutabilité

String est immuable : toute opération de modification crée une nouvelle chaîne.

String s = "Hello";
s.toUpperCase();        // "HELLO" créé, mais s pointe toujours vers "Hello"
s = s.toUpperCase();    // Maintenant s pointe vers "HELLO"

Pourquoi immuable ?

  • Sécurité (ne peut pas être modifié après création)
  • Thread-safety (pas de synchronisation nécessaire)
  • String pool (partage sécurisé des chaînes)
  • Hachage fiable (hashCode constant → bon pour HashMap)

2.2 String Pool

String s1 = "Java";          // Dans le pool (littéral)
String s2 = "Java";          // Même référence que s1
String s3 = new String("Java"); // Hors pool (objet distinct)

System.out.println(s1 == s2);      // true (même référence dans le pool)
System.out.println(s1 == s3);      // false (objet différent)
System.out.println(s1.equals(s3)); // true (même contenu)

// Intern explicite
String s4 = new String("Java").intern();
System.out.println(s1 == s4);      // true (internée dans le pool)

2.3 String, StringBuilder, StringBuffer

ClasseImmuableThread-safePerformance
StringOuiOui (immuable)Lente en concaténation
StringBuilderNonNonRapide
StringBufferNonOui (synchronisé)Moins rapide que StringBuilder
// String : immuable, crée des objets à chaque concaténation
String s = "";
for (int i = 0; i < 1000; i++) {
    s += i;  // 1000 objets String créés !
}

// StringBuilder : mutable, efficace
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);
}
String result = sb.toString();

2.4 Méthodes essentielles de String

String s = "  Hello World!  ";

s.length();                       // 15
s.charAt(0);                      // ' '
s.substring(2, 7);                // "Hello"
s.indexOf("World");               // 8
s.contains("Hello");              // true
s.startsWith("  ");               // true
s.endsWith("!  ");                // true
s.replace("World", "Java");       // "  Hello Java!  "
s.trim();                         // "Hello World!"
s.strip();                        // "Hello World!" (Java 11+, support Unicode)
s.isBlank();                      // false (Java 11+)
s.lines().collect(toList());      // ["  Hello World!  "] (Java 11+)
s.repeat(3);                      // "  Hello World!    Hello World!    Hello World!  " (Java 11+)

// Java 15+ : Text blocks
String json = """
    {
        "name": "Java",
        "version": 21,
        "features": ["records", "vt", "pm"]
    }
    """;

3. Opérateurs

3.1 Opérateurs Arithmétiques

OpérateurDescriptionExemple
+Addition / concaténationa + b
-Soustractiona - b
*Multiplicationa * b
/Divisiona / b
%Modulo (reste)a % b
++Incrémentationa++ / ++a
--Décrémentationa-- / --a

Attention aux divisions :

int a = 5 / 2;        // 2 (division entière)
double b = 5 / 2;     // 2.0 (toujours entière !)
double c = 5 / 2.0;   // 2.5
double d = 5.0 / 2;   // 2.5

3.2 Opérateurs de Comparaison

OpérateurDescription
==Égalité (primitifs) / Référence (objets)
!=Différent
<, <=Inférieur (strict/inclus)
>, >=Supérieur (strict/inclus)

3.3 Opérateurs Logiques

OpérateurDescriptionCourt-circuit
&&ET logiqueOui
||OU logiqueOui
&ET binaire / logiqueNon
|OU binaire / logiqueNon
!NON logique-
^XOR (ou exclusif)-
// Court-circuit : si a < 0, b < 10 n'est pas évalué
if (a >= 0 && b < 10) { }

// Sans court-circuit : les deux expressions sont toujours évaluées
if (a >= 0 & b < 10) { }  // Inutile et potentiellement dangereux

3.4 Opérateurs Bit à Bit

OpérateurDescriptionExemple
&ET binairea & b
|OU binairea | b
^XOR binairea ^ b
~Complément (NOT)~a
<<Décalage gauchea << 2
>>Décalage droit (signé)a >> 2
>>>Décalage droit (non signé)a >>> 2
int flags = 0b0010_1100;  // Séparateurs _ autorisés depuis Java 7
int mask = 0b0000_1100;
boolean hasAccess = (flags & mask) == mask;  // true

3.5 Opérateur Ternaire

String result = (age >= 18) ? "Majeur" : "Mineur";
int max = (a > b) ? a : b;

// Usage idiomatique avec Optional
String name = (person != null) ? person.getName() : "Inconnu";

// À éviter : ternaires imbriqués (illisibles)
String r = (a > b) ? (a > c ? "a" : "c") : (b > c ? "b" : "c");

3.6 instanceof et Pattern Matching (Java 16+)

// Avant Java 16
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.length());
}

// Java 16+ : Pattern Matching for instanceof
if (obj instanceof String s) {
    System.out.println(s.length());
}

// Avec conditions supplémentaires
if (obj instanceof String s && s.length() > 5) {
    System.out.println(s.toUpperCase());
}

4. Contrôle de Flux

4.1 if / else if / else

if (score >= 90) {
    grade = "A";
} else if (score >= 80) {
    grade = "B";
} else if (score >= 70) {
    grade = "C";
} else {
    grade = "F";
}

4.2 switch (Java 14+)

// Style classique (Java 7+ : supporte String)
String dayName;
switch (day) {
    case 1:
    case 7:
        dayName = "Weekend";
        break;
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
        dayName = "Jour de semaine";
        break;
    default:
        dayName = "Invalide";
}

// Java 14+ : Arrow switch (pas de break, pas de fall-through)
dayName = switch (day) {
    case 1, 7 -> "Weekend";
    case 2, 3, 4, 5, 6 -> "Jour de semaine";
    default -> "Invalide";
};

// Java 14+ : Switch expression avec yield
dayName = switch (day) {
    case 1, 7:
        yield "Weekend";
    case 2, 3, 4, 5, 6:
        yield "Jour de semaine";
    default:
        yield "Invalide";
};

// Java 21+ : Pattern Matching for switch
String description = switch (obj) {
    case Integer i -> "Entier : " + i;
    case String s -> "Chaîne de longueur " + s.length();
    case null -> "Null !";
    default -> "Autre type";
};

// Avec guard (when)
String type = switch (obj) {
    case String s when s.length() > 10 -> "Longue chaîne";
    case String s -> "Courte chaîne";
    case Integer i when i > 100 -> "Grand entier";
    case Integer i -> "Petit entier";
    default -> "Autre";
};

4.3 Boucles

// for classique
for (int i = 0; i < 10; i++) {
    System.out.println(i);
}

// for-each (depuis Java 5)
String[] names = {"Alice", "Bob", "Charlie"};
for (String name : names) {
    System.out.println(name);
}

// while
int i = 0;
while (i < 10) {
    System.out.println(i++);
}

// do-while (exécute au moins une fois)
int i = 0;
do {
    System.out.println(i++);
} while (i < 10);

// break et continue
for (int i = 0; i < 10; i++) {
    if (i == 3) continue;  // Passe à l'itération suivante
    if (i == 7) break;     // Sort de la boucle
    System.out.println(i); // 0, 1, 2, 4, 5, 6
}

// Labeled break (sortie de boucle imbriquée)
outer:
for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 5; j++) {
        if (i * j > 6) break outer;
        System.out.println(i + "," + j);
    }
}

5. Tableaux

5.1 Déclaration et Initialisation

// Déclaration (les deux syntaxes sont équivalentes)
int[] numbers;       // Préféré (type[])
int numbers[];       // Style C (déconseillé)

// Allocation
numbers = new int[5];  // {0, 0, 0, 0, 0}

// Initialisation
int[] primes = {2, 3, 5, 7, 11};
int[] squares = new int[]{1, 4, 9, 16, 25};

// Tableau multidimensionnel
int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// Tableau irrégulier (jagged array)
int[][] jagged = new int[3][];
jagged[0] = new int[]{1, 2};
jagged[1] = new int[]{3, 4, 5, 6};
jagged[2] = new int[]{7};

5.2 Parcours

int[] nums = {1, 2, 3, 4, 5};

// for classique
for (int i = 0; i < nums.length; i++) {
    System.out.println(nums[i]);
}

// for-each
for (int n : nums) {
    System.out.println(n);
}

// Stream (Java 8+)
Arrays.stream(nums)
      .filter(n -> n % 2 == 0)
      .forEach(System.out::println);

// Remplissage
Arrays.fill(nums, 0);          // {0, 0, 0, 0, 0}
Arrays.setAll(nums, i -> i * 2); // {0, 2, 4, 6, 8} (Java 8+)

// Copie
int[] copy = Arrays.copyOf(nums, nums.length);
int[] partial = Arrays.copyOfRange(nums, 1, 4);

5.3 Méthodes Utilitaires

int[] a = {3, 1, 4, 1, 5, 9};

Arrays.sort(a);                    // {1, 1, 3, 4, 5, 9}
int index = Arrays.binarySearch(a, 4); // 3 (tableau trié)
Arrays.parallelSort(a);            // Tri parallèle (Java 8+)
System.out.println(Arrays.toString(a)); // "[1, 1, 3, 4, 5, 9]"

int[] b = {1, 1, 3, 4, 5, 9};
System.out.println(Arrays.equals(a, b)); // true
System.out.println(Arrays.compare(a, b)); // 0 (Java 9+)
System.out.println(Arrays.mismatch(a, b)); // -1 (Java 9+)

6. Exceptions (Checked/Unchecked)

6.1 Hiérarchie des Exceptions

Throwable
├── Error (irrécupérable)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── NoClassDefFoundError
└── Exception
    ├── RuntimeException (unchecked)
    │   ├── NullPointerException
    │   ├── IllegalArgumentException
    │   ├── IllegalStateException
    │   ├── ArrayIndexOutOfBoundsException
    │   ├── ClassCastException
    │   └── ArithmeticException
    └── IOException (checked)
        ├── FileNotFoundException
        ├── EOFException
        └── SocketException

6.2 Checked vs Unchecked

CritèreCheckedUnchecked
HéritageException (sauf RuntimeException)RuntimeException ou Error
Obligation de gestionOui (catch ou throws)Non (optionnelle)
Utilisation typiqueI/O, réseau, SQLErreurs de programmation, préconditions
RécupérableOui (généralement)Non (souvent bug)
// Checked Exception — doit être gérée
public void readFile(String path) throws IOException {
    try (var reader = new BufferedReader(new FileReader(path))) {
        System.out.println(reader.readLine());
    }
}

// Unchecked Exception — peut (et devrait) être évitée
public void process(String input) {
    if (input == null) {
        throw new IllegalArgumentException("input ne peut pas être null");
    }
}

// Bonne pratique : RuntimeException pour les erreurs de l'API
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(Long id) {
        super("Utilisateur non trouvé : " + id);
    }
}

6.3 try-catch-finally

// try-catch basique
try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.err.println("Division par zéro : " + e.getMessage());
}

// Multi-catch (Java 7+)
try {
    readFile("test.txt");
} catch (IOException | IllegalArgumentException e) {
    System.err.println("Erreur : " + e.getClass().getSimpleName());
}

// finally (toujours exécuté)
var reader = new BufferedReader(new FileReader("test.txt"));
try {
    System.out.println(reader.readLine());
} catch (IOException e) {
    System.err.println("Erreur : " + e.getMessage());
} finally {
    reader.close();  // Toujours fermer
}

// try-with-resources (Java 7+)
try (var reader = new BufferedReader(new FileReader("test.txt"))) {
    System.out.println(reader.readLine());
} catch (IOException e) {
    System.err.println("Erreur : " + e.getMessage());
} // reader.close() appelé automatiquement

6.4 Propagation et Wrapping

// throws : propager au caller
public void methodA() throws IOException {
    methodB();
}

public void methodB() throws IOException {
    throw new IOException("Erreur réseau");
}

// Wrapping : transformer une exception
public void process() {
    try {
        readFile("config.properties");
    } catch (IOException e) {
        throw new RuntimeException("Échec lecture config", e);
    }
}

7. I/O et NIO.2

7.1 java.io (flux classiques)

// Lecture fichier texte
try (var reader = new BufferedReader(new FileReader("input.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

// Écriture fichier texte
try (var writer = new BufferedWriter(new FileWriter("output.txt"))) {
    writer.write("Hello, World!");
    writer.newLine();
}

// Lecture binaire
try (var input = new BufferedInputStream(new FileInputStream("image.jpg"))) {
    byte[] buffer = new byte[8192];
    int bytesRead;
    while ((bytesRead = input.read(buffer)) != -1) {
        // traiter le buffer
    }
}

7.2 java.nio.file (NIO.2 — Java 7+)

import java.nio.file.*;

// Chemins
Path path = Path.of("docs", "chapter-01", "README.md");
// -> "docs/chapter-01/README.md" (séparateur automatique)

// Vérifications
Files.exists(path);
Files.isRegularFile(path);
Files.isDirectory(path);
Files.isReadable(path);

// Lecture d'un fichier
String content = Files.readString(path);  // Java 11+
List<String> lines = Files.readAllLines(path);

// Écriture dans un fichier
Files.writeString(path, "Hello, World!");  // Java 11+
Files.write(path, List.of("Ligne 1", "Ligne 2"));

// Copie et déplacement
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
Files.delete(path);  // IOException si n'existe pas
Files.deleteIfExists(path);  // boolean

// Création de répertoires
Files.createDirectory(path);       // Un seul niveau
Files.createDirectories(path);     // Tous les niveaux manquants
Files.createTempFile("prefix", ".txt");
Files.createTempDirectory("prefix");

// Parcours de répertoire
try (var stream = Files.list(Path.of("."))) {
    stream.forEach(System.out::println);
}

// Parcours récursif
Files.walk(Path.of("src"))
     .filter(Files::isRegularFile)
     .filter(p -> p.toString().endsWith(".java"))
     .forEach(System.out::println);

// WatchService (surveillance de fichiers)
try (var watchService = FileSystems.getDefault().newWatchService()) {
    Path dir = Path.of(".");
    dir.register(watchService, 
        StandardWatchEventKinds.ENTRY_CREATE,
        StandardWatchEventKinds.ENTRY_MODIFY);
    
    WatchKey key;
    while ((key = watchService.take()) != null) {
        for (WatchEvent<?> event : key.pollEvents()) {
            System.out.println(event.kind() + " : " + event.context());
        }
        key.reset();
    }
}

7.3 Flux d'Entrée/Sortie Modernes

// java.io.InputStream / OutputStream
// java.io.Reader / Writer (texte)

// Files.newBufferedReader (NIO.2)
try (var reader = Files.newBufferedReader(path)) {
    reader.lines().forEach(System.out::println);
}

// Files.newInputStream / OutputStream
try (var in = Files.newInputStream(path);
     var out = Files.newOutputStream(targetPath)) {
    in.transferTo(out);  // Java 9+
}

// Reading all bytes
byte[] data = Files.readAllBytes(path);

// ObjectInputStream / ObjectOutputStream (sérialisation)
record Person(String name, int age) implements Serializable {}
var person = new Person("Alice", 30);

try (var oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
    oos.writeObject(person);
}

try (var ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
    var loaded = (Person) ois.readObject();
}

8. try-with-resources

8.1 Principe

Introduit en Java 7, le try-with-resources ferme automatiquement les ressources implémentant AutoCloseable.

// Avant Java 7 (verbosité)
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("file.txt"));
    System.out.println(reader.readLine());
} catch (IOException e) {
    log.error("Erreur", e);
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            log.error("Erreur fermeture", e);
        }
    }
}

// Java 7+ : try-with-resources
try (var reader = new BufferedReader(new FileReader("file.txt"))) {
    System.out.println(reader.readLine());
} catch (IOException e) {
    log.error("Erreur", e);
}
// reader.close() est appelé automatiquement

8.2 Multiples Ressources

// Plusieurs ressources séparées par ;
try (var reader = new BufferedReader(new FileReader("input.txt"));
     var writer = new BufferedWriter(new FileWriter("output.txt"))) {
    writer.write(reader.readLine());
} catch (IOException e) {
    log.error("Erreur", e);
}

8.3 try-with-resources avec catch et finally

try (var reader = Files.newBufferedReader(path)) {
    String line = reader.readLine();
    if (line == null) {
        throw new EmptyFileException(path);
    }
    return line;
} catch (EmptyFileException e) {
    log.warn("Fichier vide : {}", path);
    return "";
} catch (IOException e) {
    log.error("Erreur de lecture", e);
    throw new RuntimeException(e);
} finally {
    System.out.println("Tentative de lecture terminée pour : " + path);
}

8.4 Ressources Personnalisées

public class DatabaseConnection implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("Connexion fermée");
    }
}

// Utilisation
try (var conn = new DatabaseConnection()) {
    conn.query("SELECT * FROM users");
} // close() est appelé automatiquement

8.5 Suppressed Exceptions

Si une exception est levée dans le try ET dans le close(), l'exception du close() est "suppressed" :

public class FlakyResource implements AutoCloseable {
    public void work() { throw new RuntimeException("Erreur travail"); }
    @Override
    public void close() { throw new RuntimeException("Erreur fermeture"); }
}

try (var res = new FlakyResource()) {
    res.work();
} catch (RuntimeException e) {
    System.out.println(e.getMessage());           // "Erreur travail"
    Throwable[] suppressed = e.getSuppressed();
    System.out.println(suppressed[0].getMessage()); // "Erreur fermeture"
}

8.6 Bonnes Pratiques I/O

// ✅ Toujours utiliser try-with-resources
try (var reader = Files.newBufferedReader(path)) {
    // ...
}

// ✅ Préférer NIO.2 à java.io quand possible
Files.readString(path);           // Simple
Files.readAllLines(path);         // Multi-lignes
Files.writeString(path, content); // Écriture

// ✅ Bufferiser pour les performances
try (var reader = new BufferedReader(new FileReader("large.txt"))) { }

// ✅ Utiliser les méthodes modernes pour les petits fichiers
String content = Files.readString(path);
Files.writeString(path, content);

// ❌ Ne pas ignorer les exceptions dans close()
// dans un finally, il faut logger l'exception de close()
resource.close();  // Si ça lance une exception, celle du try est perdue

9. Résumé et Points Clés

  1. Types primitifs : 8 types, stockés sur la stack, performance optimale
  2. Classes wrapper : Boxing/unboxing automatique, cache [-128;127], utiliser equals() pour comparer
  3. String : Immuable, utiliser StringBuilder pour les concaténations en boucle
  4. Switch : Arrow syntax et pattern matching depuis Java 14/21
  5. Tableaux : Taille fixe, vérifier les bornes, préférer List en général
  6. Exceptions : Checked pour les conditions récupérables, Unchecked pour les bugs
  7. I/O : Toujours try-with-resources, préférer NIO.2
  8. try-with-resources : Fermeture automatique, gère les suppressed exceptions