--It is convenient to define toString ()
and formatTo ()
when creating a class.
--toString
has a developer-friendly format for debugging
--For formatTo
, use an easy-to-understand format from the user's perspective.
At JJUG CCC 2018 fall, Edson Yanaga appeared in a session called "Revisiting Effective Java in 2018". I tried to organize the story about formatTo that was impressive to me.
The session was very interesting because it was like talking about the know-how of Effective Java 3rd Edition + α while live coding. Among them, regarding formatTo, I was surprised that "Is there such a useful thing in the java.lang
package? ", But even if I searched with Qiita, there were almost no articles, so I decided to write it myself.
First of all, the familiar toString
method.
A method that returns a string representation of an object. It is called in the following situations.
--When you combine a string and an object
--When passed to a method that outputs as a character string, such as System.out.println ()
--When displaying variables in the debugger
It is defined in the Object class as class name +" @ "+ a hexadecimal representation of the hash value
, but it's not very manageable and it is recommended to override it in a subclass.
reference Object#toString
Override 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.";
}
}
By overriding ʻObject # toString` like this, you can output a formatted character string just by passing the variable as it is when outputting as standard.
Example of using 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.
--No need to create getters (no need to expose fields to the outside world) --You can hide the process of converting an object into a string.
In particular, I think it is very "object-oriented" to know how the object itself should be stringified, regardless of how it holds the specific data.
--Stringized in the same format everywhere --Private variables that are not used as user output but are convenient to see during debugging cannot be output.
In the above example, the debugger and log output will all be output in the form Marty, 17 years old.
.
I want to use toString
for user output, but I want more detailed information for debugging.
That's where the formatTo
method comes in.
formatTo
About the format string before formatTo
It is a mechanism that can do something similar to printf or sprintf in C language.
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."Is generated
How to specify the format is quite complicated, so please see Javadoc of Formatter class. However, all you need to know for the time being is that the format is specified by the following parameters.
Formatter format specification method
%[argument_index$][flags][width][.precision]conversion
The meaning of each is as follows.
//
//conversion (argument conversion method. Integer value is%d, the real number is%Specify the conversion according to the type such as f)
//
String.format("%d", 12); // ---> "12"
System.out.printf("%f", Math.PI); // ---> 3.141593
//
//width (minimum number of characters, missing space is filled with spaces)
//
String.format("%5d", 12); // ---> " 12"
//
// flag (-, +, #Flags for literally formatting option control, etc.)
//
//Add a minus to align left
String.format("%-5d", 12); // ---> "12 "
//Add 0 to fill with zeros instead of spaces
String.format("%05d", 12); // ---> "00012"
//At the beginning+Display the sign with
System.out.printf("%+5d", 12); // ---> "+12"
System.out.printf("%+5d", -12); // ---> "-12"
//
//precision (basically used to mean the maximum number of characters)
//
//In the case of a real number, the number of digits after the decimal point (in this case, it seems to be treated as "the maximum number of characters after the decimal point")
System.out.printf("%4.2f", Math.PI); // ---> 3.14
For example, % -4.2f
has the following values.
conversion | flag | width | precision |
---|---|---|---|
f | - | 4 | 2 |
Person doc = new Person("Doc", 65);
String.format("%30s", doc);
String.format("%30s", doc.toString()); //The output will be the same as this
In this way, if you pass an object as an argument, the toString
method of that class will be called and the result will be treated as a string.
However, if the class passed as an argument implements the Formattable
interface, it is possible to control the output method, not just the result of toString
.
An interface that defines the following methods.
Formattable interface
void formatTo(Formatter formatter,
int flags,
int width,
int precision)
If you pass an instance of a class that implements this interface to a printf method, the formatTo
method will be called to format the corresponding part.
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) {
//Formatter is performed by manipulating formatter in the formatTo method.
//How to use String#Same as format
formatter.format("%s, %d years old.", name, age);
}
}
Usage example of class that implements 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}
In this way, by leaving the output for the user to the formatTo
method, the output for debugging can be freely handled so that it is easy to use from the developer's point of view.
Even more convenient, the formatTo
method can handle locale information.
For example, multilingual support is possible as follows.
@Override
public void formatTo(Formatter formatter, int flags, int width, int precision) {
if (formatter.locale() == Locale.JAPAN) {
formatter.format("%s is%I'm d years old.", name, age);
} else {
formatter.format("%s, %d years old.", name, age);
}
}
By default, it is the system locale, but you can switch the display by passing the locale as an argument of printf.
System.out.printf("%s%n", doc); //Doc is 65 years old.
System.out.printf(Locale.ENGLISH, "%s%n", doc); // Doc, 65 years old.
Since the display width is passed to the argument of formatTo
, it is possible to switch automatically depending on the output width.
Example of automatically switching the era notation
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;
}
/**
*If the display width is 2 characters or more, the official name is output, and if it is 1 character, the alphabet is output as 1 character.
*/
@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("This year%3s 30 years.%n", Gengou.HEISEI); // This year平成 30 年です。
System.out.printf("This year%1s 30 years.%n", Gengou.HEISEI); // This yearH 30 年です。
Since the argument of formatTo
can be handled freely, it is possible to control the output of the class representing the paragraph (Paragraph) by using precision as the number of ellipsis characters and width as the total number of characters.
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....
As I wrote the article, I noticed that if the output is text like the Person
class mentioned in the example, I think it is simpler to create a separate class for message molding.
On the contrary, in the case of a value object-like class that has a meaning as a value by itself, such as "amount" and "user ID", I feel that it is always good to implement Formattable
. ..
Recommended Posts