String
String
类型是一个引用类型,本质上是一个class
,Java对它提供了特殊的支持使得我们可以使用“…”的形式表示,如:
String s = "abc";
String
的内部通过一个char[]
数组存储字符串的各个字符:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
........
}
创建字符串
可以通过上面提过的"…"形式创建字符串:
String s = "abc";
也可以通过new
创建字符串:
String s3 = new String(new char[]{'H','e','l','l','o'});
String s4 = new String("Hello");
构造方法可以接受一个已有的字符串,也可以是一个char[]
数组,new
创建与“…”的区别在于是否会在堆栈区创建String
对象
- 通过"…"直接指定或通过
+
拼接字符串时,jvm会在常量池中查找,如果有则返回,没有就在常量池中创建 - 通过
new
创建时则一定会在堆栈中创建一个新的String
对象
intern
方法
intern
方法可以扩充常量池,当一个String
实例调用该方法时,如果常量池中存在与该实例值相同的字符串常量,则返回该常量的引用,否则创建一个与该实例值相同的字符串常量放入常量池并返回引用
String s1 = "Hello";
String s3 = new String(new char[]{'H','e','l','l','o'});
System.out.println(s1 == s3); // false
System.out.println(s3.intern() == s1); // true
System.out.println(s3.intern() == s3); // false
需要注意的是当常量池中不存在时会在常量池中重新创建字符串常量而不是直接将实例放入常量池,因此,s3.intern()
和s3
是不同的引用
String
的不可变性
在上面的源码中,我们可以看到在String
中保存的char[]
数组是final
的,同时在String
内部也没有提供任何修改value
的方法,这意味着一个String
是不可变的,我们在改变一个字符串的时候只能改变它的引用而不能它的值:
String s1 = "HELLO";
String s2 = s1;
System.out.println("s1="+s1+",s2="+s2); // s1=HELLO,s2=HELLO
s1 = "hello";
System.out.println("s1="+s1+",s2="+s2); // s1=hello,s2=HELLO
s1
和s2
指向相同的对象,可以看到s1
改变后,s2
并没有改变,通过idea等工具我们也可以看到在执行s1 = "hello";
后s1
的引用地址发生了变化
字符串长度
通过length
方法可以获得字符串的长度
String s = "hello";
int len = s.length() // 5
字符串判空
通过isEmpty()
方法可以判断是否为空字符串:
System.out.println("".isEmpty()); // true
去除首尾空白字符
trim()
方法可以去除包括空格、\n
,\t
、\r
等空白字符
String s = " hello world \n\t\r".trim()+"!"; // hello world!
但是它无法去除unicode
空格,例如\u3000
String s = " hello world \n\t\r\u3000".trim()+"!"; // hello world
!
字符串比较
在比较两个字符串是否相同时,要使用
equals
方法而不能使用==
通过==
比较
class
类型的数据在通过==
进行比较时实际上是在比较它们的引用是否相同
String s1 = "hello";
String s2 = "hello";
String s3 = "Hello";
String s4 = new String("hello");
System.out.println("s1==s2:"+(s1==s2)); // s1==s2:true
System.out.println("s1==s3:"+(s1==s3)); // s1==s3:false
System.out.println("s1==s4:"+(s1==s4)); // s1==s4:false
s1
、s2
和s3
的比较都符合预想,但是s1
和s4
比较就会存在问题,这是因为s1
,s2
指向常量池中的同一个字符串常量,自然引用也是相同的,而s4
通过new
关键字,会在堆栈中创建新的对象并获得它的引用,就和s1
不同了
通过equals
比较
equals
就是比较两个字符串的值是否相同
String s1 = "hello";
String s2 = "hello";
String s3 = "Hello";
String s4 = new String("hello");
System.out.println("s1 equals s2:"+s1.equals(s2)); // s1 equals s2:true
System.out.println("s1 equals s3:"+s1.equals(s3)); // s1 equals s3:false
System.out.println("s1 equals s4:"+s1.equals(s4)); // s1 equals s4:true
另外String
还提供了equalsIgnoreCase
方法可以忽略大小写比较:
s1.equalsIgnoreCase(s3); //true
通过compareTo
比较
compareTo
方法也是比较两个字符串的值
s1.compareTo(s3);
与equals
方法不同的是它返回一个int
类型,s1
大于s2
时返回大于零的值,s1
等于s2
时返回零,s1
小于s2
时返回小于零的数,这个方法主要用于排序
compareTo
也有对应的忽略大小写版本compareToIgnoreCase
:
s1.compareToIgnoreCase(s3)
字符串搜索
1、 contains
contains
方法搜索字符串中是否存在子串:
s.contains("el");
注意: contains
方法只接受CharSequence
类型,这是一个接口,Stirng
实现了这个接口,不能传入单个字符
2、indexOf
indexOf
方法从前向后查找子串,找到后返回第一个匹配子串的下标,参数允许字符和字符串,同时可以增加一个fromIndex
参数表示从第几位开始查找
String s = "hello world!hello world!";
System.out.println(s.indexOf('l')); // 搜索字符,返回正序第一个的下标,输出2
System.out.println(s.indexOf('l',5)); // 搜索字符,从第几位开始向后查找,输出10
System.out.println(s.indexOf("rl")); // 搜索子串,返回正序第一个的下标,输出9
System.out.println(s.indexOf("rl",10)); // 搜索子串,从第几位开始向后查找,输出21
3、lastIndexOf
lastIndexOf
与indexOf
基本相同,区别在于它从后向前查找子串
String s = "hello world!hello world!";
System.out.println(s.lastIndexOf('l')); // 搜索字符,返回正序第一个的下标,输出22
System.out.println(s.lastIndexOf('l',20)); // 搜索字符,从第几位开始向后查找,输出16
System.out.println(s.lastIndexOf("rl")); // 搜索子串,返回正序第一个的下标,输出21
System.out.println(s.lastIndexOf("rl",19)); // 搜索子串,从第几位开始向后查找,输出9
需要注意的是,indexOf
方法中的fromIndex
表示从第几位开始向后查,而在lastIndexOf
方法中表示从第几位开始向前查
4、startWith
startWith
查找字符串是否以子串开头,可以增加toffset
参数表示第几位开始以子串开头
String s = "hello world!hello world!";
System.out.println(s.startsWith("hel")); //true
System.out.println(s.startsWith("o",4)); //true
5、endWith
endWith
查找字符串是否以子串结尾,只接受一个字符串参数
System.out.println(s.endsWith("!")); //true
字符串提取
1、charAt
charAt
传入下标,返回下标对应的字符
String s = "hello world!";
System.out.println(s.charAt(1)); // e
2、substring
substring
传入起始下标和结束下标,返回对应范围的子串,如果只传入一个参数,就返回该下标开始到字符串结束为止组成的子串
System.out.println(s.substring(1));
System.out.println(s.substring(1,3));
3、subSequence
API Note:
This method is defined so that the String class can implement theCharSequence
interface
subSequence
方法和substring
类似,但是它必须传入两个参数,不能省略结束下标,另外,substring
方法返回String
类型而subSequence
返回一个CharSequence
类型。根据JDK文档,这个方法只是为了实现CharSequence
接口,其行为与substring
相同,源码中也可以看出:
public CharSequence subSequence(int beginIndex, int endIndex) {
return this.substring(beginIndex, endIndex);
}
由于它返回的对象实际是String
类型,因此可以强制转化为String
String s1 = (String) s.subSequence(1,3); // el
替换子串
字符串替换子串主要有两种方式:
- 根据字符或字符串替换
- 根据正则表达式替换
1、replace
replace
将字符串中对应的字符或字符串全部转换为新的字符或字符串
String s = "Hello,my name is coco,I'm 18 years old.";
s.replace('o','k'); // "Hellk,my name is ckck,I'm 18 years kld.",将所有的'o'变为'k'
s.replace("co","k"); // "Hello,my name is kk,I'm 18 years old.", 将所有的'co'变为'k'
2、replaceAll
replaceAll
根据提供的正则表达式将符合的字符串全部转换为新的字符串
String s = "Hello,my name is coco,I'm 18 years old.";
s.replaceAll("\\d","4"); // "Hello,my name is coco,I'm 44 years old.",将所有数字转换为4
3、replaceFirst
replaceFirst
同样使用正则表达式,但是它只转换第一个匹配的字符串
String s = "Hello,my name is coco,I'm 18 years old.";
s.replaceFirst("\\d","k"); // "Hello,my name is coco,I'm k8 years old."
4、大小写转换
String
还提供了toUpperCase
和toLowerCase
方法,通过它可以便利地转换大小写:
String s = "Hello,my name is coco,I'm 18 years old.";
s.toUpperCase(); // HELLO,MY NAME IS COCO,I'M 18 YEARS OLD.
s.toLowerCase(); // hello,my name is coco,i'm 18 years old.
**注意:**替换方法返回一个替换后的新的字符串,不会修改原来的字符串
字符串分割
split()
通过split()
方法可以将字符串分割为字符串数组,需要一个正则表达式作为参数
String s = "1,2,3,4,5.5,6.6";
String[] ss = s.split(","); // [1, 2, 3, 4, 5.5, 6.6]
String[] ss1 = s.split("[,.]"); // [1, 2, 3, 4, 5, 5, 6, 6]
还可以增加一个limit
参数表示最多分为几份,分割不完的部分会放在最后一个元素里
String[] ss2 = s.split("[,.]",3); // [1, 2, 3,4,5.5,6.6]
如果limit
参数小于0,则与不加相同
String[] ss3 = s.split("[,.]",-1); // [1, 2, 3, 4, 5, 5, 6, 6]
toCharArray()
toCharArray()
方法可以将一个字符串分割为一个字符数组
char[] chars = s.toCharArray();
字符串拼接
String
类提供的静态方法join()
可以实现拼接多个字符串,它用指定的字符串连接字符串数组
String[] ss = new String[]{"abc","def","gh"};
String.join(",",ss); // abc,def,gh
它还支持变长参数:
String.join(",","hello","world"); // hello,world
字符串格式化
String
提供了静态方法format
实现字符串格式化,通过传入参数代替占位符生成新的字符串,常用的占位符包括:
%d
:整数%s
:字符串%f
:浮点数%x
:十六进制整数
String.format("I'm %d %s",18,"years old");
String.format("%x",1234);
String.format("%f",3.3);
还可以指定输出格式,例如:
String.format("%6.2f",2.34345)); // 2.34,字符串长度至少为6位,缺少的用空格代替,并取两位小数
String.format("%06d",2345)); // 002345,字符串长度至少为6位,缺少的用0代替
类型转换
1、其他类型转字符串
通过String
的valueOf
静态方法可以将其他类型转换为String
类型
基本类型
String.valueOf(true); // true
String.valueOf(12); // 12
String.valueOf(3.4); // 3.4
字符数组
valueOf
可以将char[]
数组的部分组合为字符串,需要提供起始位置和长度,不提供则组合整个数组
String.valueOf(new char[]{'1','2','3','4','5','6'}); // 123456
String.valueOf(new char[]{'1','2','3','4','5','6'},1,3); // 234
其他数组
除了char[]
以外的其他数组通过valueOf
将会获得其类型以及16进制的哈希值
String.valueOf(new int[]{1,23,4}); // [I@1b6d3586,表示int数组,哈希为1b6d3586
引用类型
以一个Cat
类为例
class Cat{
private String name;
public Cat(String name) {
this.name = name;
}
}
如果该类型没有toString()
方法,则返回其类型以及16进制的哈希值
String.valueOf(new Cat("cc"); // string.Cat@4554617c
如果有就调用toString()
方法
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
String.valueOf(new Cat("cc")); // Cat{name='cc'}
valueOf
的实现
除了boolean
和char[]
以外,valueOf
的实现都是在调用toString()
方法
引用类型如果有toString()
方法则调用toString()
,没有则会查找父类的toStirng()
方法,如果有就调用,没有就再向上查找,直至Object
类,Object
的toString()
方法为:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
这也就是上文中Cat
类没写toString()
方法时输出的内容
需要注意的是数组也是引用类型,而且没有重写toString()
,因此也是调用的Object
类的toString()
方法
基本类型会调用对应的包装类的toString()
方法,例如int
对应的Integer
:
public static String toString(int i) {
if (i == Integer.MIN_VALUE)
return "-2147483648";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
boolean
由于只有两种值,因此直接在valueOf
中进行了输出:
public static String valueOf(boolean b) {
return b ? "true" : "false";
}
而char[]
数组则是调用的String
类型的构造方法
public static String valueOf(char data[], int offset, int count) {
return new String(data, offset, count);
}
2、字符串转其他类型
字符串转其他类型可以通过其他类型提供的对应方法,例如Integer
类型:
int a = Integer.parseInt("12");
double
类型:
double b = Double.parseDouble("12.3");
例外的是char[]
类型可以通过String
提供的toCharArray()
获得,在 字符串分割 中提到过
参考
字符串和编码 - 廖雪峰的官方网站 (liaoxuefeng.com)