创建字符串
常见的构造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") ;
这样的做法有两个缺点:
- 如果使用String构造方法就会开辟两块堆内存空间,并且其中一块堆内存将成为垃圾空间(字符串常量 “hello” 也是一个匿名对象, 用了一次之后就不再使用了, 就成为垃圾空间, 会被 JVM 自动回收掉).
- 字符串共享问题. 同一个字符串可能会被存储多次, 比较浪费空间.
解决办法:使用 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不可变对象的优点
- 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑何时深拷贝字符串的问题了.
- 不可变对象是线程安全的.
- 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.