String类
创建字符串的方式
方式1: 直接赋值
String str="abed";//直接赋值
方式2: 构造方法
String str2=new String("abce");
这样会产生两个对象 不建议使用
方式3:
char[] array={'a','b','c','d'};
String str3=new String(array);
请解释String类中两种对象实例化的区别
- 直接赋值:只会开辟一块堆内存空间,并且该字符串对象可以自动保存在对象池中以供下次使用。
- 构造方法:会开辟两块堆内存空间,其中一块成为垃圾空间,不会自动保存在对象池中,可以使用intern()方法手工入池。
因此,一般采用直接赋值的方法创建字符串
字符串比较相等
-
== 表示地址比较 不是内容比较
str==str2;//比较str和str2地址是否相同
-
equals内容比较用
str.equals(str2);//比较str和str2的内容是否相同
- 双引号引起来的内容都放在常量池
比如str="abc";
往常量池中放字符串str
时,首先查看常量池中是否有abc
,如果已经存在该字符串,那就不入池,直接将当前abc
的地址给str
- 举个例子看各种情况下字符串的内存布局
public static void main(String[] args) {
String str = "abcdef";//直接赋值
String str2 = new String("abcdef");
char[] array = {'a','b','c','d','e','f'};
String str3 = new String(array);
String str4 = "abc"+"def";//编译期已经确定-》"abcdef"
String str5 = "abc"+new String("def");
System.out.println(str == str2);//false
System.out.println(str == str3);//false
System.out.println(str == str4);//true
System.out.println(str == str5);//false
String str6 = "abc";
String str7 = "def";
String str8 = str6+str7;
System.out.println(str == str8);//false
图解剖析内部存储布局:
文字详解各种方式的内部存储
-
String str = "abcdef";
创建了一个对象
“abcdef” 对应一个对象,这个对象放在字符串常量池,不管出现多少遍,常量池中都只有一个"abcdef" -
String str2 = new String("abcdef");
创建一个或两个对象
new String每写一遍,就创建一个新的对象,它使用常量"abcdef"对象的内容来创建出一个新的String对象。注意:此时str2指向的是堆中的String对象,所以str == str2
结果为false
- 这时如果常量池中已经存在"abcdef",那么就不会再在常量池中创建"abcdef"了,直接从常量池拿,这种情况只创建了一个对象(堆中的String对象);
- 如果常量池中不存在"abcdef",那么此时就会在常量池中创建一个"abcdef",这种情况它创建两个对象(堆中的String对象和常量池中的"abcdef")。
-
char[] array = {'a','b','c','d','e','f'};
此时先在堆中创建一个字符数组,String str3 = new String(array);
再创建一个String对象指向数组array所在的地址
这种情况创建了两个对象
str3指向的是指向数组的这个String的地址所以str == str3
结果为false
-
String str4 = "abc"+"def";
****
在编译期时"abc"和"def"已经自动拼接成为"abcdef"了,常量池中已有"abcdef"所以运行时不用入池 ,str4直接指向的是池中"abcdef"的地址,所以str==str4
结果为true
-
String str5 = "abc"+new String("def");
创建了3个对象(因为"abc"已经在常量池中存在,若不存在则是创建了四个对象)
如果"abc"不在常量池中的话:
遇""
括起来的内容只要""
中的内容在常量池中不存在,则要创建一个对象,遇new
又要创建一个对象new String("def");
在常量池中创建"def",在堆中创建一个String对象,共创建两个对象"abc"
创建一个对象"abc"+new String("def")
又创建一个对象存储"abc"+new String("def")
的结果
所以一共创建四个对象(如果"abc"在常量池中不存在的话)
所以str == str5
的执行结果也是false
-
String str8 = str6+str7;
创建了一个对象,存放str6+str7
运行之后的结果
所有的变量只有在运行的时候才知道结果 所以str==str8
结果为false
注意:String str="ab"+"cd"+30;
其中产生了两个对象,“ab”+“cd"在编译期间已经确定是"abcd"了,所以"ab”+“cd"创建了一个对象,然后再创建一个对象存放"abcd”+30的结果,将结果的地址给str。所以创建了两个对象
intern()
.intern()手动将String对象加入到字符串常量池中
- 常量池当中已经有该字符串 直接返回常量池当中字符串对象的地址
- 常量池当中不存在 在常量池当中生成该对象的引用
-
String str2=new String("abcdef").intern();
这样的话str==str2结果为true -
String str3=new String(array).intern();
str==str3结果也为true -
比较字符串大小
理解字符串的不可变
String类内部部分源码如下:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
private int hash; // Default to 0
...
public String() {
this.value = "".value;
}
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
...
有源码可得String类内部实现也是基于 char[] 来实现的, 但是 String 类中char value[]被final修饰,所以无法修改。
String str = "hello" ;
str = "Horld" ;
System.out.println(str);
//执行结果:Hello
由上面的内部存储图上看,虽说结果确实改变了,但并不是 String 对象本身发生改变, 而是 str 引用到了其他的对象
如果一定要修改Sring,只有两种方法:
- 借助原来的字符串创建新字符串
str="Hello";
str="h"+str.substring(1);
str.substring(1);
从1号下标开始提取一直到字符串结束
这样产生了一个新对象"hello" 并没有改变常量池中的Hello
- 但是可以通过反射 (可以在类外拿到私有的方法 对象)来改变String(了解即可)
public static void main(String[] args) throws NoSuchFieldException,
IllegalAccessException {
String str = "hello";
// 获取 String 类中的 value 字段 这个 value 和 String 源码中的 value 是匹配的
Class clc = String.class;
Field field = clc.getDeclaredField("value");
// 将这个字段的访问属性设为 true
field.setAccessible(true);
// 把 str 中的 value 属性获取到.
char[] value = (char[]) field.get(str);
System.out.println(Arrays.toString(value));//执行结果[h, e, l, l, o]
// 修改 value 的值
value[0] = 'H';
System.out.println(Arrays.toString(value));//执行结果[H, e, l, l, o]
}
为什么 String 要不可变?(不可变对象的好处是什么?)
- 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑何时深拷贝字符串的问题了.
- 不可变对象是线程安全的.
- 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.