概述
String 类代表的是字符串,Java 中所有的字符串("1234"、"abcd"、"中国")都是作为 String 类型的实例对象使用的。
String 类被关键字 final 修饰,即不能够派生子类。
String 类属于引用数据类型,因此理论上也可以通过关键字 new 的方式创建 String 类型的实例对象。
java
String s1 = "1234";
String s2 = new String("1234");
在 String 类的底层有一个关键字 final 修饰的 char[] 数组类型的成员变量 value,它是用来存放 String 类型的实例对象表示的字符串包含的字符,既然使用了关键字 final 修饰,那也就表示一旦一个字符串对象确定之后,会在内存区域生成对应的字符串对象,该对象的成员变量 value 的值就不能够在改变了,即 String 类型的实例对象表示的字符串只能够表示该对象代表的字符串,不能够是其它的字符串对象。
常量池
JVM 为了提升性能和减少内存的开销,避免字符串对象的重复创建,因此其维护了一块特殊的内存空间,即所谓的字符串常量池。
当我们使用某个字符串对象的时候,JVM 并不会立马创建该字符串对象,首先会先去字符串常量池中查看是否已经存在一个表示该字符串的字符串对象,如果存在,则直接拿来使用,如果不存在,则会初始化创建一个表示该字符串的实例对象,并且会把该字符串的实例对象放入到字符串常量池中,为了方便以后的重复使用。
直接把一个字符串对象赋值给 String 类型变量和通过 new String() 方式声明进行赋值的区别?
(1)直接赋值的方式直接把一个字符串对象赋值给 String 类型的引用变量,整个过程中会创建 0 个或者 1 个字符串实例对象,例如语句 String s = "1234",如果字符串对象 "1234" 已经存在于字符串常量池中,则直接把该字符串对象的内存地址值赋值给 String 类型的引用变量 s,此时创建了 0 个字符串实例对象,如果字符串常量池中不存在表示字符串 "1234" 的实例对象,则先创建一个表示字符串 "1234" 的实例对象,然后把该对象的内存地址赋值给 String 类型的引用变量 s,同时把刚创建出来的字符串对象放入到字符串常量池中,此时创建了 1 个字符串实例对象,以后不管以该种方式把多少个 String 类型的引用变量指向字符串对象 "1234",都会直接使用字符串常量池中已经存在的表示 "1234" 的字符串对象。
(2)new String() 方式声明字符串对象通过 new String() 方式声明字符串对象并把字符串对象赋值给 String 类型的引用变量的操作,整个过程中会创建 1 个或者 2 个字符串对象,因为我们使用关键字 new 创建实例对象的时候,肯定会在堆内存中开辟并分配一块内存区域用来创建对象,同时如果字符串对象 "1234" 已经存在于字符串常量池中,则直接把字符串常量池中的表示 "1234" 的字符串对象赋值给刚刚在堆内存中通过关键字 new 创建 出来的对象,此时创建了 1 个字符串实例对象(堆内存中的 1 个),如果字符串常量池中不存在表示字符串 "1234" 的实例对象,则先创建一个表示字符串 "1234" 的实例对象,然后把该对象赋值给刚刚在堆内存中通过关键字 new 创建出来的对象,同时把刚创建出来的字符串对象放入到字符串常量池中,此时创建了 2 个字符串实例对象(堆内存中的 1 个以及字符串常量池中的 1 个)。
示例:
java
public static void main(String[] args) {
String s1 = "1234"; //创建放入字符串常量池
String s2 = "1234"; //直接使用字符串常量池已存在的 "1234" 实例对象
String s3 = new String("1234");
//s1、s2 都表示的都是常量池中的 "1234" 字符串对象
System.out.println(s1 == s2); //true
//s1 表示的都是常量池中的 "1234" 字符串对象,而 s3 表示的是堆内存中的字符串对象
System.out.println(s1 == s3); //false
// String 类中重写了 equals() 方法,是对字符串内容的比较
System.out.println(s1.equals(s3)); //true
}
了解equals() 方法
它是 Object 类中提供的一个方法,它的源码如下:
java
public boolean equals(Object obj) {
return (this == obj);
}
该方法中的关键字 this 表示调用该方法的那个实例对象,this == obj 就是用来判断调用该方法的那个实例对象和调用该方法时实际传入的 Object 类型的实例对象是否相等,它实际比较的是两个对象的内存地址是否相同,即比较这两个对象是否是同一个对象。
虽然 Object 类中的 equals() 方法是用于比较两个对象是否相等,但是在以后的实际运用中并不会通过该方法比较两个对象是否相等,因为绝大多数类中的 equals() 方法都被重写了,重写之后的 equals() 方法并不一定是通过 "==" 比较两个对象的,因此重写之后的 equals() 方法也就不一定是比较两个对象是否相等了。如果需要比较两个对象是否相等,直接通过 "==" 比较即可。
以后 equals() 方法一般用来比较两个对象的内容是否相同,即两个对象的成员变量的值是否相同,如 String 类中的 equals() 方法就是对 Object 类中的 equals() 方法进行了重写,用来比较两个字符串对象的内容是否相同,而不是字符串对象是否相等。
== 和 equals() 方法的区别?
(1)"==" 是用来判断两个对象是否相等(是否是同一个对象)。
(2)equals() 方法是用来比较两个对象的内容是否相同(类型相同的两个对象),它跟 Object 类中的 equals() 方法不太一样,因为绝大多数类中的 equals() 方法都被重写了。
如何重写 equals() 方法?
我们可以手动的重写 equals() 方法,但是如果当一个类的成员变量比较多的时候,此时通过手动的方式去重写 equals() 方法,代码量比较大,同时还容易出错,因此在开发工具中都提供了自动生成功能,帮我们快速的完成 equals() 方法的重写。
构造方法
在 String 类中存在多个重载的构造方法。
(1)public String()
创建一个空字符串对象。
(2)public String(String original)
子字符串参数 original 作为字符串对象的内容在堆内存中创建一个字符串对象。
(3) public String(char value[])
以 char[] 类型的参数 value 中所有的字符构作为字符串对象的内容在堆内存中创建一个字符串对象。
(4)public String(char value[], int offset, int count)
以 char[] 类型的参数 value 中从 offset 索引位置开始 count 个字符作为字符串对象的内容在堆内存中创建一个字符串对象。
(5)public String(int[] codePoints, int offset, int count)
从 int[] 类型的参数 codePoints 中索引位置 offset 开始,count 个 int 类型的数字对应的字符作为字符串对象的内容在堆内存中创建一个字符串对象。 每一个 char 类型的字符对应一个 int 类型的 unicode 值。
(6)public String(byte bytes[], int offset, int length, String charsetName)
把指定 byte[] 数组中从 offset 索引位置开始,length 长度的字节以 charsetName 编码格式转换为对应的字符串对象。
(7)public String(byte bytes[], int offset, int length, Charset charset)
把指定 byte[] 数组中从 offset 索引位置开始,length 长度的字节以 charset 编码格式转换为对应的字符串对象。
(8)public String(byte bytes[], String charsetName)
把指定 byte[] 数组中所有的字节以 charsetName 编码格式转换为对应的字符串对象。
(9)public String(byte bytes[], Charset charset)
把指定 byte[] 数组中所有的字节以 charset 编码格式转换为对应的字符串对象。
(10)public String(byte bytes[], int offset, int length)
把指定 byte[] 数组中从 offset 索引位置开始,length 长度的字节以 默认的编码格式(工作空间对应的编码格式)转换为对应的字符串对象。
(11)public String(byte bytes[])
把指定 byte[] 数组中所有的字节以默认编码格式(工作空间对应的编码格式)转换为对应的字符串对象。
(12)public String(StringBuffer buffer)
以 StringBuffered 对象作为参数创建一个字符串对象。
(13)public String(StringBuilder builder)
以 StringBuilder 对象作为参数创建一个字符串对象。
常用方法
(1)int length()
返回字符串的长度,即字符串组包含的字符的数量。
(2)boolean isEmpty()
判断字符串是否是一个空字符串,即长度是否为 0。
(3)char charAt(int index)
返回字符串组中指定索引 index 处的字符。
(4)int codePointAt(int index)
返回字符串组中指定索引 index 处的字符对应的 unicode 编码值。
(5)int codePointBefore(int index)
返回字符串组中指定索引 index 处的前一个索引位置处的字符对应的 unicode 编码值。
(6)int codePointCount(int beginIndex, int endIndex)
返回字符串中 [beginIndex,endIndex) 索引区间中包含的字符的数量。
(7)int offsetByCodePoints(int index, int codePointOffset)
返回字符串中指定索引处 index 处偏移 codePointOffset 代码点的索引位置,codePointOffset 参 数可正可负。
(8)void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)
复制操作,把字符串中 [srcBegin,srcEnd) 索引区间内的字符复制到指定的 char[] 类型的目标数组中的索引dstBegin 位置。
(9)byte[] getBytes(String charsetName)
把指定的字符串以指定的编码格式 charsetName 转换成字节数组。
(10)byte[] getBytes(Charset charset)
把指定的字符串以指定的编码格式 charset 转换成字节数组。
(11)byte[] getBytes()
把指定的字符串以默认的编码格式转换成字节数组。
(12)boolean equals(Object anObject)
用来判断两个字符串对象的内容是否相同,它是对父类 Object 中的 equals() 方法的重写。
(13)boolean equalsIgnoreCase(String anotherString)
用来判断两个字符串对象的内容是否相同(不区分大小写)。
(14)boolean contentEquals(StringBuffer sb)
判断字符串对象和 StringBuffer 对象中包含的内容是否相同。
(15)boolean contentEquals(CharSequence cs)
判断字符串对象和 CharSequence 对象表示的字符串的内容是否相同。
(16)int compareTo(String anotherString)
用来比较两个字符串对象的大小,如果调用此方法的字符串对象比参数字符串对象 anotherString 大,则返回正整数,如果调用此方法的字符串对象比参数字符串对象 anotherString 小,则返回负整数,如果调用此方法的字符串对象等于参数字符串对象 anotherString ,则返回 0。 原理:依次比较两个字符串对象中对应位置的字符的 unicode 编码值,直到确定出大小位置,具体的返回值就是能够确定出大小的两个字符的 unicode 编码值之间的差值。
(17)int compareToIgnoreCase(String str)
用来比较两个字符串对象的大小(不区分字符的大小写)。
(18)boolean regionMatches(int toffset, String other, int ooffset, int len)
判断两个字符串对象在一定区域内是否相等,即字符串的部分内容是否相等,toffset 参数表示调用该方法的字符串的起始偏移量,other 目标字符串,ooffset 参数表示目标字符串的起始偏移量,len 表示要比较的字符的长度。
(19)public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len)
判断两个字符串对象在一定区域内是否相等,即字符串的部分内容是否相等,ignoreCase 参数表示是否忽略大小写,toffset 参数表示调用该方法的字符串的起始偏移量,other 目标字符串,ooffset 参数表示目标字符串的起始偏移量,len 表示要比较的字符的长度。
(20)boolean startsWith(String prefix, int toffset)
判断字符串中索引 toffset 位置开始之后的子字符串是否以指定的字符串前缀 prefix 开始 。
(21)boolean startsWith(String prefix)
判断字符串是否以指定的字符串前缀 prefix 开始 。
(22)boolean endsWith(String suffix)
判断字符串是否以指定的字符串后缀 suffix 结束。
(23)int hashCode()
对父类 Object 中的 hashCode() 方法的重写。
(24)int indexOf(int ch)
获取指定的字符(int 类型表示对应的 unicode 值) ch 在字符串中第一次出现的位置。
(25)int indexOf(int ch, int fromIndex)
获取指定的字符(int 类型表示对应的 unicode 值) ch 在字符串中从索引位置 fromIndex 开始向后第一次出现的位置。
(26)int lastIndexOf(int ch)
获取指定的字符(int 类型表示对应的 unicode 值) ch 在字符串中最后一次出现的位置。
(27)int lastIndexOf(int ch, int fromIndex)
获取指定的字符(int 类型表示对应的 unicode 值) ch 在字符串中从索引位置 fromIndex 开始向 前最后一次出现的位置。
(28)int indexOf(String str)
获取指定的字符串 str 在字符串中第一次出现的位置。
(29)int indexOf(String str, int fromIndex)
获取指定的字符串 str 在字符串中从索引位置 fromIndex 开始向后第一次出现的位置。
(30)int lastIndexOf(String str)
获取指定的字符串 str 在字符串中最后一次出现的位置。
(31)int lastIndexOf(String str, int fromIndex)
获取指定的字符串 str 在字符串中从索引位置 fromIndex 开始向前最后一次出现的位置。
(32)String substring(int beginIndex)
截取字符串中索引范围 [beginIndex,length) 的子字符串并返回。
(33)String substring(int beginIndex, int endIndex)
截取字符串中索引范围 [beginIndex,endIndex) 的子字符串并返回。
(34)CharSequence subSequence(int beginIndex, int endIndex)
截取字符串中索引范围 [beginIndex,endIndex) 的子字符串并以 CharSequence 类型的实例对象返回。
(35)String concat(String str)
用来进行字符串拼接,它是在已有的字符串的基础上拼接参数字符串 str,它 "+" 进行字符串拼接的作用效果一样。
(36)String replace(char oldChar, char newChar)
把字符串中指定的字符 oldChar 使用新的字符 newChar 替换生成一个全新的字符串返回。
(37)boolean matches(String regex)
判断字符串是否满足指定的正则。
(38)boolean contains(CharSequence s)
判断字符串是否包含 CharSequence 类型的对象 s 表示的子字符串。
(39)String replaceFirst(String regex, String replacement)
把字符串满足正则 regex 的第一个子字符串使用 replacement 替换生成一个全新的字符串并返回。
(40)String replaceAll(String regex, String replacement)
把字符串满足正则 regex 的所有子字符串使用 replacement 替换生成一个全新的字符串并返回。
(41)String replace(CharSequence target, CharSequence replacement)
把字符串中所有匹配 CharSequence 类型的实例对象 target 表示的子字符串使用 CharSequence 类型的实例对象 replacement 字符串替换替换生成一个全新的字符串并返回。
(42)String[] split(String regex)
把字符串中匹配正则 regex 的子字符串作为分割符,把字符串分割成一个 String[] 数组。
(43)String[] split(String regex, int limit)
把字符串中匹配正则 regex 的子字符串作为分割符,把字符串分割成一个长度为 limit 的 String[] 数组,参数 limit 对数组的长度进行了限制。
(44)String toLowerCase()
把字符串中所有的大写字符转换成对应的小写字符生成一个全新的字符串并返回。
(45)String toUpperCase()
把字符串中所有的小写字符转换成对应的大写字符生成一个全新的字符串并返回。
(46)String trim()
把字符串两端的空格去除掉并生成一个全新的字符串返回。
(47)String toString()
对 Object 类中的 toString() 方法的重写,即把字符串对象表示的字符串内容本身返回。
(48)char[] toCharArray()
把字符串转换成 char[] 数组返回。
注意点
String 类中的绝大多数方法,在调用执行之后,对调用方法的字符串对象本身而言并没有任何的影响,只不过是在调用方法的字符串对象本身基础上进行了相应的操作生成了一个全新的字符串对象。
字符串拼接
示例:
java
public static void main(String[] args) {
String s1 = "111";
String s2 = "222";
String s3 = "333";
String s4 = "111" + "222" + "333";
String s5 = s1 + s2 + s3;
System.out.println(s4);
System.out.println(s5);
}
正常代码的编译过程是把 .java 源文件通过 javac.exe 命令编译成 .class 字节码文件,但是通过反编译器也可以把 .class 字节码文件反编译为 .java 源文件。
通过反编译工具对上面源代码编译完成生成的 .class 字节码文件进行反编译:
java
public static void main(String[] args) {
String s1 = "111";
String s2 = "222";
String s3 = "333";
String s4 = "111222333";
String s5 = String.valueOf(s1) + s2 + s3;
System.out.println(s4);
System.out.println(s5);
}
其中的
java
String s4 = "111" + "222" + "333";
会被优化成
java
String s4 = "111222333";
但是对于 String s4 = s1 + s2 + s3; 语句并没有被优化,该字符串拼接的表达式中每 一次 "+" 的操作,都会产生一个全新的 String 类型的字符串对象,即在实际运行的时候,会先让 s1 + s2 执行拼接,会生成一个新的字符串对象 "111222",然后使用新生成的 "111222" 字符串对象跟 s3 进行拼接,最后生成一个全新的字符串对象 "111222333" ,即我们最终想要获得的拼接结果。
在整个拼接过程中产生好几个我们不需要的临时字符串对象,如果以后执行大量的字符串拼接的时候,同样的道理就会产生大量的临时字符串对象,而对象的创建需要消耗的资源相对成本较大。
所以 java 中专门提供了 StringBuffer 和 StringBuilder 这两个类,它们可以完成大量字符串拼接的操作,并且整个拼接的过程中操作了一个对象。
StringBuffer 类
StringBuffer 类在创建实例对象的时候,它可以使用无参构造创建一个不包含任何字符串内容的实例对象,也可以使用有参构造,通过传递一个字符串对象作为参数,创建一个包含字符串参数内容的实例对象。
它的底层是使用 char[] 数组来实现的,即把要拼接的字符串中的字符依次放到入到底层数组中,有点类似于集合框架中的 ArrayList 类。
构造方法
(1)public StringBuffer()
创建一个不包含任何字符串内容的实例对象。
(2)public StringBuffer(String str)
创建一个把参数 str 作为初始字符串内容的实例对象。
(3)public StringBuffer(CharSequence seq)
创建一个把参数 seq 作为初始字符串内容的实例对象。
(4)public StringBuffer(int capacity)
创建一个底层的 char[] 数组长度为 capacity 实例对象。
字符串拼接方法:StringBuffer append(String str)
该方法的源码如下:
java
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
该方法的最终返回值为 this,即调用该方法的对象本身,因此我们可以把上面的代码进行改进
java
//创建一个 StringBuffer 类型的实例对象
StringBuffer sb = new StringBuffer();
sb.append(s1).append(s2).append(s3);
String s4 = sb.toString();
System.out.println(s4);
这种写法称之为链式编程,以后对于方法的返回值就是调用方法的那个对象本身的时候,就可以使用链式编程进行代码的编写。
StringBuilder 类
它是在 JDK 1.5 开始新增的一个类,它的用法和 StringBuffer 基本一模一样,但是它们之间也存在一些区别,StringBuffer 类中的绝大多数方法都是使用关键字 synchronized 修饰的,而 StringBuilder 类中的方法却没有使用关键字 synchronized 修饰。
(1)StringBuffer 类是线程安全的,但是效率略低。
(2)StringBuilder 类是非线程安全的,但是效率略高。
String、StringBuffer、StringBuilder 之间的区别?
(1)String 类型的实例对象表示的字符串内容是不可变的,即一个字符串对象创建出来之后,该对象底层实现的 char[] 数组中的内容就不能够再改变了(被关键字 final 修饰了),String 类型的实例对象使用 "+" 或者 concat() 方法进行字符串拼接的时候,会产生临时字符串对象。但对于 StringBuffer、 StringBuilder 对象在调用 append() 方法进行字符串拼接的时候不会产生临时字符串,因为它们操作的是对象本身。
(2)StringBuffer 类型的对象的底层实现数组是可变的,并且是线程安全的,但效率略低,同时调用 append() 方法进行字符串拼接的时候不会产生临时字符串,因为操作的是对象本身。
(3)StringBuilder 类型的对象的底层实现数组是可变的,是非线程安全的,但效率略高,同时调用 append() 方法进行字符串拼接的时候也不会产生临时字符串,因为操作的是对象本身。