String s = new String("123");创建了几个String对象?

提要

最近复习看到一道题目很有意思啊:

String s = new String("123");

问这样一行代码创建了几个String对象?

乍一看我其实以为和JDK的版本有关系,因为在深入理解java虚拟机第三版读书笔记021.6.2 intern方法有意思的点曾经提到,JDK7之前,字符串常量池在方法区(永久代)中,而JDK7开始,就将字符串常量池移到了堆中。那么这个题目和这个知识点有关系吗?

然而仔细一看这行代码,便发现了蹊跷:我们平时定义字符串会使用这样的语法赋值吗?平时常用的给字符串赋值的方法应该是下面这样才对:

String s = "123";

那么这样两行代码有什么区别呢?先从搞清楚"123"是什么开始:

字符串字面量

字符串的字面量和基本类型的字面量有点不同,基本类型可以写1,true,2l这样的字面量。然而在Java中字符串不属于基本类型,String本身是一个类,那么 "123"这样一个字面量肯定也是一个String的实例对象

但是我们之前又提到了“字符串常量池”这个东西,到底什么时候字符串会从常量池中取值呢?下面通过几个例子验证一下:

public static void main(String[] args) {
    String a = "111";
    System.out.println("111" == a);
}

首先,"111"是一个String的实例对象,将它赋值给a,我们通常说,在Java中只有值传递,没有引用传递,就是指这里其实是把"111"的引用地址通过值传递给了a这个引用类型,使得它们指向了同一个对象。

而后面的输出语句中,再一次出现了"111"这个字面量,通过它和a做==比较,看它们指向的是不是同一个对象,结果输出true,说明重复出现的字面量其实指向的是同一个对象,这里可以猜测第一次出现的字符串字面量就创建了一个String实例,并放入了常量池中,后面出现的相同的字面量都是从常量池中取值

那么既然第一次出现的字面量就会将创建的String实例放入字符串常量池,intern这个方法有什么存在的必要呢?其实这是因为字符串不仅仅会以字面量的形式赋值

比如下面这个方法:

public static void main(String[] args) {
    String a = new StringBuilder().append("11").append("1").toString();
    String b = "111";
    System.out.println(a == b);
}

a是通过StringBuilder来构造的,因此没有出现"111"这个字符串字面量,相反而是出现了"11","1"这两个字面量,因此在与b进行比较的时候,输出的是false。

但假设我们仍想在第一行代码中就让"111"进入字符串常量池,就可以使用intern方法:

public static void main(String[] args) {
    String a = new StringBuilder().append("11").append("1").toString();
    a.intern();
    String b = "111";
    System.out.println(a == b);
}

这样就会输出true

通过这里的研究可以知道,下面这样的代码就是多此一举:

String a = "111";
a.intern();

因为第一行其实已经实现了intern的效果。

如何解答这个问题

了解完了字符串字面量这个概念,相信回答这个问题也不难了,

String s = new String("123");

这行代码中,"123"本身是一个String的实例对象,它的来源可能有两种情况:
1. 若之前字符串常量池中没有"123",这里就会创建一个字符串,并放入常量池
2. 若之前字符串常量池中有"123",这里就会直接指向常量池中的对象。

s是怎么来的?看一下这个构造方法(JDK8):

public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

可以看到这个构造方法实际上是一个浅复制,只是会将字符串字面量中的value字符数组复制过来。

所以这个问题应该这样回答:

如果在这段代码运行之前字符串常量池中已经有"123"了,就只会创建一个String对象,相反则会创建两次,因为字符串字面量第一次出现会创建一个String实例对象。

与之对应的是,

String s = "123";

这样一行代码倒是有可能一个字符串对象也不创建,因为只是将常量池中对象的引用地址传递了一下。

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/string-s-new-string123%e5%88%9b%e5%bb%ba%e4%ba%86%e5%87%a0%e4%b8%aastring%e5%af%b9%e8%b1%a1%ef%bc%9f/

(0)
彭晨涛彭晨涛管理者
上一篇 2020年5月24日
下一篇 2020年5月25日

相关推荐

  • 日志门面SLF4J介绍和使用

    SLF4j概述 上篇文章写了日志门面介绍和JCL使用,作为一个曾经的主流日志门面技术,JCL终究因为设计时的产生问题所限,导致至今已没什么人使用,而一款优秀的日志门面技术SLF4j…

    2020年3月9日
    01340
  • 深入理解java虚拟机第三版读书笔记12

    以下是第十二章 Java内存模型与线程的内容 硬件的效率与一致性 基于高速缓存的存储交互很好地解决了处理器与内存速度之间的矛盾,但是也为计算机系统带来更高的复杂度,它引入了一个新的…

    2020年1月29日
    01540
  • 深入理解java虚拟机第三版读书笔记11

    以下是第十一章 后端编译与优化的内容 把Class文件转换成与本地基础设施(硬件指令集、操作系统)相关的二进制机器码可以视为整个编译过程的后端。 最近几年提前编译也开始兴起,我们在…

    2020年1月27日
    0300
  • 网络编程之AIO

    AIO介绍 AIO是JDK1.7引入的,也称为NIO 2.0 NIO的同步非阻塞类似于轮询,让Selector去询问各个通道的状态。 而AIO则是通过Future实现真正的异步,让…

    Java 2020年2月8日
    0290
  • NIO底层原理-epoll

    BIO模型存在三个socket: ServerSocket:专门用来监听是否有来自客户端的连接accept返回的Socket:专门用于处理客户端请求的socketSocket:客户…

    Java 2020年2月13日
    0110
  • CAS应用之JUC下的原子类

    原子整数 AtomicInteger i = new AtomicInteger(0); // 获取并自增(i = 0, 结果 i = 1, 返回 0),类似于 i++ Syste…

    Java 2020年2月2日
    0530
  • ArrayList源码分析

    总结 总结放前面防止太长不看: ArrayList内部是用数组实现的。 如果使用无参构造函数建立ArrayList,在添加第一个元素的时候会分配10个元素的空间。 ArrayLis…

    2019年11月22日
    0160
  • Java中SPI机制介绍和源码分析

    本文参考资源: 高级开发必须理解的Java中SPI机制 - 简书 什么是SPI SPI全称为Service Provider Interface,是一种服务发现机制。SPI 的本质…

    Java 2020年3月19日
    0710
  • Java基础查缺补漏02

    哈哈我其实没有想到这个系列真会有续集,上次写完01以后以为不会再写下去了,没想到最近牛客网刷题有些题目还是挺纠结的,这里补一补 构造器能带哪些修饰符 题目: Which of th…

    Java 2020年5月25日
    080
  • Java自动装箱缓存机制

    尝试运行这段代码: 相似的两段代码,得到的结果却完全不相同。 首先要知道在java中==比较的是对象的引用,从直觉出发,无论是integer1、integer2还是integer3…

    Java 2019年12月5日
    0180

发表回复

登录后才能评论