instring java,我所理解的String in Java8

0. 前言

本文旨在介绍 String 类的相关特性以及源码分析,同时还列出了常见的面试题以及示例回答。

相信学习过 Java 的同学都见过这样一段代码:

public static void main(String[] args){

System.out.println("Hello, World!");

}

复制代码

这几乎是每种语言必写的一个示例代码,而在这段 Java 代码中的 Hello, World! 就属于接下来要聊的 String 类。

本文 JDK 源代码版本为 1.8.0_221

1. String 的不可变性

正如 String 类源码文档中所说的那样:String 是常量,它的值在创建后就不能改变。而 String 类的不可变性是由于它被 final 关键字所修饰。

public final class String

implements java.io.Serializable, Comparable, CharSequence{

}

复制代码

1.1 final 关键字

顺便复习一下 Java 基础—— final 关键字的作用:

final 关键字可以被修饰于类、方法和变量

final 关键字强调的是一种不可变性,对于上述三种情况有不同的具体含义:

当 final 被修饰于类时,表明该类不能被继承

当 final 被修饰于方法时,表明该方法不能被重写

当 final 被修饰于变量时,又分两种情况:

若变量属于基本数据类型,那么它的值在初始化之后就不能再更改

若变量是引用类型,那么在初始化之后它就不能再指向另外的对象

所以在 String 类的源码上使用 final 关键字修饰,保证了 String 类不能被扩展。

1.2 String 底层结构

在 String 类中有一个 char 类型的数组,它是用来存储字符串的。

/** The value is used for character storage. */

private final char value[];

复制代码

这个 char 类型的变量也是被 final 关键字修饰,而数组变量属于引用类型,它的值其实就是数组的地址,也就是说 value 数组在初始化之后就不能再指向另外的对象,这也保证了 String 类的不可变性。

但是这里有一点需要额外提到的就是,虽然 value 数组在初始化之后就不能再指向另外的对象,但是它原本指向的地址的内容是可以改变的。

下面是两个直接修改被 final 修饰的变量的示例。

final char[] str = {'1','2','3'};

// 直接赋值将 str 数组的内容修改为{'1','2','4'}

str[2] = '4';

// 通过反射将 str 数组的内容修改为{'1','2','5'}

java.lang.reflect.Array.set(str, 2, '5');

复制代码

第一个方式是直接对 str 数组变量中元素进行赋值,第二个方式是通过反射修改数组内容。所以,要记得的是:被 final 修饰的引用类型变量只是引用地址不能改变。

而为了保证 String 对象的内容,也就是 value 数组的元素不被修改,源码中也没有提供任何修改 value 数组的方法,这也是保证 String 类的不可变性的一种方式。

1.3 使用 final 修饰的原因

首先第一原因是高效,就拿常量池来说,只有变量是不可修改的,才能够被缓存起来,从而实现常量池的功能。

第二个原因是安全, Java 之父 James Gosling 解释过,迫使 String 类设计成不可变的另一个原因是安全,当你在调用其他方法时,比如调用一些系统级操作指令之前,可能会有一系列校验,如果是可变类的话,可能在你校验过后,它的内部的值又被改变了,这样有可能会引起严重的系统崩溃问题。

2. String 的创建流程

通常 String 类有两种创建方式,直接赋值和new。

String a = "abc";

String b = new String("abc");

复制代码

2.1 直接赋值

首先来说直接赋值,首先会去常量池中寻找 abc 字符串是否存在,若已存在会将 a 变量直接指向常量池中的值。如果不存在,会在常量池中先创建一个 abc 字符串,然后把 str 指向刚刚创建出来的 abc 字符串。

2.2 new String()

对于使用 new 关键词来创建一个 String 对象的情况,首先虚拟机会在 Java 堆中创建一个 String 对象,然后再去常量池中寻找 abc 字符串是否存在,如果存在,将创建的对象的值指向常量池中已存在的字符串;如果不存在会在常量池中创建一个 abc 字符串,然后把 Java 堆中的对象引用的值指向在常量池中创建的 abc 字符串。

2.3 代码示例

我们写个例子验证一下上面的结论。

public static void main(String[] args){

String a = "abc";

String b = new String("abc");

System.out.println(a==b);

}

复制代码

下面这张图描述了示例代码中对象之间的关系。

26f8f8bd0b5343c19d4674f88ce5193c.png

第1行代码中使用直接赋值的方式,由于常量池中 abc 字符串已经存在(虚拟机在类加载期间在常量池中创建该字符串),所以 a 变量会直接指向常量池中的 abc 字符串。

第2行代码中使用 new String("abc") 创建了一个对象,所以会在堆中创建一个 value[] 数组对象,而此时常量池已存在 abc 字符串,所以 b 对象的 value[] 数组的引用值会指向常量池中的 abc 字符串。

最后,由于 a 对象的地址相当于常量池中 abc 字符串的地址,而 b 对象的地址相当于 value[] 对象的地址,这两者地址不相同,所以 a 与 b 两个对象不相等。

3. String#intern

除了上述的两种创建方式之外,还有一种方式可以创建 String 对象,那就是 String#intern 方法。

intern 是一个本地方法,它返回的是一个字符串对象,如果常量池已经包含了字符串,那么直接返回该字符串;否则,会将该字符串添加到常量池,并返回该字符串。

在上述示例代码的基础上,再加上一个变量。

public static void main(String[] args){

String a = "abc";

String b = new String("abc");

String c = b.intern();

System.out.println(a==b);

System.out.println(a==c);

System.out.println(b==c);

}

复制代码

a 和 b 的关系我们已经知道了,那么对于使用 intern 方法返回的 c 字符串,会是怎样的情况呢?执行这个方法,我们会发现输出结果是:

false // a!=b

true // a==c

false // b!=c

复制代码

对照 intern 方法的作用,其实也能够知道这个结果:当调用 intern 方法时,如果常量池中存在 abc 字符串,那么直接返回。

在示例代码的场景中,此时 abc 字符串肯定已经存在于常量池中,而且这个字符串是在类加载期间创建的,同时又被赋值给 a 变量,所以 a==c 的判断结果是 true。

4. String、StringBuilder 和 StringBuffer 的区别

这个问题真的被问吐了,网上资料也非常多了,这里就贴几个链接好了。

参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中的String类有一个equals(Object obj)方法和一个equalsIgnoreCase(String anotherString)方法用于比较字符串对象,但这两种方法比较的是两个字符串对象的内容是否相同,而不是它们在内存中的地址是否相同。 在这种情况下,可以使用Java中的String.intern()方法和String.valueOf(Object obj)方法。intern()方法将当前字符串的值存储在字符串池中,如果该字符串在字符串池中不存在,则将其添加到池中。在其他地方创建的具有相同值的字符串对象实际上将是指向同一字符串池中的对象。 相反,valueOf(Object obj)方法返回一个包含指定对象的字符串表示形式的字符串对象。如果指定对象为null,则返回字符串“ null”。如果指定对象是原始类型,则会调用适当的包装类的toString()方法。如果指定对象是一个字符数组,则通过使用该数组中的字符构造一个新的字符串。如果指定对象是其他类型的数组,则调用Arrays.toString(Object \[\])方法返回一个包含每个元素的字符串表示形式的字符串对象。 在一些特殊情况下,例如在字符串匹配中,使用String.equals()方法可能会更有效,因为它是使用相同的语言结构实现的。在这种情况下,可以使用常量字符串常量池(而不是对象池)来存储相同的字符串,以便在比较字符串时可以使用相同的引用,从而提高了效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值