[JAVA] L'aperçu Records publié dans JDK 14 est sympa alors essayons-le avec JShell

introduction

Si vous lisez JDK 14 Rampdown: Build 27 dans DZone

One might say that JDK 14 Early Access Build 27 is the "records build."

J'ai essayé ** Records ** avec JShell, que j'ai finalement essayé.

Veuillez noter que le contenu peut avoir changé lorsque JDK14 a été publié et lorsque Records a été officiellement publié après cela.

JEP 359: Records

JEP 359: Records [^ 1] est l'un des projets Ambers [^ 2] visant à améliorer la grammaire Java.

Java est publié tous les six mois, mais comme initialement prévu, des améliorations sont apportées en référence à divers langages modernes.

Dans de telles circonstances, ** Records ** est le [Constructor Shorthand] de TypeScript (https://maksimivanov.com/posts/typescript-constructor-shorthand/) et le [Primary Constructor] de Kotrin (https://kotlinlang.org). Comme dans la notation simple [^ 2] où l'argument constructeur de /docs/reference/classes.html#constructors sert également de définition de la variable de champ, ** la définition du nom de l'enregistrement et de l'argument (composant d'enregistrement), et du conteneur de données C'est un mécanisme pour définir la classe de données ** [^ 3].

Une fois que cela est disponible, les classes (et les méthodes de champ appropriées) peuvent être utilisées pour créer "juste un" conteneur de données "" avec des valeurs de retour, des DTO (Data Transfer Objects), des opérations d'API Stream, etc. En plus de la situation actuelle selon laquelle «vous devez créer une classe de données», l'option «vous pouvez créer explicitement une classe de données en tant que conteneur de données» est ajoutée.

Comment utiliser les enregistrements

Utilisation de base

La notation des enregistrements est également utilisée dans Qiita, "Changements dans la syntaxe Java envisagés par Amber" et "Quel est le meilleur, Kotlin ou futur Java? ", mais

record Person(String name) {};

Si vous écrivez

final class Person {

  //Variables de champ
  public final String name;

  //Constructeur avec arguments
  public Point(String name) {
    this.name = name;
  }

  //Obtention selon la variable de champ, hashCode, equals,toString est automatiquement implémenté
  //getter est un nom de méthode courant (identique au nom de la variable de champ).
  public String name() {
    return this.name;
  }
  public int hashCode() { /*Omis, mis en œuvre pour maintenir l'équivalence*/ }
  public boolean equals() { /*Omis, mis en œuvre pour maintenir l'équivalence*/ }
  public String toString() { /*Omis, renvoie le nom de la classe / le nom du champ / la valeur*/ }

  //c'est tout
}

Autrement dit, une classe de données équivalente à la classe immuable est automatiquement définie. Pratique.

Cependant, ce n'est pas seulement une notation pour la classe et une notation pour résoudre les plaques chauffantes comme lombok [^ 4], mais un nouveau mécanisme appelé "conteneur de données" (un mécanisme pour les classes restreintes comme enum). ) Est le but [^ 5], et l'élimination de la redondance n'est que le résultat **, qui est également écrit en JEP.

Lors de son utilisation, il semble préférable d'être conscient de ce domaine.

Déclaration d'interface

Utilisez des outils ainsi que de la classe.

Par exemple, si vous souhaitez définir une interface sérialisable,

record Person(String name) implements Serializable {};

Si vous écrivez

final class Person implements Serializable {
 //Abréviation
}

Cela signifie qu'une classe de données sérialisable a été définie.

Héritage

Ce ne peut pas être une superclasse ou une sous-classe. Comme mentionné précédemment, il ne s'agit pas d'une notation de classe conventionnelle.

Augmentez vos propres constructeurs et méthodes

Si vous voulez augmenter le nombre de constructeurs et de méthodes autres que ceux qui sont automatiquement définis, écrivez-les dans le corps (à l'intérieur de {}).

record Person(String name){
  Person() {
    this("Jhon Doe");
  }
  public int getNameLength() {
    return name.length();
  }
} 

Dans l'exemple ci-dessus, en plus du contenu défini automatiquement par record, un constructeur sans arguments et une méthode getNameLength sont préparés.

Vous pouvez augmenter le nombre de constructeurs, méthodes de classe, champs de classe, initialiseurs de classe, méthodes d'instance et initialiseurs d'instance. Les champs d'instance ne peuvent pas être ajoutés afin de ne pas modifier la forme de la classe de données [^ 6].

Essayez de l'utiliser avec JShell

Je vais l'utiliser avec JShell. Le PC que j'ai essayé contient JDK 14 Build 28 (14.ea.28-open) qui peut être installé avec SDKMAN! Au moment de l'écriture.

java -version
openjdk version "14-ea" 2020-03-17
OpenJDK Runtime Environment (build 14-ea+28-1366)
OpenJDK 64-Bit Server VM (build 14-ea+28-1366, mixed mode, sharing)

Ci-dessous, si vous souhaitez réellement le reproduire, veuillez copier et coller sauf pour jshell> et ...>.

1. Démarrez jshell

Démarrez JShell avec la fonctionnalité de version d'aperçu activée.

jshell --enable-preview

2. Définissez et instanciez une classe de données dans l'enregistrement

Définissons une classe de données Person qui a un nom de chaîne dans le composant d'enregistrement (c'est-à-dire le champ d'instance).

jshell> record Person(String name) {} ;

Puisque la classe de données est terminée sans aucun affichage, instancions-la.

jshell> var someone = new Person("Yamada");
someone ==> Person[name=Yamada]

Les instances peuvent désormais être référencées avec la variable quelqu'un.

Récupérons les données et utilisons la méthode.

jshell> System.out.println(someone.name());
Yamada
jshell> System.out.println(someone.toString());
Person[name=Yamada]

Vérifions également l'équivalence.

var other = new Person("Yamada");
   ...> someone.equals(other);
other ==> Person[name=Yamada]
$7 ==> true
jshell> other = new Person("Ichikawa");
   ...> someone.equals(other);
other ==> Person[name=Ichikawa]
$9 ==> false

Même si les instances sont différentes, vous pouvez juger de l'équivalence des valeurs de champ.

3. Essayez de configurer l'interface

Créons une personne et une personne sérialisable pour déterminer le type d'interface.

someone instanceof Serializable;
|Erreur:
|Type incompatible:Personne java.io.Impossible de convertir en sérialisable:
|  someone instanceof Serializable;
|  ^-----^
jshell> record SerializablePerson(String name) implements Serializable {} ;
   ...> var nextOne = new SerializablePerson("Sato");
   ...> nextOne instanceof Serializable;
nextOne ==> SerializablePerson[name=Sato]
$16 ==> true

Pour une instance de SerializablePerson qui est ʻimplements Serializable, le résultat de ʻinstanceof est true, indiquant que les paramètres d'interface sont en vigueur.

4. Ajoutez vos propres constructeurs et méthodes

Augmentons le constructeur par défaut dans le corps de Person et ajoutons une méthode qui utilise le composant record.

jshell> record Person(String name) {
   ...>         Person() {
   ...>             this("Jhon Doe");
   ...>         }
   ...>         public int getNameLength() {
   ...>             return name.length();
   ...>         }
   ...>     }

Quand Person est instancié avec le constructeur par défaut, il est initialisé avec Jhon Doe. Nous avons également ajouté une méthode d'instance qui renvoie la longueur.

Instancions et utilisons-le.

jshell> var someone = new Person();
   ...> someone.name();
   ...> someone.getNameLength();
someone ==> Person[name=Jhon Doe]
$3 ==> "Jhon Doe"
$4 ==> 8

Les paramètres du constructeur par défaut et les méthodes augmentées fonctionnent.

5. Autre

Class.isRecord()

Vous pouvez vérifier isRecord pour voir s'il s'agit d'une classe de données créée à partir d'un enregistrement.

jshell> Person.class.isRecord();
$8 ==> true
jshell> String.class.isRecord();
$9 ==> false

Class.getRecordComponents()

Renvoie le composant d'enregistrement de la classe de données créée à partir de l'enregistrement sous la forme d'un tableau de RecordComponent.

jshell> record Range(int lo, int hi) {};

jshell> Range.class.getRecordComponents();
$12 ==> RecordComponent[2] { int lo, int hi }

6. Exemple d'utilisation

Comme exemple d'utilisation, j'ai essayé un exemple d'opération intermédiaire de Stream dans Background text of JEP.

record Person(String name) {} ;

record PersonX(Person p, int hash) {
  PersonX(Person p) {
    this(p, p.name().toUpperCase().hashCode());
  }
}

//Je pense qu'il est censé provenir d'une source de données
var list = List.of(new Person("Yamada"), 
  new Person("Ichikawa"), 
  new Person("Sato"), 
  new Person("Tanaka"));
       
list.stream()
  .map(PersonX::new)
  .sorted(Comparator.comparingInt(PersonX::hash))
  .peek(System.out::println)
  .map(PersonX::p)
  .collect(Collectors.toList());

Cliquez ici pour les résultats de J Shell.

list ==> [Person[name=Yamada], Person[name=Ichikawa], Pers ... ato], Person[name=Tanaka]]
PersonX[person=Person[name=Tanaka], hash=-1827701194]
PersonX[person=Person[name=Yamada], hash=-1684585447]
PersonX[person=Person[name=Ichikawa], hash=-159644485]
PersonX[person=Person[name=Sato], hash=2537801]
$16 ==> [Person[name=Tanaka], Person[name=Yamada], Person[name=Ichikawa], Person[name=Sato]]

Bien que le résultat de l'exécution soit légèrement omis dans JShell, c'est un mécanisme pour convertir la liste des classes (de données) avec Yamada, Ichikawa, Sato, Tanaka en PersonX et les trier dans l'ordre de hachage [^ 7].

Person et PersonX sont des classes de données créées avec ** record, mais elles sont différentes des classes normales, mais le cadre de la classe n'est pas hors de portée, donc même lorsque vous l'utilisez, vous pouvez le mélanger avec des classes normales et agrandir la grammaire jusqu'à présent. Vous pouvez l'utiliser sans changer **.

Dans le passé, Person et PersonX devaient définir des classes et préparer des méthodes selon mutable / immuable ... mais si cela peut être traité comme un "conteneur de données",

record Person(String name) {} ;
record PersonX(Person p, int hash) {
  PersonX(Person p) {
    this(p, p.name().toUpperCase().hashCode());
  }
}

Écrivez-le.

Vous pouvez également utiliser Class # isRecord pour déterminer si vous devez faire la distinction entre une classe et un «conteneur de données».

En utilisant ** Records de cette manière, il semble qu'il sera possible d'incorporer dans Java la manière de distinguer «(simple) conteneur de données» et les classes conventionnelles, et de garder la description simple **.

en conclusion

En utilisant JShell, j'ai essayé de découvrir des enregistrements prévisualisés à partir de JDK14 avec JShell.

Pour être honnête, j'ai d'abord pensé "Mesures de la plaque de chaudière! Bien!", Mais quand j'ai lu le texte JEP et que je l'ai utilisé, il est apparu de la même manière que "enum" dans le texte. , L’aspect du nouveau mécanisme de «conteneur de données» s’adapte mieux.

Bien sûr, il existe un moyen de mélanger et d'utiliser rapidement la classe de données de Kotlin, mais JEP 359 semble être un symbole que Java devient plus facile à utiliser même s'il est standard.

Nous attendons avec impatience JDK14 et la sortie officielle après cela.

[^ 1]: Pour ceux qui ne sont pas bons en anglais journal de kagamihoge --JEP 359: documents traduits (aperçu) en texte Prenons un aperçu avec. [^ 2]: Le contenu spécifique d'Amber est facile à comprendre si vous lisez les [Modifications de la syntaxe Java envisagées par Amber] de Naoki Kishida (https://qiita.com/nowokay/items/6b8f109fe9dec4037d7e). .. [^ 3]: Background text of JEP contient la classe de données de Kotlin, la classe de cas de Scala et la classe d'enregistrement de C #. Il a été donné comme exemple de classe de données. [^ 4]: De JEP 359: Records,
* "Bien qu'il soit superficiellement tentant de traiter les enregistrements comme étant principalement une réduction standard, nous choisissez un objectif plus sémantique: modéliser les données sous forme de données. (Si la sémantique est correcte, le passe-partout prendra soin de lui-même.) "* [^ 5]: De JEP 359: Records,
* Les enregistrements sont un nouveau type de déclaration de type en langage Java. Comme une énumération, un record est une forme restreinte de classe. * [^ 6]: From JEP 359: Records
* "Le corps de l'enregistrement peut déclarer des méthodes statiques, des champs statiques, des initialiseurs statiques, des constructeurs, des instances méthodes, initialiseurs d'instance et types imbriqués. "* [^ 7]: J'ai modifié Typo dans le code d'origine, supprimé la limite et ajouté un aperçu pour plus de clarté.

Recommended Posts

L'aperçu Records publié dans JDK 14 est sympa alors essayons-le avec JShell
Java 10 (JDK 10) est sorti le 20 mars 2018, alors essayons Hello World
À propos des enregistrements ajoutés pour l'aperçu dans Java JDK 14
Dans Java Try-with-Resources, même si vous retournez dans la clause try, elle sera fermée correctement, alors revenons sans nous inquiéter