🏅 Records du monde 🌍

Tour d’horizon et cas d’utilisation des records

World Record

guinness

Record depuis Java 16

  • 03/2020 - JDK 14 : JEP 359 Records (Preview)

  • 09/2020 - JDK 15 : JEP 384 Records (Second Preview)

  • ⭐️ 03/2021 - JDK 16 : JEP 395: Records

  • 09/2021 - JDK 17 (LTS)

public record MedalCount(int gold, int silver, int bronze)

Des champs immutables

public record MedalCount(int gold, int silver, int bronze)

var medalCount = new MedalCount(1, 2, 3);
var gold = medalCount.gold;
var silver = medalCount.silver;
var bronze = medalCount.bronze;
public class MedalCount {
  private final int gold;
  private final int silver;
  private final int bronze;

  public MedalCount(int gold, int silver, int bronze) {
      this.gold = gold;
      this.silver = silver;
      this.bronze = bronze;
  }
  ....
}

toString

public record MedalCount(int gold, int silver, int bronze)
public class MedalCount {
  private final int gold;
  private final int silver;
  private final int bronze;
  ....

  @Override
  public String toString() {
    return "MedalCount[gold=" + gold
            + ", silver=" + silver
            + ", bronze=" + bronze + "]";
  }
}

equals / hashCode

public record MedalCount(int gold, int silver, int bronze)
public class MedalCount {
  private final int gold;
  private final int silver;
  private final int bronze;
  ....

  @Override
  public int hashCode() {
      return Objects.hash(gold, silver, bronze);
  }

  @Override
  public boolean equals(Object obj) {
      if (this == obj) {
          return true;
      } else if (!(obj instanceof MedalCount)) {
          return false;
      } else {
          MedalCount other = (MedalCount) obj;
          return Objects.equals(gold, other.gold)
            && Objects.equals(silver, other.silver)
            && Objects.equals(bronze, other.bronze);
      }
  }
}

Benoit Prioux

binout

alan eng

De Java à Python, en passant par Kotlin

timeline

🪱 Avant les Records, Lombok

@Data
public class MedalCount {
    private final int gold;
    private final int silver;
    private final int bronze;
}

MedalCount medalCount = new MedalCount(1, 2, 3);
int gold = medalCount.getGold();
int silver = medalCount.getSilver();
int bronze = medalCount.getBronze();

🪱 Avant les Records, Lombok

  • 🔗 Dépendance nécessaire

  • 🪄 Configuration Annotation Processor

  • 🤯 Ouvre la porte à d’autres fonctionnalités

🪜 case class en Scala

case class MedalCount(gold: Int, silver: Int, bronze: Int)

🅺 data class en Kotlin

data class MedalCount(gold: Int, silver: Int, bronze: Int)

🐍 @dataclass en Python

@dataclass(Frozen=True)
class MedalCount:
  gold: int
  silver: int
  bronze: int

Oui mais pourquoi faire ?

record why

🔀 Data Transfer Objects

  • Objet de transport de donnée pour faciliter la sérialisation/désérialisation

  • Facilité d’écriture avec les records

dto

😱 Primitive Obsession - Problème

  • Utilisation de types primitifs pour modéliser des "petits" objets

public MedalCount getMedalCount(String country,
                                String olympicGame,
                                String sport) {
  ....
}

var medalCount = getMedalCount("FR", "14", "swimming"); ✅
var medalCount = getMedalCount("14", "FR", "swimming"); 🤯

💡 Primitive Obsession - Record !

  • Identifier Type pattern

public record CountryCode(String value)
public record OlympicGameId(String value)
public record SportName(String value)

public MedalCount getMedalCount(CountryCode countryCode,
                                OlympicGameId olympicGameId,
                                SportName sportName) {
  ....
}

var medalCount = getMedalCount(CountryCode("FR"),
                               OlympicGameId("14"),
                               SportName("swimming"));

✅ Validation des données

  • Validation post contruction pour assurer des invariants métier

public record MedalCount(int gold, int silver, int bronze) {

  public MedalCount {
    if (gold < 0 || silver < 0 || bronze < 0) {
      throw new IllegalArgumentException(
        "Medal count should be positive")
    }
  }
}

Domain Driven Design

ddd

Entity vs Value Object

  • Entity: objet métier avec une identité et un cycle de vie

  • Value Object: objet métier immutable et défini par ses attributs

🎉 Les records, solution idéale pour modéliser les Value Objects ! 🎉

Exemple - Value Object

public record MedalCount(int gold, int silver, int bronze)
              implements Comparable<MedalCount> {

  public MedalCount {
    if (gold < 0 || silver < 0 || bronze < 0) {
      throw new IllegalArgumentException(
        "Medal count should be positive")
    }
  }

  public int total() {
      return gold + silver + bronze;
  }

  @Override
  public int compareTo(MedalCount medalCount) {
      return Comparator.comparing(MedalCount::gold)
              .thenComparing(MedalCount::silver)
              .thenComparing(MedalCount::bronze)
              .compare(this, medalCount);
  }
}

Encore d’autres méthodes !

public record MedalCount(int gold, int silver, int bronze) {

  public MedalCount add(MedalCount medalCount) {
      return new MedalCount(
              gold + medalCount.gold,
              silver + medalCount.silver,
              bronze + medalCount.bronze);
  }

}

Vous avez reconnu le Monoid 🙈

public record MedalCount(int gold, int silver, int bronze) {

  /* Binary Operator */
  public MedalCount add(MedalCount medalCount) {
      return new MedalCount(
              gold + medalCount.gold,
              silver + medalCount.silver,
              bronze + medalCount.bronze());
  }

  /* Neutral Element */
  public static MedalCount NEUTRAL_ELEMENT
                      = new MedalCount(0, 0, 0);
}

Vous avez reconnu le Monoid 🙈

var count1 = new MedalCount(5, 2, 1);
var count2 = count1.add(NEUTRAL_ELEMENT);
print(count1.equals(count2)) /* true */

/* Associativity */
var count3 = new MedalCount(1, 3, 0);

var countLeft  = count1.add(count2.add(count3))
var countRight = (count1.add(count2)).add(count3)
print(countLeft.equals(countRight)) /* true */

Monoid : DDD + FP

Merci 🙏