目录
String类
1. String类的声明
//jdk源码:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
String类被final修饰,不允许被继承。即String类不存在子类,Java程序中有且仅有一个String类。
继承和方法重写在为程序带来灵活性的同时,也会带来多个子类行为不一致的问题。为保证所有JDK使用者都使用同一String类的固有实现,不允许自定义修改其中的具体实现,不希望体现个体差异性,故使用final终结器,防止String类被继承后进行方法重写。
2. 创建String字符串对象的四种方式
1)直接赋值:String str = "Hello BubbleoOoo"
2)构造方法中传入字符串:String str = new String("Hello BubbleoOoo");
3)构造方法中传入字符数组:将字符数组转为字符串
char[] data = new char[] {'a', 'b', 'c'}; String str = new String(data);
4)调用String类的静态方法valueOf(任意数据类型):将其他数据类型转为String类型
String str = String.valueOf(10);
3. String字符串的不可变性
一个String类字面量
""
即一个字符串对象,保存在字符串常量池中。new出来的字符串对象保存在堆中。
String字符串为不可变字符串,字符串对象一旦声明后即不可修改。程序中修改的仅仅是String类的引用,修改其指向的字符串对象地址。
public class StringTest {
public static void main(String[] args) {
String str1 = "hello world";
String str2 = str1; //str2引用也指向"hello world"
str2 = "hello"; //【仅修改str2引用指向的字符串对象,并未修改字符串对象本身,也不影响str1引用】
System.out.println(str2); //输出str2转为指向"hello"
System.out.println(str1); //输出str1仍然指向"hello world"
}
}
e.g. 下方例题输出结果为 good and gbc 。
【题解】
1)输出gbc的原因:change方法的形参ch为数组对象的引用。当main方法中的ex.ch
作为实参传递给change方法的形参ch
时,这两个引用都指向同一个数组对象的地址值。而在执行change方法的过程中,ch[0] = 'g';
通过形参引用ch
改变了数组对象本身,故当再次通过实参引用ex.ch
访问该数组时,访问到的是改变后的数组,即gbc。
2)输出good的原因:main方法中的ex.str在创建对象ex调用构造方法时完成初始化,指向堆中的”good”。当调用change方法时,会拷贝传入的实参引用ex.str
的地址值,使得此时形参引用str
也指向堆中的”good”。在执行change方法的过程中,形参引用str
的指向发生了转变,转为指向常量池中的”test ok”,但原堆中对象”good”并未被改变。且实参的ex.str
仍然指向原堆中的”good”。待change方法执行结束,其形参引用str
随之销毁。
当引用数据类型的引用作为方法形参时,根据参数的值传递,会将实参引用的地址值拷贝一份给形参引用,使得两个引用指向同一个地址值。
方法的形参、方法内的局部变量均为临时变量。当方法被调用入栈时,其形参、局部变量入栈,待方法调用结束出栈时,这些临时变量都会随之销毁。
public class Example{
String str = new String("good");
char[] ch = { 'a' , 'b' , 'c' };
public static void main(String args[]){ //【main方法入栈】
Example ex = new Example(); //【ex.str入栈、ex.ch入栈】
//ex.str在创建对象调用构造方法时完成初始化,指向堆中的"good"
ex.change(ex.str,ex.ch); //【change方法入栈】【change方法的形参str、ch入栈】
//change方法的形参str由实参ex.str值传递而来,初始时也指向堆中的"good"
/* 执行change方法:改变形参str的指向为常量池中的"test ok",不影响堆中的"good"本身
直接改变了形参ch引用指向的堆中的数组本身 */
//【change方法及其形参出栈】
//此时change方法的形参引用str、ch都随方法的调用结束而销毁
System.out.print(ex.str + " and ");
//ex.str引用仍然指向堆中的"good",且"good"本身未发生改变
System.out.print(ex.ch);
//原实参ex.ch引用指向的数组本身已经发生了改变,访问到的即是改变后的数组gbc
} //【main方法出栈】
public void change(String str,char ch[ ]){ //形参str引用初始时指向堆中的"good"
str = "test ok"; //形参str引用转为指向常量池中的"test ok",但并非修改"good"对象本身
ch[0] = 'g'; //通过形参ch引用已经改变了堆中的数组对象本身
}
}
【总结】
注意当String引用作为实参通过值拷贝传递给形参时,形参和实参的String引用会指向同一个内存地址值。但当形参的Stiring引用发生改变时,只是改变了形参String引用的指向,不改变原String字符串对象(不可变性); 而原来实参的String引用仍然指向原地址值,即指向原String字符串对象。
String字符串不可变的原因
//jdk8源码:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
@Stable
private final char[] value;
...
}
//jdk17源码:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence, Constable, ConstantDesc {
@Stable
private final byte[] value;
...
}
String字符串实际上底层是以字符数组value实现存储的,访问控制修饰符为private,类外无法直接访问,且String类并未为value数组提供相应public权限的getter和setter方法,故外部无法访问此属性,无法对此字符数组进行修改,即无法对此字符串进行修改。
修改字符串内容的两种方式
- 在运行时通过反射破坏value数组的封装(不推荐)
- 改用StringBuilder类或StringBuffer类
4. 字符串常量池
JDK8以前,字符串常量池在方法区中; JDK8以后,字符串常量池在堆中。
使用直接赋值法String str = "hello world";
创建字符串对象时,会先在字符串常量池中检索是否已经存在一个”hello world”字符串常量。若不存在则创建一个新的,加入常量池中,并使str引用指向此新对象; 若常量池中已经存在此对象,则直接将str引用指向常量池中现有的字符串对象。
//以下代码只产生了1个字符串对象
String str3 = "hello world";
String str4 = "hello world";
String str5 = "hello world";
System.out.println(str3 == str4); //输出true,指向的是同一个对象
System.out.println(str4 == str5); //输出true,指向的是同一个对象
//以下代码产生了3个字符串对象:str6、str7指向的对象在堆区,"hello"在常量池中
String str6 = new String("hello");
String str7 = new String("hello");
5. String类的常用方法
1)equals()方法
String类重写了Object类的equals()方法,重写前默认比较的是对象的地址值,重写后比较的是对象的内容。
equals()方法比较两个字符串的内容,区分大小写。
【注意事项】使用equals()方法时尽量将字符串字面量写在前面"hello".equals(str)
,若写作str.equals("hello")
,则当str为null时会引发空指针异常。
JDK常用类已经覆写了equals()方法,如String、Integer等,可直接比较内容。
2)equalsIgnoreCase()方法
equalsIgnoreCase()方法比较两个字符串的内容,不区分大小写。
3)intern()方法
intern()方法会将当前String类引用指向的对象保存到字符串常量池中。
//jdk源码:
public native String intern();
若常量池中已存在该字符串对象,则不再入池。返回常量池中此现有的字符串对象地址。
若常量池中不存在该字符串对象,则让该对象入池(移动到常量池中)。返回此对象入池后的地址。
e.g. (intern不入池的情况)
String str1 = new String("hello");
str1.intern(); //让堆区的"hello"入池,但池中已有一个"hello",不再入池
//【此时str1仍然指向堆区的"hello"】
String str2 = "hello"; //str2指向池中的"hello"
System.out.println(str1 == str2); //输出false
String str1 = new String("hello");
str1 = str1.intern(); //intern()的返回值为常量池中现有的"hello"的地址
//【此时str1转为指向池中的"hello"】
String str2 = "hello";
System.out.println(str1 == str2); //输出true
以上两段代码仅第2行不同,但输出结果相反。
e.g. (intern入池的情况)
char[] data = new char[] {'a','b','c'};
String str1 = new String(data); //str1指向堆区的"abc"
str1.intern(); //常量池中不存在"abc",str1指向的"abc"入池(从堆区移动到常量池)
//【此时str1指向的"hello"位于常量池中】
String str2 = "abc";
System.out.println(str1 == str2); //输出true
4)comparable()方法
String类也实现了Comparable接口,重写了compareTo()方法。按照“字典序”比较两个字符串的大小,返回二者ASCII码的差值。
字典序:按照字符的ASCII码值从小到大排序。如:A(65)排在a(97)前面。
String str1 = "abc";
String str2 = "Abc";
System.out.println(str1.compareTo(str2)); //输出32,【返回值int为str1-str2】 97(a)-65(A)=32
5)字符串的查找操作
public boolean contains(CharSequence s)
:判断该字符串的子序列是否存在
public boolean startsWith(String prefix)
:判断该字符串是否以指定字符串开头
public boolean endsWith(String suffix)
:判断该字符串是否以指定字符串结尾
String str = "hello world";
System.out.println(str.contains("world")); //判断是否包含"world",输出true
System.out.println(str.startsWith("hello")); //判断是否以"hello"开头,输出true
System.out.println(str.startsWith("ooo")); //输出false
System.out.println(str.endsWith("world")); //判断是否以"world"结尾,输出true
System.out.println(str.endsWith("ooo")); //输出false
6)字符串的替换操作
public String replaceAll(String regex, String replacement)
:替换该字符串中所有指定的字符串内容
public String replaceFirst(String regex, String replacement)
:替换该字符串中第一个指定的字符串内容
String str = "hahahawaiwo";
System.out.println(str.replaceAll("ha", "01")); //把所有ha都替换为01,输出010101waiwo
System.out.println(str.replaceFirst("ha", "01")); //把第一个ha替换为01,输出01hahawaiwo
7)字符串的拆分操作
public String[] split(String regex)
:将一个字符串按照指定字符串作为间隔,全部拆分为多个字符串
public String[] split(String regex, int limit)
:将一个字符串按照指定字符串作为间隔,拆分成指定个数的字符串
String str = "hello java hello Rocket";
String[] strings1 = str.split(" "); //将整个字符串按照空格拆分为多个字符串,保存在字符串数组中
String[] strings2 = str.split(" ", 2); //将字符串按照空格拆分成两个字符串,保存在字符串数组中
System.out.println(Arrays.toString(strings1)); //输出[hello, java, hello, Rocket]
System.out.println(Arrays.toString(strings2)); //输出[hello, java hello Rocket]
若拆分的标准是特殊字符,则需要进行转义处理,否则无法拆分。
String s = "192.168.1.1";
String[] strings = s.split("\\."); //特殊字符需要转义处理
System.out.println(Arrays.toString(strings)); //输出[192, 168, 1, 1]
8)字符串的截取操作
public String substring(int beginIndex)
:从指定索引处开始截取到字符串结尾
public String substring(int beginIndex, int endIndex)
:从指定索引处开始截取字符串到指定索引之前结束
Java中大多数范围均为左闭右开区间。
String str = "hello world";
System.out.println(str.substring(5)); //从索引为5处开始截取字符串到末尾,输出 world
System.out.println(str.substring(2, 7)); //截取[2,7)之间的字符串,输出llo w
9)其他常用方法
public String trim()
:去除字符串的首尾空格,保留中间的空格
public String toUpperCase()
:字符串转大写
public String toLowerCase()
:字符串转小写
public int length()
:获取字符串长度
public boolean isEmpty()
:判断字符串是否为空字符串【空字符串 ≠ null】
String str = " hello bubble ";
System.out.println(str.trim()); //输出hello bubble
System.out.println(str.toUpperCase()); //输出 HELLO BUBBLE
System.out.println(str.toLowerCase()); //输出 hello bubble
System.out.println(str.length()); //输出14,包括空格
将字符串的首字母作大写处理
public static void main(String[] args) {
System.out.println(StringFirstUpper("")); //空字符串,输出null
System.out.println(StringFirstUpper("a")); //输出A
System.out.println(StringFirstUpper("hello")); //输出Hello
}
/** 将字符串的首字母大写处理 */
public static String StringFirstUpper(String str) {
// 1.判空处理
if(str == null || str.isEmpty()) {
return null;
}
// 2.边界条件
if(str.length() == 1) { //字符串只有一个字符
return str.toUpperCase(); //直接返回
}
//字符串中不止一个字符
return str.substring(0,1).toUpperCase() + str.substring(1);
//截取第一个字符转为大写后,截取原后续字符串进行拼接
}
6. String和char的相互转换
1)char转换为String
① 构造方法String(char[] value)
:将整个字符数组转为String字符串
char[] data = new char[] {'a','b','c'};
String str = new String(data);
System.out.println(str); //输出abc
② 构造方法String(char[] value, int offset, int count)
:将部分字符数组转为String字符串
offset:开始转换为字符串的索引 count:转换为字符串的字符个数
char[] data = new char[] {'a','b','c','d','e'};
String str = new String(data, 1, 3); //从索引1开始,转换3个字符为字符串
System.out.println(str); //输出bcd
2)String转换为char
① String类的public char charAt(int index)
方法:取出字符串中指定索引处的单个字符
String str = "hello";
System.out.println(str.charAt(1)); //输出e
② String类的public char[] toCharArray()
方法:将整个字符串转为字符数组
String str = "hello";
char[] ch = str.toCharArray();
System.out.println(ch); //输出hello
ch[2] = '7'; //修改字符数组
System.out.println(ch); //输出he7lo
System.out.println(str); //输出hello,原字符串并未被修改(String对象不可变)
判断一个字符串是否是纯数字字符串
先将整个字符串转为字符数组,再遍历检查每个字符是否都是数字,找到反例则返回false。
判断一个字符是否是数字:可使用包装类Character的静态方法isDigit(char c)
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc123";
System.out.println(isDigitString(s1)); //输出true
System.out.println(isDigitString(s2)); //输出false
}
/** 判断某个字符串是否是纯数字字符串 */
public static boolean isDigitString(String str) {
//先将该字符串转换为字符数组
char[] ch = str.toCharArray();
//遍历字符数组,检查每个字符
for(char c : ch) {
//若存在一个字符不是数字,则返回false
//if(c < '0' || c > '9') return false;
if(Character.isDigit(c)) return false;
}
return true;
}
7. String和byte的相互转换
1)byte转换为String
① 构造方法String(byte[] bys)
:将整个字节数组转为String字符串,每个字节按照对应的ASCII码值转换为字符
byte[] data = new byte[] {97,98,99};
String str = new String(data); //按照相应的ASCII码值转为字符串
System.out.println(str); //输出abc
② 构造方法String(byte[] bytes, int offset, int length)
:将部分字节数组转为String字符串
offset:开始转换为字符串的索引 length:转换为字符串的字节个数
byte[] data = new byte[] {97,98,99,100,101};
String str = new String(data, 2, 3); //从索引2开始,转换3个字节为字符串
System.out.println(str); //输出cde
2)String转换为byte
① String类的public byte[] getBytes()
方法:将字符串按照默认的编码格式转换为字节数组
② String类的public byte[] getBytes(String charsetName)
方法:将字符串按照指定的编码格式转换为字节数组
String str = "你好中国";
byte[] bys1 = str.getBytes(); //IDEA默认编码格式为UTF-8
byte[] bys2 = str.getBytes("GBK"); //抛出UnsupportedEncodingException异常
System.out.println(Arrays.toString(bys1)); //输出[-28, -67, -96, -27, -91, -67, -28, -72, -83, -27, -101, -67]
System.out.println(Arrays.toString(bys2)); //输出[-60, -29, -70, -61, -42, -48, -71, -6]
UTF-8编码格式下,一个中文汉字占3个字节。
GBK编码格式下,一个中文汉字占2个字节。
StringBuilder类
可变字符串,StringBuilder对象的内容可以修改。
1. StringBuilder类的常用方法
1)append()方法
使用append()方法可实现字符串的拼接操作,返回拼接后的StringBuilder对象。
2)reverse()方法
使用reverse()方法可实现StringBuilder字符串的反转操作。
3)delete(int start, int end)方法
删除字符串中指定索引范围为 [start,end) 的所有内容。
Java中大多数范围均为左闭右开区间。
StringBuilder sb = new StringBuilder("hello");
sb.append("world");
sb.delete(5,8); //删除索引范围为[5,8)内的所有内容,即从w开始删除到l之前
System.out.println(sb); //输出hellold
4)insert(int start, 任意数据类型)方法
在索引start处开始插入任意数据类型的内容。插入内容的起始索引是start。
StringBuilder sb1 = new StringBuilder("hello");
sb1.insert(2,77);
System.out.println(sb1); //输出he77llo
sb1.insert(3,"ooo");
System.out.println(sb1); //输出he7ooo7llo
2. StringBuilder与String的相互转换
1)String转为StringBuilder
① 构造方法StringBuilder(String str)
StringBuilder sb = new StringBuilder("hello");
② 字符串拼接方法append(String str)
StringBuilder sb = new StringBuilder();
sb.append("123");
2)StringBuilder转为String
通过StringBuilder对象调用toString()方法
String str = sb.toString();
String、StringBuilder、StringBuffer的区别
- String为不可变字符串类,StringBuilder、StringBuffer为可变字符串类。
- StringBuilder类性能较高,但存在线程安全问题; StringBuffer类线程安全,但性能较差。