Skip to content

Instantly share code, notes, and snippets.

@evaisse
Created October 9, 2025 08:47
Show Gist options
  • Select an option

  • Save evaisse/7c04ceffc914a5177457526e51409e9c to your computer and use it in GitHub Desktop.

Select an option

Save evaisse/7c04ceffc914a5177457526e51409e9c to your computer and use it in GitHub Desktop.
Dart & comparaison , égalité logique (operator ==) , hashCode , compareTo ...
import 'dart:core';
/// Démo d’égalité, hashCode et comparaison d’entités.
///
/// Points clés:
/// - L’égalité logique (operator ==) est basée sur l’ID en minuscules.
/// - Le hashCode est cohérent avec == (utilise le même critère).
/// - compareTo (Comparable<MyEntity>) illustre un ordre de tri.
///
/// Subtilités:
/// - == et hashCode doivent être cohérents: si a == b alors a.hashCode == b.hashCode.
/// - hashCode n’implique PAS l’égalité (collisions possibles).
/// - compareTo définit un ordre de tri. S’il est cohérent avec ==, alors compareTo(a,b) == 0 <=> a == b.
/// Ce n’est pas obligatoire, mais fortement recommandé pour éviter des comportements surprenants dans des structures ordonnées.
class MyEntity implements Comparable<MyEntity> {
final String id;
final String name;
const MyEntity({required this.id, required this.name});
/// Égalité logique basée uniquement sur l’ID, insensible à la casse.
///
/// Bonnes pratiques:
/// - Vérifier identical(this, other) pour le fast path.
/// - Vérifier le type.
/// - Utiliser la même logique que hashCode.
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is MyEntity &&
runtimeType == other.runtimeType &&
id.toLowerCase() == other.id.toLowerCase();
}
/// hashCode doit utiliser le même critère que ==.
///
/// ATTENTION: Si tu modifies la logique d’égalité, mets à jour hashCode.
@override
int get hashCode => id.toLowerCase().hashCode;
/// Ordre naturel de tri.
///
/// Ici, on choisit un ordre cohérent avec ==:
/// - On compare d’abord l’ID insensible à la casse.
/// - Si les IDs sont égaux (case-insensitive), on compare le name (insensible à la casse)
/// uniquement pour départager dans l’ordre, PAS pour l’égalité.
///
/// Conséquence:
/// - Si compareTo retourne 0, les IDs (en minuscules) sont égaux, donc == est true.
/// - Deux entités avec même ID mais noms différents seront égales (== true), mais compareTo
/// peut quand même retourner 0 (si on le décide) ou départager via name.
///
/// Pour stricte cohérence == <=> compareTo == 0, il faut que compareTo ignore aussi name.
/// Ci-dessous, nous faisons ce choix pour la stricte cohérence: compareTo ne tient compte que de l’ID en minuscules.
@override
int compareTo(MyEntity other) {
final a = id.toLowerCase();
final b = other.id.toLowerCase();
return a.compareTo(b);
}
@override
String toString() => 'MyEntity(id: $id, name: $name)';
}
void main() {
print('--- Démonstration: ==, hashCode, compareTo ---');
// Création d’entités avec IDs équivalents logiquement (insensible à la casse)
final MyEntity entity1 = const MyEntity(id: 'ABC123', name: 'First Item');
final MyEntity entity2 = const MyEntity(id: 'abc123', name: 'Second Item');
final MyEntity entity3 = const MyEntity(id: 'Abc123', name: 'Third Item');
// Entité avec ID différent
final MyEntity entity4 = const MyEntity(id: 'DEF456', name: 'Another Item');
// Même ID que entity2 mais nom différent
final MyEntity entity5 = const MyEntity(id: 'abc123', name: 'Renamed Item');
print('\nEntities created:');
print('entity1: $entity1');
print('entity2: $entity2');
print('entity3: $entity3');
print('entity4: $entity4');
print('entity5: $entity5');
print('\n--- Égalité (operator ==) ---');
// Même ID logique -> true
print('entity1 == entity2: ${entity1 == entity2}'); // true
print('entity1 == entity3: ${entity1 == entity3}'); // true
print('entity2 == entity3: ${entity2 == entity3}'); // true
print('entity2 == entity5: ${entity2 == entity5}'); // true (name ignoré)
// ID différent -> false
print('entity1 == entity4: ${entity1 == entity4}'); // false
print('\n--- hashCode ---');
// hashCode égal pour objets égaux
print('entity1 hashCode: ${entity1.hashCode}');
print('entity2 hashCode: ${entity2.hashCode}');
print('entity3 hashCode: ${entity3.hashCode}');
print('entity5 hashCode: ${entity5.hashCode}'); // même que entity1..3
// hashCode probablement différent pour entité non égale
print('entity4 hashCode: ${entity4.hashCode}');
print('\nVérifications hashCode:');
print('entity1.hashCode == entity2.hashCode: ${entity1.hashCode == entity2.hashCode}'); // true
print('entity1.hashCode == entity3.hashCode: ${entity1.hashCode == entity3.hashCode}'); // true
print('entity2.hashCode == entity5.hashCode: ${entity2.hashCode == entity5.hashCode}'); // true
print('entity1.hashCode == entity4.hashCode: ${entity1.hashCode == entity4.hashCode}'); // false (très probable)
print('\n--- compareTo (Comparable) ---');
// Rappels:
// - compareTo < 0 : this < other
// - compareTo = 0 : "égaux" pour l’ordre (et ici aussi == true car on a lié l’ordre à l’égalité d’ID)
// - compareTo > 0 : this > other
// Même ID logique -> 0
print('entity1.compareTo(entity2): ${entity1.compareTo(entity2)}'); // 0
print('entity2.compareTo(entity3): ${entity2.compareTo(entity3)}'); // 0
print('entity1.compareTo(entity5): ${entity1.compareTo(entity5)}'); // 0
// IDs différents -> ordre alphabétique insensible à la casse sur l’ID
print('entity1.compareTo(entity4): ${entity1.compareTo(entity4)}'); // 'abc123' vs 'def456' -> < 0
print('\n--- Tri avec compareTo ---');
final list = <MyEntity>[entity4, entity3, entity1, entity5, entity2];
print('Avant tri: $list');
list.sort(); // utilise compareTo
print('Après tri (par ID case-insensitive): $list');
print('\n--- Utilisation dans Set et Map (fondés sur == et hashCode) ---');
// Set: pas de doublons logiques (== true) si hashCode est cohérent
final set = <MyEntity>{};
set.add(entity1);
set.add(entity2); // considéré comme doublon (même ID logique)
set.add(entity3); // doublon
set.add(entity4);
set.add(entity5); // doublon de entity1/2/3
print('Set (taille attendue 2: ABC123, DEF456): $set');
print('set.length: ${set.length}');
// Map: mêmes clés logiques se remplacent
final map = <MyEntity, String>{};
map[entity1] = 'value for entity1';
map[entity2] = 'value for entity2 (overwrites entity1)'; // même clé logique
map[entity3] = 'value for entity3 (overwrites again)';
map[entity4] = 'value for entity4';
map[entity5] = 'value for entity5 (overwrites again)';
print('Map keys: ${map.keys.toList()}');
print('Map values: ${map.values.toList()}');
print('map.length (attendu 2): ${map.length}');
print('\n--- Pièges et bonnes pratiques ---');
// 1) Incohérence entre == et hashCode:
// Si tu changes == sans mettre à jour hashCode, les Set/Map auront des comportements erratiques.
// 2) Collisions de hashCode:
// Deux objets différents peuvent avoir le même hashCode (c’est permis). Ne jamais inférer l’égalité via hashCode.
// 3) compareTo et ==:
// Dans cet exemple, compareTo == 0 <=> == true (cohérents, car tous deux basés sur l’ID insensible à la casse).
// Si compareTo prenait aussi le name, on pourrait avoir compareTo != 0 alors que == true, ce qui est parfois surprenant
// pour certaines structures ordonnées. Choisis la cohérence explicitement selon tes besoins.
// 4) Collections:
// - Set/Map utilisent ==/hashCode, pas compareTo.
// - sort()/SplayTree utilisent compareTo (ou un Comparator fourni).
// 5) Performance:
// - hashCode doit être rapide et stable (en fonction des champs immuables).
// - compareTo peut être plus coûteux; optimiser si nécessaire.
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment