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 :
| Type | Taille | Valeur min | Valeur max | Défaut | Exemple |
|---|---|---|---|---|---|
byte | 8 bits | -128 | 127 | 0 | byte b = 42; |
short | 16 bits | -32 768 | 32 767 | 0 | short s = 1000; |
int | 32 bits | -2³¹ | 2³¹-1 | 0 | int i = 42; |
long | 64 bits | -2⁶³ | 2⁶³-1 | 0L | long l = 42L; |
float | 32 bits | ±1.4E-45 | ±3.4E+38 | 0.0f | float f = 3.14f; |
double | 64 bits | ±4.9E-324 | ±1.7E+308 | 0.0d | double d = 3.14; |
char | 16 bits | 0 | 65 535 | '\u0000' | char c = 'A'; |
boolean | ~1 bit | - | - | false | boolean b = true; |
1.2 Classes Wrapper
Chaque type primitif a une classe wrapper dans java.lang :
| Primitif | Wrapper | Exemple d'autoboxing |
|---|---|---|
byte | Byte | Byte b = 42; |
short | Short | Short s = 1000; |
int | Integer | Integer i = 42; |
long | Long | Long l = 42L; |
float | Float | Float f = 3.14f; |
double | Double | Double d = 3.14; |
char | Character | Character c = 'A'; |
boolean | Boolean | Boolean 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 ?
| Situation | Primitif | Wrapper |
|---|---|---|
| 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
| Classe | Immuable | Thread-safe | Performance |
|---|---|---|---|
String | Oui | Oui (immuable) | Lente en concaténation |
StringBuilder | Non | Non | Rapide |
StringBuffer | Non | Oui (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érateur | Description | Exemple |
|---|---|---|
+ | Addition / concaténation | a + b |
- | Soustraction | a - b |
* | Multiplication | a * b |
/ | Division | a / b |
% | Modulo (reste) | a % b |
++ | Incrémentation | a++ / ++a |
-- | Décrémentation | a-- / --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érateur | Description |
|---|---|
== | Égalité (primitifs) / Référence (objets) |
!= | Différent |
<, <= | Inférieur (strict/inclus) |
>, >= | Supérieur (strict/inclus) |
3.3 Opérateurs Logiques
| Opérateur | Description | Court-circuit |
|---|---|---|
&& | ET logique | Oui |
|| | OU logique | Oui |
& | ET binaire / logique | Non |
| | OU binaire / logique | Non |
! | 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érateur | Description | Exemple |
|---|---|---|
& | ET binaire | a & b |
| | OU binaire | a | b |
^ | XOR binaire | a ^ b |
~ | Complément (NOT) | ~a |
<< | Décalage gauche | a << 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ère | Checked | Unchecked |
|---|---|---|
| Héritage | Exception (sauf RuntimeException) | RuntimeException ou Error |
| Obligation de gestion | Oui (catch ou throws) | Non (optionnelle) |
| Utilisation typique | I/O, réseau, SQL | Erreurs de programmation, préconditions |
| Récupérable | Oui (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
- Types primitifs : 8 types, stockés sur la stack, performance optimale
- Classes wrapper : Boxing/unboxing automatique, cache [-128;127], utiliser
equals()pour comparer - String : Immuable, utiliser StringBuilder pour les concaténations en boucle
- Switch : Arrow syntax et pattern matching depuis Java 14/21
- Tableaux : Taille fixe, vérifier les bornes, préférer List en général
- Exceptions : Checked pour les conditions récupérables, Unchecked pour les bugs
- I/O : Toujours try-with-resources, préférer NIO.2
- try-with-resources : Fermeture automatique, gère les suppressed exceptions