Java核心类(1 - String)
String类
在Java中,String
是一个引用类型,它本身也是一个class
。但是,Java编译器对String
有特殊处理,即可以直接用"..."
来表示一个字符串:
String s = "HelloWorld!";
实际上,字符串在String
内部是通过一个char[]
数组表示的,因此下列写法也是正确的:
String s = new String(new char[]{'H','w','l','l','o','!'});
Java字符串一个重要特点就是字符串不可变
。这种不可变是通过内部的private final char[]
字段,以及没有任何修改char[]
的方法实现的。
String s = "abcd";
System.out.println(s);
s="1234";
System.out.println(s);
输出:
abcd
1234
从上述结果可以看出s的值确实改变了,那么为什么还说String对象是不可改变的呢?s只是一个String对象的引用,并不是对象本身、引用只是一个4字节的数据,里面存放了它所指的对象地址,通过这个地址可以访问对象。
一开始,s指向"abcd"这个具体的对象,当s="1234"这句代码执行过后,内存中新开辟出一段空间存字符串"1234",然后s指向这个新字符串,而原字符串"abcd"在内存中依旧存在,并未改变。
下面的只是一些常用的String类方法,如果想要详细了解String类,可以参考Java官方文档:
https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/lang/String.html
字符串比较
当我们想比较两个字符串内容
是否相等,我们必须要使用equals()
方法,而不能使用==
。
public class Main{
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "Hello";
System.out.println(s1==s2);
System.out.println(s1.equals(s2));
}
}
true
true
从表面上看,两个字符串用==
和equals()
比较都为true
,但实际上那只是因为Java编译器在编译期,会自动的把所有相同的字符串当做一个对象放入常量池,自然s1
和s2
的引用就是相等的。
所以,这种==
比较返回true
纯属巧合,换一种写法就会出错,比如:
public class Main{
public static void main(String[] args) {
String s1 = "HELLO";
String s2 = "hello".toUpperCase();
System.out.println(s1==s2);
System.out.println(s1.equals(s2));
}
}
输出:
flase
true
可以看出,两个字符串比较,必须使用equals()
方法。
要忽略大小写比较,可以使用equalsIgnoreCase()
方法。
下面总结一些String
类常用的方法:
注意:索引号是从
0
开始的
包含子串
//查看是否包含子串
"Hello".contains("ll"); // true
contains()
方法的参数是CharSequence
而不是String
,因为CharSequence
是String
的父类。
搜索子串
//查找字符串第一次出现的位置(索引)
"Hello".indexof("l"); // 2
//查找字符串最后一次出现的位置(索引)
"Hello".lastIndexof("l"); // 3
//检查原字符串是否是以字符串a开始的
"Hello".startsWith("He"); // true
//检查原字符串是否是以字符串b结尾的
"Hello".sendsWith("lo"); // true
提取子串
"Hello".substring(2); // "llo" 截取索引号2及以后的字符串
"Hello".substring(2,4); // "ll" 截取索引号2到4(不含4)的字符串
去除首尾空白字符
//使用trim()可以去除首尾空白字符(空格,\t,\r,\n)
"\r \tHello\n ".trim(); // "Hello"
//使用strip()除了可以去除首尾空白字符,还可以去除类似中文的空格字符\u3000
"\u3000\r \tHello\n \u3000 ".strip(); // "Hello"
判断是否为空或空白字符串
"".isEmpty(); //true , 因为字符串的长度为0
" ".isEmpty(); //false , 因为字符串的长度不为0
" \n".isBlank(); //true , 因为该字符串只包含空白字符
"Hello \n".isBlank(); //false , 因为该字符串包含非空白字符
替换子串
String s = "Hello";
System.out.println(s.replace("l","w"));
System.out.println(s.replace("ll","~~"));
输出:
Hewwo
He~~o
分割字符串
public class Main{
public static void main(String[] args) {
String s = "A,B,C,D";
String[] ss = s.split("\\,"); //{"A","B","C","D"} 正则表达式
for(String a:ss){
System.out.println(a);
}
}
}
输出:
A
B
C
D
拼接字符串
String[] arr = {"A","B","C"};
String s= String.join("***",arr); // "A***B***C"
格式化字符串
public class Main{
public static void main(String[] args) {
String s = "Hello %s, your score is %d!";
System.out.println(s.formatted("Bob",100));
System.out.println(String.format("Hello %s, your score is %d!","Alan",78));
}
}
输出:
Hello Bob, your score is 100!
Hello Alan, your score is 78!
formatted()
方法从JDK13开始提供
常用的占位符:
%s
:显示字符串,不确定用啥占位符就始终用%s
,它可以显示任何数据类型;%d
:显示整数;%x
:显示十六进制整数;%f
:显示浮点数,%.2f
表示显示两位小数。
类型转换
要把任意基本数据类型或引用类型转换为字符串,可以使用静态方法valueOf()
。这是一个重载方法,编译器会根据参数自动选择合适的方法:
String.valueOf(123); //"123"
String.valueOf(154.34); //"154.34"
String.valueOf(true); //"true"
String.valueOf(new Object()); //类似java.lang.Object@4c873330
要把字符串转换为其他类型,就需要根据情况。例如,把字符串转换为int
类型:
int n1 = Integer.parseInt("123"); // 123
int n2 = Integer.parseInt("ff",16); //按十六进制转换,255
把字符串转换为boolean
类型:
boolean a = Boolean.parseBoolean("true"); //true
boolean b = Boolean.parseBoolean("FALSe"); //false
parseBoolean()
方法是无视字符串的大小写的,因为他的函数体是return "true".equalsIgnoreCase(s);
。
转化为char[]
String
和char[]
类型可以互相转换,方法是:
char[] s1 = "Hello".toCharArray(); // String -> char[]
String s2 = new String (s1); // char[] -> String
StringBuilder
Java编译器对String
做了特殊处理,使得我们可以直接使用+
进行拼接字符串。例如:
public class Main{
public static void main(String[] args) {
String s = "";
for(int i=0;i<10;i++)
s=s+","+i;
System.out.println(s);
}
}
输出:
,0,1,2,3,4,5,6,7,8,9
但是这样直接进行拼接字符串是低效的,在每次循环中,都会创建新的字符串对象,然后扔掉旧的字符串对象,这个就是之前讲过的字符串的不可变性。这样,绝大多数字符串都是临时对象,不但浪费内存,还会影响GC效率。
拓展:
GC:Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,在使用JAVA的时候,一般不需要专门编写内存回收和垃圾清理代 码。这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制,可以自动清除无用对象。
为了高效拼接字符串,Java标准库提供了StringBuilder
,他是一个可变对象,可以预分配缓冲区,这样往StringBuilder
中新增字符时,不会创建新的临时对象:
public class Main{
public static void main(String[] args) {
StringBuilder Str = new StringBuilder();
for(int i=0;i<10;i++){
Str.append(",");
Str.append(i);
}
String s = Str.toString();
System.out.println(s);
}
}
输出:
,0,1,2,3,4,5,6,7,8,9
StringBuilder
还可以进行链式操作。
public class Main{
public static void main(String[] args) {
StringBuilder Str = new StringBuilder();
for(int i=0;i<10;i++){
Str.append(",")
.append(i);
}
System.out.println(Str);
}
}
输出:
,0,1,2,3,4,5,6,7,8,9
如果我们查看StringBuilder
源码,我们可以发现,进行链式操作的关键是,定义的append()
方法会返回this
,这样,就可以不断调用自身的其他方法。
StringBuilder
类append()
方法源码如下:
public StringBuilder append(String str) {
super.append(str);
return this;
}
注意:对于普通的字符串+
操作,并不需要我们将其改写为StringBuilder
,因为Java编译器在编译时就自动将多个连续的+
操作编码为StringConcatFactory
的操作。在运行期,StringConcatFactory
会自动把字符串连续操作优化为数组复制或者StringBuilder
操作。
StringJoiner
可以使用StringJoiner
方法来用分隔符拼接数组。
public class Main {
public static void main(String[] args) {
StringJoiner stringJoiner = new StringJoiner(",","Hello ","!");
String[] names = {"Alan","Join","Mike"};
for(String s : names){
stringJoiner.add(s);
}
System.out.println(stringJoiner.toString());
}
}
输出:
Hello Alan,Join,Mike!
StringJoiner(分解符,前缀,后缀)
如果不需要指定开头和结尾,那么可以使用String.join()
public class Main {
public static void main(String[] args) {
String[] names = {"Alan","Join","Mike"};
String s = String.join(",",names);
System.out.println(s);
}
}
输出:
Alan,Join,Mike
String.join(分解符,字符串数组)