一、String类概述
1、概述
java.lang.String 类代表字符串。使用一对 "" 引起来表示,Java 程序中所有的字符串文字(例如“abc”)都可以看作是实现此类的实例。
String 是引用数据类型,不是基本数据类型。
类 String 中包括用于检查各个字符串的方法,比如用于比较字符串,搜索字符串,提取子字符串以及创建具有翻译为大写或小写的所有字符的字符串的副本。
2、特点【重要】
a、字符串不变:字符串的值在创建后不能被更改。【非常重要】
Demo:
String s1 = "abc";
s1 += "def";
System.out.println(s1); // s1 = "abcdef"
分析:内存中有"abc","abcdef"两个对象,s1从指向 "abc",改变指向,指向了"abcdef" ,字符串本身并没有改变,而是改变了指向。
思考:String 对象怎么就不可变?
底层char[]数组有final修饰,意味着这个数组不能扩容等,来达到存更多的字符
char[]数组是私有的,程序员无法直接操作这个char[]数组,而且String没有提供这样的方法,来修改char[]数组的元素的值。
String提供的所有的方法,对字符串的修改都是给你返回一个新的字符串对象。
b、因为String对象是不可变的,可以把一些字符串存到常量池中,字符串在常量池中,可以被共享。
Demo:
String str1 = "abc";
String str2 = "abc";
分析:内存中只有一个 "abc" 对象被创建,同时被 s1 和 s2 共享。
注意:
① 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中;
① 字符串常量池中是不会存储相同内容的字符串的。
c、字符串对象底层的存储:
JDK1.9之前:底层是用 char[ ] 存储
JDK1.9之后:底层选用 byte[ ] 存储
Demo:
String str = "abc";
相当于
char datas[] = {'a', 'b', 'c'};
String str = new String(datas);
d、String 类型不能被继承,因为 String 是由 final 修饰的。
3、String 代表不可变的字符序列。简称:不可变性
体现:
1.当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
2. 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
3. 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
二、String 类结构与使用
1、包结构
String 这个类 是 java.lang.String 包内的,不需要手动导入。
扩展:常用的如 基本数据类型,String,还有一些工具类,只要是 lang 包下面的,就不必写 import 导包语句。
2、类结构
类声明:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{}
(1)实现了Serializable接口:表示字符串是支持序列化的;
(2)实现了Comparable接口:表示String可以比较大小;
(3)声明为final的,不可被继承;
3、字符串实例化方式
创建字符串的常见 3 + 1 中方式。(常见的有三种构造方法,一种直接创建)
(1)构造方法
public String() : 初始化新创建的 String对象,以使其表示空字符序列
public String(char[] value) :通过当前参数中的字符数组来构造新的String。
public String(byte[] bytes) :通过使用平台的默认字符集解码当前参数中的字节数组来构造新的String。
Demo:
// 无参构造
String str = new String();
// 通过字符数组构造
char chars[] = {'a', 'b', 'c'};
String str2 = new String(chars);
// 通过字节数组构造
byte bytes[] = { 97, 98, 99 };
String str3 = new String(bytes);
注意:通过 new+构造器的方式:此时的str和str2保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
(2)直接创建(字面量的定义方式)
String str = "字符串内容"; // 右边直接用双引号
分析:这里面虽然没有 new 关键字,但同时创建了一个 String 对象。
① 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中;
② 字符串常量池中是不会存储相同内容的字符串的;
三、String 对象的创建【重要】
String 创建实例的方式有多种,下面来学习一下各种创建的本质:
String str = “hello”; //在字符串常量中创建
String s1 = new String(); // 本质上 this.value = new char[0];
String s2 = new String(String original); //this.value = original.value;
String s3 = new String(char[] a); //this.value = Arrays.copyOf(value, value.length);
String s4 = new String(char[] a,int startIndex,int count)
.......
图解:
常见面试题:
(1)String str = new String("hello"); 涉及几个对象?—— 两个
一个堆空间中 new 结构,另一个是 char[] 对应的常量池中的数据 "hello";
(2)String str1 = new String("hello");
String str2 = new String("hello");涉及几个对象?—— 三个
str1和str2 分别是两个在堆空间中 new 结构,另一个是 char[] 对应的常量池中的数据 "hello";
四、字符串是如何存储的
String对象的字符内容是存储在一个字符数组value[]中的。
字符串常量存储在字符串常量池,目的是共享。
字符串非常量对象存储在堆中。
字符串常量池:
1、当直接创建一个字符串时,该变量会到字符串常量池中去寻找该字符串,如果找到了,该变量指向该字符串;如果没有找到,会用 byte[ ] 拼接成所需的字符串,然后放入常量池中并指向它。
2、使用 new 关键字创建字符串,会在堆区中创建一个 String 对象,而且底层是用 byte[ ]数组拼接的,这个 String 对象并没有放入常量,而是在堆中,而该数组类型的 values 指向的才是 字符串常量池中的具体的字符串。
扩展:字符串常量池在哪里?(Oracle 官方虚拟机HotSpot)
(1)JDK1.6以及之前:方法区中(具体实现为永久区)
(2)JDK1.7:挪到堆中,即在堆中单独划分了一块字符串常量
(3)JDK1.8:从堆中挪出,挪到一个 “元空间meta space”,即类似于方法区
五、字符串的拼接
1 @Test
2 public void test1(){
3 String s1 = "javaEE";
4 String s2 = "hadoop";
5
6 String s3 = "javaEEhadoop";
7 String s4 = "javaEE" + "hadoop";
8 String s5 = s1 + "hadoop";
9 String s6 = "javaEE" + s2;
10 String s7 = s1 + s2;
11
12 System.out.println(s3 == s4);//true
13 System.out.println(s3 == s5);//false
14 System.out.println(s3 == s6);//false
15 System.out.println(s3 == s7);//false
16 System.out.println(s5 == s6);//false
17 System.out.println(s5 == s7);//false
18 System.out.println(s6 == s7);//false
19
20 String s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
21 System.out.println(s3 == s8);//true
22 }
23
24 @Test
25 public void test2(){
26 String s1 = "javaEEhadoop";
27 String s2 = "javaEE";
28 String s3 = s2 + "hadoop";
29 System.out.println(s1 == s3);//false
30
31 final String s4 = "javaEE";//s4:常量
32 String s5 = s4 + "hadoop";
33 System.out.println(s1 == s5);//true
34
35 }
结论:
(1)因为只有常量池中才是共享,==比较才为 true;
(2)常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量;
(3)只要其中有一个是变量,结果就在堆中;
(4)如果拼接的结果调用intern()方法,返回值就在常量池中;
六、空字符串
1、表现方式
(1)String str = "";
(2)String str2 = new String();
(3)String str3 = new String("");
2、判断是否为空
(1)if(str != null && str.length() == 0)
(2)if(str != null && str.equals(""))
(3)if("".equals(str)) 推荐
(4)if(str!=null && str.isEmpty())
七、String 使用陷阱
1、String s1 = "a";
说明:在字符串常量池中创建了一个字面量为"a"的字符串
2、s1 = s1 + "b";
说明:实际上原来的“a”字符串对象已经丢弃了, 现在在堆空间中产生了一个字符串s1+"b"(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能。
3、String s2 = "ab";
说明:直接在字符串常量池中创建一个字面量为"ab"的字符串。
4、String s3 = "a" + "b";
说明: s3指向字符串常量池中已经创建的"ab"的字符串;
5、String s4 = s1.intern();
说明:堆空间的s1对象在调用intern()之后,会将常量池中已经存在的"ab"字符串赋值给s4。