在日常开发中,我们总需要打印日志,记录程序中一些关键对象的信息,大大提高bug的排查速度。但是如果类的设计不规范,这些类的对象信息是不能被Log框架或者System.out.println()语句规范地打印出来的。比如下面这个Person类,这种只有成员变量以及成员变量的get/set函数的类,常被用作VO、DTO、DO等,如果直接按照以下方式,其对象信息打印出来对日志分析基本没有用处。(本文代码运行环境采用JDK版本为JDK9)
《一》采用System.out.println()方式打印对象信息:
public class Person {
private int age;
private String name;
public Person(){
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public static void main(String []args){
Person p = new Person(22,"Allen");
Person p1 = new Person(18,"Peter");
System.out.println(p);
System.out.println(p1);
}
}
我们看一下输出结果:
Person@64c64813
Person@3ecf72fd
可见根本没有打印出我们想看到的对象p的成员变量信息,这个打印结果是怎么来的呢?我们先看一下System.out.println()的内部实现(java.io.PrintStream类):
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
可以看出先是打印String.valueOf(x)的信息,然后再打印一个换行,而String.valueOf(x)的内部实现如下:
/**
* Returns the string representation of the {@code Object} argument.
*
* @param obj an {@code Object}.
* @return if the argument is {@code null}, then a string equal to
* {@code "null"}; otherwise, the value of
* {@code obj.toString()} is returned.
* @see java.lang.Object#toString()
*/
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
如果对象为null就返回字符串null,否则返回这个对象的toString()函数的结果,我们需要注意toString()这个函数是定义在Object类中的,其在Object类中的定义如下:
/**
* Returns a string representation of the object. In general, the
* {@code toString} method returns a string that
* "textually represents" this object. The result should
* be a concise but informative representation that is easy for a
* person to read.
* It is recommended that all subclasses override this method.
* <p>
* The {@code toString} method for class {@code Object}
* returns a string consisting of the name of the class of which the
* object is an instance, the at-sign character `{@code @}', and
* the unsigned hexadecimal representation of the hash code of the
* object. In other words, this method returns a string equal to the
* value of:
* <blockquote>
* <pre>
* getClass().getName() + '@' + Integer.toHexString(hashCode())
* </pre></blockquote>
*
* @return a string representation of the object.
*/
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
从中我们不难看到每个类如果没有重写toString()函数的话,默认是返回该类的名称 + “@” + 该对象hashCode值得十六进制数字;所以JDK官方是推荐我们所有的类都应该重写此类(It is recommended that all subclasses override this method.)。如果我们在上面的Person类中重写该方法,那么System.out.println语句就会按照我们的重写的toString()方法打印对象信息,如下:
public class Person {
private int age;
private String name;
public Person() {
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Person(age:" + age + ",name:" + name + ")";
}
public static void main(String[] args) {
Person p = new Person(22, "Allen");
Person p1 = new Person(18, "Peter");
System.out.println(p);
System.out.println(p1);
}
}
输出结果:
Person(age:22,name:Allen)
Person(age:18,name:Peter)
这样就可以清晰地打印出每个Person对象的成员信息了。
如果你使用了Lombok插件,那么直接使用它的@Data注解,无需实现toString()函数的复写了,如下:
import lombok.Data;
@Data
public class Person {
private int age;
private String name;
public Person() {
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public static void main(String[] args) {
Person p = new Person(22, "Allen");
Person p1 = new Person(18, "Peter");
System.out.println(p);
System.out.println(p1);
}
}
代码运行结果:
Person(age=22, name=Allen)
Person(age=18, name=Peter)
这里使用的Lombok插件版本为1.16.10,可以看出插件帮我们完成了对一个对象的描述功能。如果我们既使用了Lombok插件,又自己重写了一下toString()函数,会有什么的情况呢?我们试一下:
import lombok.Data;
@Data
public class Person {
private int age;
private String name;
public Person() {
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "年龄:" + age + ";名字:" + name;
}
public static void main(String[] args) {
Person p = new Person(22, "Allen");
Person p1 = new Person(18, "Peter");
System.out.println(p);
System.out.println(p1);
}
}
输出结果为:
年龄:22;名字:Allen
年龄:18;名字:Peter
可以看到打印信息使用的是我们重写的toString()方式,具体Lombok插件是如何实现的,大家感兴趣可以看一下Lombok的实现机制。
本次主要讲了System.out.println()这种最基础的的方式打印对象信息需要注意的地方,主要就是toString()函数,下一节我们看一下一些日志框架(例如log4j等)是如何实现对象信息的打印效果的。