你好呀,我是沉默王二,一个和黄家驹一样身高,和刘德华一样颜值的程序员。虽然已经写了十多年的 Java 代码,但仍然觉得自己是个菜鸟(请允许我惭愧一下)。

在一个月黑风高的夜晚,我思前想后,觉得再也不能这么蹉跎下去了。于是痛下决心,准备通过输出的方式倒逼输入,以此来修炼自己的内功,从而进阶成为一名真正意义上的大神。与此同时,希望这些文章能够帮助到更多的读者,让大家在学习的路上不再寂寞、空虚和冷。


为了更好的输入,我选择 Stack Overflow 作为战斗的第一线,毕竟很多前辈都在强烈推荐。本篇文章,我们来探讨一下如何优雅地打印一个Java对象。

真没想到,这个问题的访问量像阿尔泰山一样高,访问量足足有 29+ 万次,这不得了啊!说明有很多很多的程序员被这个问题困扰过。

来回顾一下提问者的问题吧。

提问者定义了这样一个类:

public class Cmower {
    private String name;

    public Cmower(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

然后创建了一个该类的对象,并尝试打印它:

Cmower cmower = new Cmower("沉默王二");
System.out.println(cmower);

但是输出的结果并不是他想要的:

com.cmower.java_demo.stackoverflow.printObject.Cmower@355da254

除此之外,他在打印数组的时候也出现了相似的问题:

Cmower [] cmowers = {new Cmower("沉默王二"), new Cmower("沉默王三")};
System.out.println(cmowers);

输出结果为:

[Lcom.cmower.java_demo.stackoverflow.printObject.Cmower;@4dc63996

Cmower@355da254[LCmower;@4dc63996 这样的输出结果代表着什么意思呢?怎么样才能把 Cmower 类的 name 打印出来呢?以及如何打印一个对象的列表(数组或者集合)呢?

如果大家也被这样的问题困扰过,或者正在被困扰,就请随来,咱们肩并肩手拉手一起梳理一下这个问题,并找出最佳答案。Duang、Duang、Duang,打怪进阶喽!

01、究竟发生了什么?

所有的 Java 对象都默认附带了一个 toString() 的方法,当我们尝试打印这个对象的时候,该方法就会被调用。

System.out.println(object);  // 调用 object.toString()

toString() 方法由 Object 类(所有 Java 对象的超类)定义,该方法会返回一个看起来晦涩难懂的字符串:

1)Class 名,由包名和类名组成,比如 com.Cmower
2)@ 连接符;
3)十六进制的哈希码。

来看一下该方法的源码:

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

数组和普通的 Java 对象类似,只有一点点不同——追踪 Class 类的 getName() 方法就可以印证这一点。

If this class object represents a class of arrays, then the internal form of the name consists of the name of the element type preceded by one or more '[' characters representing the depth of the array nesting.

大致的意思就是,如果是一个数组的话,Class 名的前面会有一个或者多个英文中括号“[”,表示数组的维度(一维数组为一个“[”,二维数组为两个“[”),然后再紧跟一个元素的类型首字母。


这就是为什么对象数组的前缀是“[L”的原因。是不是有一种恍然大悟的感觉?


02、自定义输出

如果想在打印的时候输出自己预期的结果,就必须在自定义类中重写 toString() 方法,来看例子。

public class Cmower {
    private String name;
    // 省略构造方法和 getter/setter

    @Override
    public String toString() {
        return name;
    }
}

当我们再次打印 Cmower 对象时,输出结果就不再是 com.Cmower@355da254 了。

沉默王二

但是这样的结果并不会令我们满意,它有些突兀,没法表示对象的类型。更优雅的做法是这样的:

public class Cmower {
    private String name;
    // 省略构造方法和 getter/setter

    @Override
    public String toString() {
        return getClass().getSimpleName() + "[name=" + name + "]";
    }
}

再次打印 Cmower 对象,输出结果为:

Cmower[name=沉默王二]

这样的形式不仅看起来美观,还能够在调试的时候给出有用的信息。但是,有时候我们不想重写 toString() 方法(想保留原有的打印格式 ClassType@123121),又想打印该对象的信息,那么最好定义一个新的方法,比如说 toMyString() 方法。

03、自动化输出

IDE(Eclipse 或者 Intellj IDEA) 通常会提供一种针对类的字段的输出格式,用来覆盖 toString() 方法。

@Override
public String toString() {
    return "Cmower{" +
            "name='" + name + '\'' +
            '}';
}

另外,一些开源的第三方类库也会提供这样的功能,比如说:

1)Apache Commons Lang 的 ToStringBuilder。

使用方法:

@Override
public String toString() {
    return ToStringBuilder.reflectionToString(this);
}

输出结果:

com.cmower.printObject.Cmower@355da254[name=沉默王二]

2)Google Guava 的 MoreObjects

使用方法:

@Override
public String toString() {
    return MoreObjects.toStringHelper(this)
            .add("name", getName())
            .toString();
}

输出结果:

Cmower{name=沉默王二}

3)Lombok 的 @toString 注解(IDE 需要先安装 Lombok 的插件)

使用方法:

@ToString
public class Cmower {

    private String name;

    // 省略构造方法和 getter/setter
}

只需要一个 @toString 注解,不需要覆盖 toString() 方法。

输出结果:

Cmower(name=沉默王二)

04、打印对象列表(数组或者集合)

上述内容已经把打印单个对象的事情唠明白了,are you ok?接下来,我们来说道说道打印对象列表的事儿。

1)数组

Arrays.toString() 可以将任意类型的数组转成字符串,包括基本类型数组和引用类型数组。代码示例如下。

Cmower[] cmowers = {new Cmower("沉默王二"), new Cmower("沉默王三")};
System.out.println(Arrays.toString(cmowers));

输出结果:

[Cmower{name='沉默王二'}, Cmower{name='沉默王三'}]

2)集合

对于集合来说,可以直接打印就能输出我们预期的结果。代码示例如下。

List<Cmower> list = new ArrayList<>();
list.add(new Cmower("沉默王二"));
list.add(new Cmower("沉默王三"));
System.out.println(list);

输出结果:

[Cmower{name='沉默王二'}, Cmower{name='沉默王三'}]

05、鸣谢

好了,我亲爱的读者朋友,以上就是本文的全部内容了。能在疫情期间坚持看技术文,二哥必须要伸出大拇指为你点个赞

如何优雅地打印一个Java对象?的更多相关文章

  1. 一个Java对象到底占用多大内存?

    最近在读<深入理解Java虚拟机>,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 在网上搜到了一篇博客讲的非常好 ...

  2. 一个Java对象到底占用多大内存

    在网上搜到了一篇博客讲的非常好,里面提供的这个类也非常实用: import java.lang.instrument.Instrumentation; import java.lang.reflect ...

  3. 一个Java对象到底占多大内存

    最近在读<深入理解Java虚拟机>,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 在网上搜到了一篇博客讲的非常好 ...

  4. 一个Java对象到底占多大内存?(转)

    最近在读<深入理解Java虚拟机>,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 在网上搜到了一篇博客讲的非常好 ...

  5. java.lang.instrument: 一个Java对象占用多少字节?

    一.对象头包括两部分信息:Mark Word(标记字段)和 Klass Pointer(类型指针)   1. Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode).GC分代年 ...

  6. 【转】一个Java对象到底占多大内存?

    最近在读<深入理解Java虚拟机>,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 在网上搜到了一篇博客讲的非常好 ...

  7. 一个 Java 对象到底有多大?

    阅读本文大概需要 2.8 分钟. 出处:http://u6.gg/swLPg 编写 Java 代码的时候,大多数情况下,我们很少关注一个 Java 对象究竟有多大(占据多少内存),更多的是关注业务与逻 ...

  8. 高端面试必备:一个Java对象占用多大内存

    这个问题一般会出现在稍微高端一点的 Java 面试环节.要求面试者不仅对 Java 基础知识熟悉,更重要的是要了解内存模型. Java 对象模型 HotSpot JVM 使用名为 oops (Ordi ...

  9. 如何复制一个java对象(浅克隆与深度克隆)

    在项目中,有时候有一些比较重要的对象经常被当作参数传来传去,和C语言的值传递不同,java语言的传递都是引用传递,在任何一个地方修改了这个对象的值,就会导致这个对象在内存中的值被彻底改变.但是很多时候 ...

随机推荐

  1. Linux中编写shell脚本的小例子

    1.创建一个test.sh的文件 touch test.sh 2.编辑这个文件 vi test.sh 3.进入到编辑页面后将编辑页面先切换成编辑模式(按Esc键就可以切换) 4.切换到编辑模式后开始向 ...

  2. ELK学习实验006:Nginx的日志分析系统之filebeat配置

    一 Filebeat工作原理 Filebeat由两个主要组件组成: prospector和 harvester 1.1 harvester 负责读取单个文件的内容 如果文件在读取时被制除或重命名, F ...

  3. 012 Ceph多区域网关

    一.基本概念 1.1 多区域概念 Multi-Size功能是从J版本开始的.一个single zone配置通常由一个zone group组成,该zone group包含一个zone和多个用于负载均衡的 ...

  4. 如何在Linux上创建,列出和删除Docker容器

    本篇文章介绍的内容是关于在Linux机器上创建,列出和删除docker容器,下面我们来看具体的内容. 1.启动Docker容器 使用下面的命令启动新的Docker容器.这将启动一个新的容器,并为你提供 ...

  5. Java高级特性——流

    以上就是这段时间学习完流的知识以后我的总结,.mmap文件可以去我的github上获取:https://github.com/xiaozhengyu/StudyNotes.git

  6. 「Luogu P3866」[TJOI2009]战争游戏 解题报告

    题面 好难表述啊~ 在n*m的矩阵上,有一些大兵(为0),一些空地(一个正整数),障碍物(-1),现在摧毁一些空地,使所有大兵不能走出矩阵去(代价为表示空地的整数),求最小代价 思路: 网络流最小割 ...

  7. vue-cli3 中 sockjs-node/info?t=报错 的解决方法

    页面突然出现这种报错: 查看Network看到是info接口报错,我项目中没有调用过该项目,然后百度查询sockjs-node/info?t=1562204191563, 首先 sockjs-node ...

  8. java反射-- Field 用法实践

    java 反射是一种常用的技术手段, 通过加载类的字节码的方式来获取相关类的一些信息 比如成员变量, 成员方法等. Field 是什么呢? field 是一个类, 位于java.lang.reflec ...

  9. 《工具》centos 7.x 下安装 JDK,超详细总结一看就会

    第一步 检查 JDK 查看JDK版本,在命令行输入:java -version [root@test ~]# java -version bash: java: command not found 如 ...

  10. shell学习-常用语句

    为什么使用shell 可以快速.简单的完成编程,实现自己的想法.Shell非常适合编写小的工具,因为小工具更强调的是易于配置.维护.移植等,而不是执行效率. 当自己的想法确实有必要进行优化,有必要让它 ...