1. 字符
字符序列:把多个字符按照一定得顺序排列起来.
字符串:把多个字符串串联起来(好比羊肉串).
2. 字符串的分类:
(1)不可变的字符串(String):
当前对象创建完毕之后,该对象的内容(字符序列)是不能改变的,一旦内容改变就是一个新的对象。
(2)可变的字符串(StringBuilder/StringBuffer):
当对象创建完毕之后,该对象的内容可以发生改变,当内容发生改变的时候,对象保持不变.
我们看下String类源码:其实String就是char数组的封装!
字符串的本质(底层是其实就是char[])
char表示一个字符,数组表示同一种类型的多个数据如何理解char[]:
String str = “ABCDEFG”; //定义一个字符串对象,等价于
char[] cs = new char[]{'A','B','C','D','E','F','G'};
3. String类分析(不可变字符串)
String类表示不可变字符串,当前对象创建完毕之后,该对象的内容(字符序列)是不能改变的,一旦内容改变就是一个新的对象。
String str = "hello";
str = "world"; // 此时的str已经不再是之前的str了,引用地址发生了变化
在内存中:
3.1 String对象的创建(两种方式)
(1)直接赋值一个字面量
String str = ”ABCD“;
(2)通过构造器创建
String str = new Sting("ABCD");
3.2 创建String对象两种方式的区别?(面试题1)
我们首先看一个概念:
常量池: 专门存储常量的一个地方,都指的方法区中。其实就是一个缓存区。
(1)编译常量池:把字节码加载在JVM的时候,存储的是字节码的相关信息(不研究)
(2)运行常量池:存储常量数据(研究)
String str1 = "hello";
String str2 = new String("hello");
以上两种创建String对象的方式有什么区别,我们来看一下它们的在内存中的分配。
String str1 = "hello";
// 最多创建一个String对象,最少不创建对象。
- 其实就是在创建str1对象前,先判断常量池中有hello,如果有就str直接引用,此时不创建对象。
- 如果没有就创建对象,再引用。
String str2 = new String("hello");
// 最多创建两个String对象,最少创建一个String对象。new关键字绝对会在堆空间中内存区域,所以至少创建一个String对象。
- 先判断常量池中有"hello"对象,如果没有就在常量池中创建,然后在堆空间中创建String对象。再引用常量池中的"hello";
- 如果常量池中存在"hello"对象,在堆空间中先创建String对象,再引用常量池中的"hello"。
因此:
String str1 = "hello";
String str2 = new String("hello");
System.out.println(str1 == str2); // false
System.out.println(str1.equals(str2)); // true
3.3 String对象的空值:
(1)引用为空(null):没有初始化,也就是说没有给对象分配内存空间;
String str = null;
(2)内容为空字符串,已经初始化,分配了内存空间,不过内容为空;
String = "";
3.4 判断字符串为空的方法
(1)引用不能为空(null);
(2)字符内容不能为空字符串("");
public static boolean isNull(String str) {
if (str != null && !str.equals("")) {
return false;
}
return true;
}
}
3.5 字符串的比较操作
基本数据类型的比较使用" == " ,而引用数据类型的使用 " == "比较的是内存地址;
(1)使用 " == “,只能比较内存地址;
(2)使用equals方法,在Object类中和” == "相同,建议子类覆盖该方法比较自己的内容。String类覆盖了equals方法,比较的是字符内容
下面是String类中equals方法:
(1)先使用" == "比较内存地址是否相同,相同说明,属于同一对象,那么内容肯定相等。
(2)先校验是否属于String类,再校验两个字符串长度是否一致。然后逐字符比较,有一个字符不同就返回false。
3.5 字符串的比较练习(面试题2)
说说下面String对象,彼此之间是否相等.
String str1 = "ABCD";
String str2 = "A" + "B" + "C" + "D";
String str3 = "AB" + "CD";
String str4 = new String("ABCD");
String temp = "AB";
String str5 = temp + "CD";
String str6 = getXx() + "CD";
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // true
System.out.println(str1 == str4); // false
System.out.println(str1 == str5); // false
System.out.println(str1 == str6); // false
private static String getXx() {
return "AB";
}
首先需要明确的是,使用equals方法比较str1-str6内容时,都是相等的,我们不考虑这个情况,只比较它们的内存地址。
下面我们逐个分析原因:
(1) 从打印的结果可知,str1==str2==str3
,从之前我们学习的知识可知,随着字符串的改变,每次都会创建不同的String对象,为什么会出现这种情况。我们看下编译前和编译后的代码就明白了。
编译前的代码:
编译后的代码:
编译之后的代码存在编译优化操作:
编译器发现str2其实就是四个常量连在一起,就是"ABCD",所以编译器将String str2 = "A" + "B" + "C" + "D";
优化成了String str2 = "ABCD";
str3也是如此。
(2)str1 == str4 // false
这个原因我们已经在3.2中进行了证明
(3)str1 == str5 // fasle
在代码编译时,编译器确定不了temp变量的值是多少,仅检查语法是否正确,运行时期才知道temp是什么,所以在编译时无法对str5进行优化,因此又创建了str5对象。
(4)str1 == str6 //false
在代码编译时,是不会调用getXx()方法,运行时才调用该方法,才知道getXx()返回的值是多少。
3.6 字符串比较总结
(1)单独使用""引号创建的字符串都是直接量,编译时期就已经存在于常量池中。
如:String str1 = "ABCD";
(2)使用new String("")创建的对象会存储到堆内存中,只有运行时才创建。
如:String str4 = new String("ABCD");
(3)使用只包含直接量的字符串链接符:“aa”+“bb”,创建的也是直接量,编译器会优化,存在与常量池中。
String str2 = "A" + "B" + "C" + "D";
String str3 = "AB" + "CD";
(4)使用包含String直接量(无final修饰符)的字符串表达式:“aa”+str1穿件的对象运行时期才创建,存储于堆中;
如:
String temp = "AB";
String str5 = temp + "CD";
【通过变量、调用方法去连接的字符串,都只能在运行时期才能确定变量的值和方法的返回值,不存在编译优化】
如:
String temp = "AB";
String str5 = temp + "CD";
或:
String str6 = getXx() + "CD";
private static String getXx() {
return "AB";
}