[JAVA] Lors de la définition d'une classe, écrivez formatTo ainsi que toString (comment utiliser Formattable)

En 3 lignes

--Il est pratique de définir toString () et formatTo () lors de la création d'une classe. --toString a un format facile à comprendre par les développeurs pour le débogage. --Le formatTo a un format facile à comprendre pour l'utilisateur

introduction

À l'automne 2018 du JJUG CCC, «Revisiting Effective Java in 2018» [session] d'Edson Yanaga (https://jjug-cfp.cfapps.io/submissions/0fb90e6a-1717-4ce9-a2b9-56cef379a131) est apparu sobrement J'ai essayé d'organiser l'histoire du format, ce qui m'a impressionnée.

La session était très intéressante car c'était comme parler du savoir-faire de Effective Java 3rd Edition + α en live coding. En ce qui concerne formatTo, j'ai été surpris qu '"Y a-t-il une chose aussi utile dans le paquet java.lang?", Mais même si je cherchais avec Qiita, il n'y avait presque pas d'articles, alors j'ai décidé de l'écrire moi-même.

méthode toString

Tout d'abord, la méthode familière toString.

Aperçu

Une méthode qui renvoie une représentation sous forme de chaîne d'un objet. Il est appelé dans les situations suivantes.

--Lorsque vous combinez une chaîne et un objet --Lorsqu'il est passé à une méthode qui génère une chaîne de caractères, telle que System.out.println () --Lors de l'affichage des variables dans le débogueur

Il est défini dans la classe Object comme nom de classe +" @ "+ représentation hexadécimale de la valeur de hachage, mais il n'est pas très gérable et il est recommandé de le remplacer dans la sous-classe.

référence Object#toString

Exemple

Remplacer toString


class Person {
  private String name;
  private int age;

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  @Override
  public String toString() {
    return name + ", " + age + " years old.";
  }
}

En remplaçant ʻObject # toString` comme ceci, vous pouvez sortir une chaîne de caractères formatée simplement en passant la variable telle qu'elle est lors de la sortie en standard.

Exemple d'utilisation de toString


Person marty = new Person("Marty", 17);
Person doc = new Person("Doc", 65);

System.out.println(marty); // Marty, 17 years old.
System.out.println(doc); // Doc, 65 years old.

Avantages de l'utilisation de toString

En particulier, je pense qu'il est très "orienté objet" de savoir comment l'objet lui-même doit être stringifié, quelle que soit la manière dont il contient les données spécifiques.

Problème avec toString

--Charted dans le même format partout

Dans l'exemple ci-dessus, toute la sortie du débogueur et du journal sera sortie sous la forme de «Marty, 17 ans».

Je veux utiliser toString pour la sortie utilisateur, mais je veux des informations plus détaillées pour le débogage. C'est là qu'intervient la méthode formatTo.

formatTo

À propos de la chaîne de spécification de format avant formatTo

Chaîne de formatage

C'est un mécanisme qui peut faire quelque chose de similaire à printf ou sprintf en langage C.

System.out.printf("%s, %d years old.%n", "Marty", 17); // --> Marty, 17 years old.
String str = String.format("%s, %d years old.", "Marty", 17); // --> "Marty, 17 years old."Est généré

Comme la méthode de spécification du format est assez compliquée, veuillez consulter Javadoc de la classe Formatter. Cependant, tout ce que vous devez savoir pour le moment est que le format est spécifié par les paramètres suivants.

Méthode de spécification du format du formateur


 %[argument_index$][flags][width][.precision]conversion

La signification de chacun est la suivante.

//
//conversion (Comment convertir l'argument. La valeur entière est%d, le nombre réel est%Spécifiez la conversion en fonction du type tel que f)
//
String.format("%d", 12); // ---> "12"
System.out.printf("%f", Math.PI); // ---> 3.141593

//
//largeur (nombre minimum de caractères, l'espace manquant est rempli d'espaces)
//
String.format("%5d", 12); // ---> "   12"

//
// flag (-, +, #Indicateurs pour le contrôle d'options de formatage, etc.)
//
//Ajouter un signe moins pour s'aligner à gauche
String.format("%-5d", 12); // ---> "12   "
//Ajouter 0 pour remplir avec des zéros au lieu d'espaces
String.format("%05d", 12); // ---> "00012"
//Au début+Afficher le code avec
System.out.printf("%+5d", 12); // ---> "+12"
System.out.printf("%+5d", -12); // ---> "-12"

//
//précision (essentiellement utilisé pour désigner le nombre maximum de caractères)
//
//Dans le cas d'une valeur réelle, le nombre de chiffres après la virgule décimale (dans ce cas, il semble être traité comme "le nombre maximum de caractères après la virgule décimale")
System.out.printf("%4.2f", Math.PI); // ---> 3.14

Par exemple, «% -4.2f» a les valeurs suivantes.

conversion flag width precision
f - 4 2

Traitement lorsqu'un objet est passé en argument

Person doc = new Person("Doc", 65);
String.format("%30s", doc);
String.format("%30s", doc.toString()); //La sortie sera la même que celle-ci

De cette façon, si vous passez un objet comme argument, la méthode toString de cette classe sera appelée et le résultat sera traité comme une chaîne.

Cependant, si la classe passée à l'argument implémente l'interface Formattable, il est possible de contrôler la méthode de sortie au lieu du résultat de toString.

Interface formatable

Une interface dans laquelle les méthodes suivantes sont définies.

Interface formatable


void formatTo(Formatter formatter,
            int flags,
            int width,
            int precision)

Si vous passez une instance d'une classe qui implémente cette interface à une famille de méthodes printf, la méthode formatTo sera appelée pour formater la partie correspondante.

Utilisation de base

class Person implements Formattable {
  private String name;
  private int age;

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  @Override
  public String toString() {
    return "{name=" + name + ",age=" + age + "}";
  }

  @Override
  public void formatTo(Formatter formatter, int flags, int width, int precision) {
    //Le formatage est effectué en manipulant le formateur dans la méthode formatTo.
    //Comment utiliser String#Identique au format
    formatter.format("%s, %d years old.", name, age);
  }
}

Exemple d'utilisation d'une classe qui implémente formatTo


Person marty = new Person("Marty", 17);
Person doc = new Person("Doc", 65);

System.out.printf("%s%n", marty); // Marty, 17 years old.
System.out.printf("%s%n", doc); // Doc, 65 years old.

System.out.println(marty); // {name=Marty,age=17}
System.out.println(doc); // {name=Doc,age=65}

De cette façon, en laissant la sortie pour l'utilisateur à la méthode formatTo, la sortie pour le débogage peut être gérée librement afin qu'elle soit facile à utiliser du point de vue du développeur.

Multilingue

Encore plus pratique, la méthode formatTo peut gérer les informations locales. Par exemple, le support multilingue est possible comme suit.

@Override
public void formatTo(Formatter formatter, int flags, int width, int precision) {
  if (formatter.locale() == Locale.JAPAN) {
    formatter.format("%s est%J'ai d ans.", name, age);
  } else {
    formatter.format("%s, %d years old.", name, age);
  }
}

Par défaut, il s'agit de la locale du système, mais vous pouvez changer l'affichage en passant la locale comme argument de printf.

System.out.printf("%s%n", doc); //Doc a 65 ans.
System.out.printf(Locale.ENGLISH, "%s%n", doc); // Doc, 65 years old.

Exemple d'application ... 1

Puisque la largeur d'affichage est passée à l'argument de formatTo, il est possible de basculer automatiquement en fonction de la largeur de sortie.

Exemple de changement automatique de la notation yuan


private enum Gengou implements Formattable {
  MEIJI("Meiji", 'M'), TAISHO("Taisho", 'T'), SHOWA("Showa", 'S'), HEISEI("Heisei", 'H');

  private String name;
  private char initialLetter;

  private Gengou(String japaneseName, char initial) {
    this.name = japaneseName;
    this.initialLetter = initial;
  }

  /**
   *Si la largeur d'affichage est de 2 caractères ou plus, le nom officiel est affiché.
   */
  @Override
  public void formatTo(Formatter formatter, int flags, int width, int precision) {
    if (width >= 2) {
      formatter.format("%s", name);
    } else {
      formatter.format("%s", initialLetter);
    }
  }
}
System.out.printf("Cette année%3s 30 ans.%n", Gengou.HEISEI); // Cette année平成 30 年です。
System.out.printf("Cette année%1s 30 ans.%n", Gengou.HEISEI); // Cette annéeH 30 年です。

Exemple d'application ... 2

Puisque l'argument de formatTo peut être manipulé librement, il est possible de contrôler la sortie de la classe représentant le paragraphe (Paragraph) en utilisant la précision comme nombre de caractères d'ellipse et la largeur comme nombre total de caractères.

private static class Paragraph implements Formattable {
  private String content;

  public Paragraph(String content) {
    this.content = content;
  }

  @Override
  public void formatTo(Formatter formatter, int flags, int width, int precision) {
    StringBuilder sb = new StringBuilder();

    if (content.length() < width) {
      sb.append(content);
    } else {
      sb.append(content.substring(0, width - precision));

      if (precision > 0) {
        for (int i = 0; i < precision; i++) {
          sb.append(".");
        }
      }
    }

    formatter.format(sb.toString());
  }
}
Paragraph p = new Paragraph("This is very very long text!");
System.out.printf("%15.3s%n", p); // This is very...
System.out.printf("%10.4s%n", p); // This i....

Où utiliser

En écrivant l'article, j'ai remarqué que si la sortie est du texte comme la classe Person mentionnée dans l'exemple, je pense qu'il est plus simple de créer une classe séparée pour le formatage des messages.

Au contraire, dans le cas d'une classe de type objet de valeur qui a une signification en tant que valeur par elle-même, telle que «montant» et «ID utilisateur», je pense qu'il est toujours bon d'implémenter «Formattable». ..

Recommended Posts

Lors de la définition d'une classe, écrivez formatTo ainsi que toString (comment utiliser Formattable)
[Rails] Comment écrire lors de la création d'une sous-requête
Comment utiliser la classe Java
Comment utiliser la classe wrapper
Comment utiliser les méthodes de classe [Java]
[Java] Comment utiliser la classe Math
Un mémorandum sur l'utilisation d'Eclipse
[Java] Comment utiliser la classe File
[Basique] Comment écrire un auto-apprentissage Dockerfile ②
[Introduction à Java] Comment écrire un programme Java
Comment afficher les messages d'erreur et les messages de réussite lors de l'inscription en tant qu'utilisateur
[Traitement × Java] Comment utiliser la classe
Comment utiliser la classe Java Scanner (Remarque)
[Java] Comment utiliser la classe Calendar
[SpringBoot] Comment écrire un test de contrôleur
Rails: comment bien écrire une tâche de râteau
Comment convertir un contrat de solidité en une classe de contrat Java
[Java] Comment utiliser la classe Calendar et la classe Date
Je souhaite également utiliser Combine dans UIKit.
Comment exécuter l'application SpringBoot en tant que service
Comment utiliser un tableau pour la clé TreeMap
Comment écrire un test unitaire pour Spring Boot 2
java: Comment écrire une liste de types génériques [Note]
Comment écrire une recherche de comparaison de dates dans Rails
Comment écrire un mod de base dans Minecraft Forge 1.15.2
Remarques à vérifier lorsque vous essayez d'utiliser Lombok
Comment utiliser et appliquer la classe JFrame / Canvas de Java
Comment utiliser Map
Comment écrire des rails
Comment utiliser rbenv
Comment utiliser with_option
Comment utiliser fields_for
Comment utiliser la carte
Comment utiliser collection_select
Comment utiliser Twitter4J
Comment utiliser active_hash! !!
Comment utiliser MapStruct
Comment utiliser TreeSet
[Comment utiliser l'étiquette]
Comment écrire docker-compose
Comment utiliser l'identité
Comment utiliser le hachage
Comment écrire Mockito
Comment écrire un fichier de migration
Comment utiliser Dozer.mapper
Comment utiliser Gradle
Comment utiliser org.immutables
Comment utiliser java.util.stream.Collector
Comment utiliser VisualVM
Comment utiliser Map
Comment demander un fichier CSV au format JSON avec jMeter
[1er] RSpec débutant a essayé d'écrire ModelSpec en tant que débutant
Comment utiliser ArgumentMatchers tels que Mockito's any () dans Kotlin