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日

相关推荐

  • Servlet4.0初识总结

    JavaEE8 JavaEE8,是自2013年6月Java企业版的首次更新。JAVAEE8提供了一些新的API,提供了对HTTP/2的新支持。 Servlet4.0 Servlet…

    Java 2019年11月28日
    02050
  • 日志门面介绍和JCL使用

    日志门面概述 之前有写过Java日志框架JUL和Log4j介绍,可以对比发现它们之间的使用还是有一些区别的,比如有一些类名是不同的、日志的输出级别也不完全一致。 那么实际上是增大了…

  • Java日志框架Logback介绍和使用

    前置知识: 日志门面SLF4J介绍和使用 Logback概述 Logback是由log4j、slf4j创始人Ceki Gülcü设计的另一个开源日志组件,性能比log4j要好。它自…

  • Java基础查缺补漏02

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

    Java 2020年5月25日
    080
  • HashSet源码分析

    Set家族一览: HashSet简介 Set是Collection三大接口其中之一,意为集合,且元素不能重复。Set接口中的方法和Collection中的方法完全一致,只是起到一个…

    2019年12月2日
    0200
  • Java之UDP编程

    DatagramSocket概述 上次在Java网络套接字Socket编程那篇博客里只写了Socket和ServerSocket,即TCP通信,这次来补充一下UDP通信。 和Soc…

    Java 2020年3月11日
    0810
  • ConcurrentHashMap源码分析

    ConcurrentHashMap的源码比较难读,特别是JDK8中几乎对其进行了重写,设计思想非常优秀,代码量也是非常多,不过我这里算是总结了一些相对重要的部分,仅供参考。 线程安…

    2020年2月13日
    0160
  • 为什么说Java只有值传递?

    先说一下。。以后可能不会怎么写Java相关的博客了,因为找到了字节跳动的实习工作,用Go/Python开发后端,所以这几天在抓紧时间学Go,在学Go的时候,了解到Go语言只有值传递…

    Java 2020年6月26日
    03100
  • 详解Java中的四种引用及其应用

    本文参考资源: 深入理解Java中的引用(一)——Reference - 简书 深入理解Java中的引用(二)——强软弱虚引用 - 简书 深入理解Java中的引用(三)——Dire…

    2020年2月14日
    0190
  • Java基础查缺补漏03(附赠哈夫曼树&哈夫曼编码)

    继续我的复习刷题 构造器显式调用父类构造方法的规则 题目: 以下程序的输出结果为 class Base{ public Base(String s){ System.out.pri…

    2020年5月27日
    0220

发表回复

登录后才能评论