2. Types, égalité et opérateurs
Exemples complets : lessons/l02 — après mvn compile, lancez-en un avec java -cp target/classes lessons.l02.Numbers ; la CI vérifie chaque sortie ci-dessous.
Deux sortes de types, et pas de struct
Section intitulée « Deux sortes de types, et pas de struct »| C# | Java | |
|---|---|---|
| Types valeur intégrés | int, long, double, bool, char, decimal… |
8 types primitifs : byte short int long float double boolean char |
| Types valeur définis par l’utilisateur | struct, record struct |
aucun (voir Project Valhalla) |
| Tout le reste | types référence | types référence |
| Entiers non signés | byte, ushort, uint, ulong |
aucun |
| Arithmétique décimale | decimal |
la classe BigDecimal |
| Primitif dans un contexte objet | boxing vers object |
boxing vers une classe enveloppe (wrapper) : Integer, Long, Boolean… |
En C#, int est System.Int32, une struct dotée de méthodes. En Java, int est un primitif sans méthodes, et Integer est une classe distincte. Le compilateur convertit automatiquement de l’un à l’autre (autoboxing), ce qui est pratique jusqu’au jour où ça ne l’est plus.
int max = Integer.MAX_VALUE;System.out.println(max + 1);
try { System.out.println(Math.addExact(max, 1));} catch (ArithmeticException e) { System.out.println("ArithmeticException: " + e.getMessage());}
byte b = (byte) 200;System.out.println(b);System.out.println(Byte.toUnsignedInt(b));
int allOnes = -1;System.out.println(Integer.toUnsignedString(allOnes));System.out.println(Integer.divideUnsigned(allOnes, 2));-2147483648ArithmeticException: integer overflow-5620042949672952147483647- Le dépassement reboucle en silence, exactement comme C# dans son contexte
uncheckedpar défaut. Java n’a pas de mot-cléchecked:Math.addExact,multiplyExactet consorts lèvent une exception à la place. byteest signé (de −128 à 127). C’est en lisant des octets depuis un fichier ou un tampon réseau que ça fait mal :(byte) 200vaut-56, etByte.toUnsignedIntvous rend le 200. En C#,byteest non signé et c’estsbytequi est signé.- Pas de
uintni deulong, maisIntegeretLongont des méthodes statiques qui interprètent les mêmes bits comme non signés.
Le côté C#, exécuté par la CI du cours :
-2147483648OverflowException: Arithmetic operation resulted in an overflow.200-56La division et l’arithmétique sur les char se comportent de la même façon dans les deux langages : la division entière par zéro lève une exception (ArithmeticException: / by zero contre DivideByZeroException), la division en virgule flottante donne Infinity, et 'a' + 1 est l’int 98.
== compare les références pour les objets
Section intitulée « == compare les références pour les objets »C’est celui à retenir. Pour les primitifs, == compare les valeurs. Pour les objets — Integer et String compris — il compare les références, et Java n’a pas de surcharge d’opérateurs pour changer cela.
Integer a = 127;Integer b = 127;Integer c = 128;Integer d = 128;System.out.println(a == b);System.out.println(c == d);System.out.println(c.equals(d));
String literal = "hello";String sameLiteral = "hello";String built = new StringBuilder("hel").append("lo").toString();System.out.println(literal == sameLiteral);System.out.println(literal == built);System.out.println(literal.equals(built));truefalsetruetruefalsetruea == bvauttrueuniquement parce que l’autoboxing passe parInteger.valueOf, qui met en cache les valeurs de −128 à 127. À partir de 128, chaque boxing crée un nouvel objet. Du code qui compare desIntegeravec==passe tous les tests avec de petits identifiants et échoue en production.- Deux littéraux de chaîne identiques sont le même objet (les littéraux sont internés), mais une chaîne construite à l’exécution ne l’est pas.
- Utilisez
equalspour les objets, toujours.Objects.equals(x, y)gère aussinull.
En C#, string surcharge == pour comparer le contenu, donc literal == built vaut True. Des valeurs boxées comparées en tant qu’object se comportent comme en Java, mais on écrit rarement cela en C# :
object boxedA = 127, boxedB = 127;Console.WriteLine(boxedA == boxedB); // False : comparaison de références sur object
string literal = "hello";string built = new System.Text.StringBuilder("hel").Append("lo").ToString();Console.WriteLine(literal == built); // True : string surcharge ==Le boxing fait aussi entrer null dans l’arithmétique. Déballer un Integer null lève une exception, et depuis Java 14 le message indique exactement quelle variable était nulle (NullPointerExceptions explicites) :
Integer missing = null;int value = missing;NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "missing" is nullvar et final
Section intitulée « var et final »var name = "Ada"; // inféré en Stringfinal var year = 1843; // ne peut pas être réaffecté ; C# n'a pas d'équivalent pour les variables localesvar fonctionne comme en C#, mais uniquement pour les variables locales avec un initialiseur :
class VarField { var count = 0;}VarField.java:2: error: 'var' is not allowed here var count = 0; ^1 errorfinal sur une variable locale ou un champ signifie « affecté exactement une fois » — le readonly de C# pour les champs, et quelque chose pour lequel C# n’a pas de mot-clé sur les variables locales. Il n’y a pas de const : une constante est un champ static final.
class ReassignFinal { void run() { final int limit = 10; limit = 20; }}ReassignFinal.java:4: error: cannot assign a value to final variable limit limit = 20; ^1 errorLes conversions sont aussi strictes qu’en C#
Section intitulée « Les conversions sont aussi strictes qu’en C# »Une conversion restrictive exige un cast, un int n’est pas un boolean, et une variable locale doit être définitivement affectée avant d’être utilisée. Les règles correspondent à celles de C# presque une à une :
class LossyConversion { void run() { double price = 3.5; int rounded = price; }}LossyConversion.java:4: error: incompatible types: possible lossy conversion from double to int int rounded = price; ^1 errorclass Unassigned { int run(boolean flag) { int result; if (flag) { result = 1; } return result; }}Unassigned.java:7: error: variable result might not have been initialized return result; ^1 errorChaînes : formatage et blocs de texte
Section intitulée « Chaînes : formatage et blocs de texte »Java n’a pas d’interpolation de chaînes. Les string templates ont été proposés en préversion dans Java 21 et 22, puis retirés. On formate avec String.formatted (ou String.format), avec des spécificateurs à la printf :
System.out.println("%s published her notes in %d.".formatted(name, year));Ada published her notes in 1843.Les blocs de texte sont les chaînes multilignes de Java, proches des littéraux de chaîne bruts de C#. Le """ fermant fixe l’indentation à retirer :
String json = """ { "name": "%s", "year": %d } """.formatted(name, year);System.out.print(json);{ "name": "Ada", "year": 1843}Expressions switch
Section intitulée « Expressions switch »Le switch à flèches est une expression, comme l’expression switch de C#, et ne passe jamais d’un cas au suivant (pas de fall-through). Un bloc qui calcule la valeur se termine par yield :
var size = Size.MEDIUM;int price = switch (size) { case SMALL -> 3; case MEDIUM -> 4; case LARGE -> { int base = 4; yield base + 1; }};Un switch sur un enum qui énumère toutes les constantes n’a pas besoin de default. Sur un int, si, et Java en fait une erreur là où C# ne donne qu’un avertissement (CS8509) :
class SwitchNotExhaustive { String describe(int code) { return switch (code) { case 200 -> "OK"; case 404 -> "Not Found"; }; }}SwitchNotExhaustive.java:3: error: the switch expression does not cover all possible input values return switch (code) { ^1 errorLa leçon 8 va plus loin avec switch, grâce aux motifs et aux types scellés.
À retenir
Section intitulée « À retenir »- Java a huit primitifs et aucun type valeur défini par l’utilisateur ; tout le reste est une référence.
==sur des objets compare des références : utilisezequals. Le cache d’Integerdonne l’illusion que==fonctionne pour les petits nombres.- Le dépassement est silencieux ;
Math.*Exactest l’équivalent dechecked.byteest signé et il n’y a pas de types non signés. varest réservé aux variables locales ;finalsignifie affecté une seule fois ; les constantes sontstatic final.- Pas d’interpolation :
formatted, les blocs de texte, et les expressionsswitchavec->etyield.
Exercices
Section intitulée « Exercices »- Sans l’exécuter, prédisez la sortie. Expliquez ensuite comment corriger la méthode.
static boolean sameId(Integer left, Integer right) { return left == right;}// sameId(42, 42) → ?// sameId(1000, 1000) → ?Solution
true, puis false : 42 est dans le cache d’Integer, donc les deux arguments sont boxés vers le même objet ; 1000 ne l’est pas, ce sont donc deux objets. Comparez les valeurs avec left.equals(right), ou Objects.equals(left, right) si l’un des deux peut être null. Si null n’est pas un identifiant valide, la meilleure correction est de déclarer les paramètres en int.
- Une méthode C# lit une longueur sous forme de
uintdans un en-tête binaire. Écrivez l’équivalent Java deuint length = BitConverter.ToUInt32(bytes, 0);pour unbyte[]little-endian, en renvoyant une valeur capable de contenir n’importe queluint.
Solution
static long readUInt32LittleEndian(byte[] bytes) { return java.nio.ByteBuffer.wrap(bytes, 0, 4) .order(java.nio.ByteOrder.LITTLE_ENDIAN) .getInt() & 0xFFFFFFFFL;}getInt() renvoie un int signé ; le masque 0xFFFFFFFFL l’élargit en long sans extension de signe (Integer.toUnsignedLong fait la même chose). ByteBuffer est big-endian par défaut, contrairement à BitConverter sur x86, d’où l’order explicite.
- Réécrivez cette méthode C# en Java, en gardant une expression :
static string Classify(int status) => status switch{ >= 200 and < 300 => "success", 404 => "not found", _ => "other",};Solution
static String classify(int status) { return switch (status) { case 404 -> "not found"; default -> status >= 200 && status < 300 ? "success" : "other"; };}Java 25 n’a pas de motifs relationnels comme >= 200 and < 300 sur les primitifs (les motifs primitifs sont encore en préversion) : la vérification d’intervalle passe donc dans la branche default — ou toute la méthode devient une chaîne de if.