如何创建字符串、字符串在内存上的存储、字符串之间比较、字符串的不可变性

本文探讨Java中字符串的创建,包括直接赋值和使用构造方法,强调字符串在内存中的引用布局及字符串常量池的概念。同时,文章分析了字符串比较的正确方式,解释了为何应使用equals而非==。此外,还详细阐述了字符串的不可变性,及其带来的优点,如线程安全和优化缓存。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

创建字符串

常见的构造String的方式

方式1

String str = "Hello World!";

方式2

String str2 = new String("Hello World");

方式3

char[] arr = {'a','b','c'};
String str3 = new String(arr);

在官方文档上 (https://docs.oracle.com/javase/8/docs/api/index.html) 我们可以看到 String 还支持很多其他的构造方式,

  • 注意事项:
    1、“hello” 这样的字符串字面值常量, 类型也是 String.
    2、String 也是引用类型.

String 类型的引用内存布局

1、String str = “Hello”;
在这里插入图片描述
2、String str1 = “Hello”;String str2 = str1;
在这里插入图片描述
当改变str1时,str2不会发生改变

str1 = "world";
System.out.println(str2);
// 执行结果
Hello

原因:str1 = “world” 这样的代码并不算 “修改” 字符串, 而是让 str1 这个引用指向了一个新的 String 对象
在这里插入图片描述

字符串比较相等

  • 像int类型的变量,可以使用==来判断两个变量的值是否相等
  • 但是String类型的两个引用在使用==来比较时,比较的是两个引用是否指向同一个对象,而不能判断字符串的内容是否相等。
  • Java 中要想比较字符串的内容, 必须采用String类提供的equals方法.
String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.equals(str2));
System.out.println(str2.equals(str1)); 

// 执行结果
true

equals使用注意事项

比较一个String的引用str的值是否和一个字面值常量“Hello”相等

String str = new String("Hello");
// 方式一
System.out.println(str.equals("Hello"));
// 方式二
System.out.println("Hello".equals(str));

推荐使用 “方式二”. 一旦 str 是 null, 方式一的代码会抛出异常, 而方式二不会.

String str = null;
// 方式一
System.out.println(str.equals("Hello"));  // 执行结果 抛出 java.lang.NullPointerException 异
常
// 方式二
System.out.println("Hello".equals(str));  // 执行结果 false
  • “Hello” 这样的字面值常量, 本质上也是一个 String 对象, 完全可以使用 equals 等 String 对象的方法.

字符串常量池

String类的两种实例化操作, 直接赋值和 new 一个新的 String.

直接赋值

String str1 = "hello" ;
String str2 = "hello" ;
String str3 = "hello" ;
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // true
System.out.println(str2 == str3); // true

在内存上表现为
在这里插入图片描述
为什么现在并没有开辟新的堆内存空间呢?
String类的设计使用了共享设计模式

在JVM底层实际上会自动维护一个对象池(字符串常量池)

  • 如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池之中.
  • 如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用
  • 如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用
    “池” 是编程中的一种常见的, 重要的提升效率的方式,

采用构造方法

类对象使用构造方法实例化是标准做法。

String str = new String("hello") ;

在这里插入图片描述
这样的做法有两个缺点:

  1. 如果使用String构造方法就会开辟两块堆内存空间,并且其中一块堆内存将成为垃圾空间(字符串常量 “hello” 也是一个匿名对象, 用了一次之后就不再使用了, 就成为垃圾空间, 会被 JVM 自动回收掉).
  2. 字符串共享问题. 同一个字符串可能会被存储多次, 比较浪费空间.
    解决办法:使用 String 的 intern 方法来手动把 String 对象加入到字符串常量池中
String str1 = new String("hello").intern() ;
String str2 = "hello" ;
System.out.println(str1 == str2);
// 执行结果
true

在这里插入图片描述

字符串不可变

字符串是一种不可变对象. 它的内容不可改变.

String str = "hello" ;		//创建了一个引用
str = str + " world" ;//先创建一个“world”的对象放在堆上,在创建一个“hello world”放在堆上,令str引用“hello world”这个对象
str += "!!!" ;
System.out.println(str);
// 执行结果
hello world!!!

在这里插入图片描述

+= 这样的操作,不是 String 对象本身发生改变, 而是 str 引用到了其他的对象

要修改字符串该怎么办?

字符串 str = “Hello” , 改成 str = “hello”
1、借助原字符串, 创建新的字符串

String str = "Hello";
str = "h" + str.substring(1);
System.out.println(str);
// 执行结果
hello

2、使用 “反射” 破坏封装, 访问一个类内部的 private 成员

String str = "Hello";
// 获取 String 类中的 value 字段. 这个 value 和 String 源码中的 value 是匹配的.
Field valueField = String.class.getDeclaredField("value");
// 将这个字段的访问属性设为 true
valueField.setAccessible(true);
// 把 str 中的 value 属性获取到.
char[] value = (char[]) valueField.get(str);
// 修改 value 的值
value[0] = 'h';
System.out.println(str);
// 执行结果
hello

反射是面向对象编程的一种重要特性, 有些编程语言也称为 “自省”.
指的是程序运行过程中, 获取/修改某个对象的详细信息(类型信息, 属性信息等), 相当于让一个对象更好的 “认清自己” .

String不可变对象的优点

  1. 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑何时深拷贝字符串的问题了.
  2. 不可变对象是线程安全的.
  3. 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值