String的两种实例化方式

关于String的两种实例化方式,我们要先理解类、对象、实例的含义和它们之间的区别与联系。类是抽象化的(如狗),对象是具体化的(如京巴狗),我们常说一个对象(京巴狗)是某个类(狗)的一个实例(instance),所以对象与实例的含义等价。

我们知道,字符串操作是计算机程序设计中最常见的行为。学习String的实例化是非常有必要的。以下是String的源码:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
}

我们可以看出String是final类型,不能被继承。它继承了3个接口,Serializable(可序列化的)、Comparable(可比较的)、CharSequence(字符序列)。

  • 继承Serializable接口,可以被JVM序列化成文件。
  • 继承Comparable接口,主要实现它的compareTo(T o)方法,此方法用来比较两个字符串的长度,返回一个int值。
  • 继承CharSequence接口,实现的方法常用的有length()、toString()等。

顺便提醒,Java是单继承、多实现语言。

注意:

  1. 本文多次出现的字符串是指字符串常量,为表达意思更加贴切,遂有的地方使用字符串代替字符串常量。
  2. 本文多次出现的引用指对象引用(实例引用),引用和基本数据类型均为变量,在JVM中存于Java虚拟机栈中,更确信的说是局部变量表中,如果你想要深入了解请看《深入理解Java虚拟机(第二版)》或者这篇博文
  3. 本文多次使用大意为“返回引用”的语句,可以理解为返回引用所指向的对象。

字符串常量池

想要了解String的实例化,要先了解字符串常量池。字符串常量池存放于Java文件编译生成的Class文件中,Class文件存放于方法区(《深入理解Java虚拟机(第二版)》第65页)。

JVM中设计字符串常量池是为了减少实例化重复的字符串,以节约新建时间和内存空间。如果字符串已经在字符串常量池存在,就不再新建,而是将新的引用指向它,可以理解为多个引用共享一个字符串。因为String为final类型,它不可改变的特点,保证其在字符串常量池的唯一性(已经存在的不新建,新建的肯定不同),这也是字符串常量池能设计出来的原因。

隐式实例化:直接赋值

public class Demo {
    public static void main(String[] args) {
        String s = "hello";
        String s2 = "hello";
        System.out.println(s == s2);
    }
}

以上直接赋值,是我们常用的实例化方式。我们将采用直接赋值方式生成的字符串也称为匿名对象,当然,匿名对象也是对象。符号"=="判断两个引用是否指向同一个对象(字符串),通过输出结果为true可知,对于引用s2来说,没有重新新建一个字符串,而是将引用s2指向字符串"hello",

从JVM角度来看,JVM在编译Java文件生成Class文件时,将字符串"hello"添加到字符串常量池中,在虚拟机栈(局部变量表)中给s、s2分配一块内存,均指向字符串"hello"。如下图,我们可以将引用理解为存放它“指向”的对象(字符串)的内存地址,假设字符串"hello"的地址为0x1001。

image

那如果隐式实例化如下,引用与字符串是什么关系呢?

String s = "hello";
s = "world";

因为String是final类型,即一个字符串(String对象)创建后就不能被修改。所以"world"是一个新的字符串。引用s指向字符串"world",字符串常量池中的字符串"hello"由于没有了引用,会被回收。(本文是基于jdk1.8研究,字符串常量池已经移出永久代。字符串可以被回收)如下图所示:

image

显式实例化:使用构造函数

public class Demo {
    public static void main(String[] args) {
        String s = "hello";
        String s2 = new String("hello");
        String s3 = new String("hello");
        System.out.println(s==s2);
        System.out.println(s==s3);
        System.out.println(s2==s3);
    }
}

从输出三个false可以看出,三个对象均不相同,我们知道使用new关键字创建的实例存放于Java堆中。假设堆中存放的两个String对象的内存地址分别为0x1002和0x1003。如前面所讲,引用s所“指向”的字符串存放于字符串常量池。从JVM分析,有如下图示:

image

显式实例化常用于将字符数组转换为字符串,使用方式如下:

String string = "hello";
char[] array = string.toCharArray();
return new String(array);

intern()方法

你是不是觉得,两种实例化方式产生的“相等(equals())”对象总感觉能联系起来,你的直觉是对的。它们之间的联系纽带在于intern()方法。intern()方法是String类的方法,一个Native方法。一个存放在堆中的String对象调用此方法,JVM会在字符串常量池中去比较是否有“等于(==)”此String对象的字符串,如果有,返回池中代表这个字符串的String对象,没有则将此String对象包含的字符串放入字符串常量池中,再返回此String对象的引用(书中78页)。如果执行以下代码,会输出false/true/true。

public class Demo {
    public static void main(String[] args) {
        String s = "hello";
        String s2 = new String("hello");
        String s3 = new String("hello");
        System.out.println(s2 == s2.intern());
        System.out.println(s == s3.intern());
        System.out.println(s2.intern() == s3.intern());
    }
}

来分析一下结果:

第一个输出false,我们将引用s、s2、s3所指向String对象分别称为对象1(字符串"hello")、对象2和对象3,根据我上面所写的定义,s2.intern()将返回字符串常量池中的对象1,池中的对象1和堆中的对象2不是同一个对象,我们知道,符号"=="判断两个引用是否指向同一个对象,所以返回false。

如果此时池中没有对象1,即对象2包含的字符串"hello"是首次出现的。代码如下:

String s2 = new String("hello");
System.out.println(s2 == s2.intern());

s2.intern()方法将会把对象2的引用(我理解为字符串“hello”)放入字符串常量池中,并返回此引用,所以结果为true。

第二个和第三个输出true。此时对象1(字符串“hello”)存在字符串常量池中,s2.intern()和s3.intern()均返回对象1,所以两个true。

总结

根据《深入理解Java虚拟机(第二版)》书本上的理解,字符串常量池中的存放直接赋值的字符串(匿名对象)。待我自己安装后一套JDK,实际操作后,再来看看理解是否有误。只有代码不会撒谎!任何观点或定义必须用代码才能解释清楚。

参考书籍

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容