Le problème avec Java 8 : Le Code Boilerplate
En Java 8, pour créer un simple DTO (Data Transfer Object), tu dois écrire :
- Les champs privés
- Un constructeur
- Des getters
- Un equals()
- Un hashCode()
- Un toString()
Résultat : 40+ lignes de code pour stocker 3 champs. Ridicule.
Java 14+ : Records
Un Record est une classe immuable qui génère automatiquement :
- Constructor
- Getters (sans le préfixe
get) - equals() et hashCode()
- toString()
Tout ça en 1 ligne de code.
Exemple concret
Java 8 : 40 lignes
public class UserDTO {
private final String id;
private final String name;
private final String email;
// + constructor, getters, equals, hashCode, toString...
}
Java 17+ : 1 ligne
record UserDTO(String id, String name, String email) {}
C'est TOUT. -97.5% de code !
Utilisation
// Créer un record
var user = new UserDTO("123", "Amadou", "amadou@dakar.dev");// Accéder aux champs (pas de get prefix)
String name = user.name();
String email = user.email();
// equals() et hashCode() automatiques
var user2 = new UserDTO("123", "Amadou", "amadou@dakar.dev");
boolean same = user.equals(user2); // true
// toString() lisible automatiquement
System.out.println(user);
// Output: UserDTO[id=123, name=Amadou, email=amadou@dakar.dev]
Cas d'usage parfaits pour les Records
- DTOs : UserDTO, ProductDTO, OrderDTO...
- Réponses API : ApiResponse, ErrorResponse...
- Tuples : Pair<K, V>, Triple<A, B, C>...
- Value Objects : Money, Coordinates, Address...
Records + Spring Boot = 💕
// DTO pour API REST
record CreateDomainRequest(String name, int years) {}record DomainResponse(String id, String name, String status, LocalDate expiresAt) {}
@RestController
@RequestMapping("/api/domains")
public class DomainController {
@PostMapping
public DomainResponse create(@RequestBody CreateDomainRequest request) {
// Utilisation directe du record
Domain domain = domainService.create(request.name(), request.years());
// Retour d'un record
return new DomainResponse(
domain.getId(),
domain.getName(),
domain.getStatus(),
domain.getExpiresAt()
);
}
}
Productivité x10 : Focus sur la logique métier, pas sur le boilerplate.
// ========== JAVA 8 : 40 LIGNES DE BOILERPLATE ==========
public class DomainDTO {
private final String id;
private final String name;
private final String status;
private final LocalDate expiresAt;
public DomainDTO(String id, String name, String status, LocalDate expiresAt) {
this.id = id;
this.name = name;
this.status = status;
this.expiresAt = expiresAt;
}
public String getId() { return id; }
public String getName() { return name; }
public String getStatus() { return status; }
public LocalDate getExpiresAt() { return expiresAt; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DomainDTO that = (DomainDTO) o;
return Objects.equals(id, that.id) &&
Objects.equals(name, that.name) &&
Objects.equals(status, that.status) &&
Objects.equals(expiresAt, that.expiresAt);
}
@Override
public int hashCode() {
return Objects.hash(id, name, status, expiresAt);
}
@Override
public String toString() {
return "DomainDTO{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", status='" + status + '\'' +
", expiresAt=" + expiresAt +
'}';
}
}
// ========== JAVA 17+ : 1 LIGNE !!! ==========
record DomainDTO(String id, String name, String status, LocalDate expiresAt) {}
// ========== UTILISATION ==========
// Créer un record
var domain = new DomainDTO(
"123",
"dakar.dev",
"ACTIVE",
LocalDate.of(2027, 1, 13)
);
// Accès aux champs (sans préfixe get)
String name = domain.name();
LocalDate expires = domain.expiresAt();
// Equals automatique
var domain2 = new DomainDTO("123", "dakar.dev", "ACTIVE", LocalDate.of(2027, 1, 13));
boolean same = domain.equals(domain2); // true
// ToString lisible
System.out.println(domain);
// Output: DomainDTO[id=123, name=dakar.dev, status=ACTIVE, expiresAt=2027-01-13]