目录
1. Java中为何要有 String 类?
2. String 类中的常用方法
2.1 String 类中的构造方法
2.2 String 类对象的比较
2.2.1 == 比较是否引用同一个对象
2.2.2 使用 equals 方法
2.2.3 compareTo 方法
2.3 字符串的查找
2.4 字符串与别的数据类型的转换
2.4.1 数值和字符串的转换
2.4.2 大小写转换
2.4.3 字符串转换成数组
2.4.4 字符串格式化
2.5 字符串的替换
2.6 字符串的拆分
2.7 字符串的截取
2.8 trim——去掉字符串开头和结尾的空白字符
2.9 字符串的不变性
2.10 字符串的修改
2.10.1 StringBuilder
2.11 面试题
1. Java中为何要有 String 类?
在C语言中已经涉及到字符串了,但是在C语言中要表示字符串只能使用字符数组或者字符指针,可以使用标准库提 供的字符串系列函数完成大部分操作,但是这种将数据和操作数据方法分离开的方式不符合面相对象的思想,而字符串应用又非常广泛,因此Java语言专门提供了String类。
2. String 类中的常用方法
2.1 String 类中的构造方法
String 类的构造方法有很多,下面介绍常用的三种:
public class Text {
public static void main(String[] args) {
//使用常量字符串构造
String s = "She is a beauty";
System.out.println(s);
//直接 new String 对象
String s1 = new String("Wow!");
System.out.println(s1);
//使用字符数组进行构造
char[] ch = {'S','O','S'};
String s2 = new String(ch);
System.out.println(s2);
}
}
需要注意的是:
1. 由于 String 是引用类型,内部并不存储字符串本身,在 String 类的实现源码中,String 类实例变量如下:
可见,字符串实际是保存在字符数组中的。
public class Text {
public static void main(String[] args) {
String s1 = new String("We'll meet again");
String s2 = new String("The oak tree");
String s3 = s1;
}
}
对于上述代码中,s1 和 s2 引用的是不同的对象,而 s1 和 s3 引用的是相同的对象:
2. 在 Java 中,使用双引号 " " 引起来的,也是 String 的对象
System.out.println("try everything".length());
2.2 String 类对象的比较
字符串的比较是常见操作之一,比如:字符串排序。Java中总共提供了4中方式:
2.2.1 == 比较是否引用同一个对象
注意:对于基本数据类型,== 比较的是变量中的值;对于引用类型,== 比较的是引用中的地址。
public static void main(String[] args) {
String s1 = new String("We'll meet again");
String s2 = new String("We'll meet again");
String s3 = new String("The oak tree");
String s4 = s1;
System.out.println(s1 == s2);
System.out.println(s1 == s4);
System.out.println(s2 == s3);
}
输出
false
true
false
2.2.2 使用 equals 方法
String类重写了父类 Object 中 equals 方法,Object 中 equals 默认按照 == 比较,String 重写equals 方法后,按照如下规则进行比较,比如: s1.equals(s2)
public boolean equals(Object anObject) {
// 1. 先检测this 和 anObject 是否为同一个对象比较,如果是返回true
if (this == anObject) {
return true;
}
// 2. 检测anObject是否为String类型的对象,如果是继续比较,否则返回false
if (anObject instanceof String) {
// 将anObject向下转型为String类型对象
String anotherString = (String)anObject;
int n = value.length;
// 3. this和anObject两个字符串的长度是否相同,是继续比较,否则返回false
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
// 4. 从前往后逐个字符进行比较,只要有一个不同,就返回false
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
下面看一个简单的例子,了解一下 String 类 equals 方法吧 :
public static void main(String[] args) {
String s1 = new String("oops");
String s2 = new String("oops");
String s3 = new String("Oops");
System.out.println(s1 == s2);
System.out.println(s2 == s3);
System.out.println(s1.equals(s2));
System.out.println(s2.equals(s3));
}
输出
false
false
true
false
但如果是 equalsIgnoreCase 方法,则可以忽略大小写
System.out.println(s2.equalsIgnoreCase(s3));
输出
true
2.2.3 compareTo 方法
与 equals 不同的是,equals 返回的是 boolean 类型,而 compareTo 返回的是 int 类型。具体比较方式:
1. 先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
2. 如果前 k 个字符相等( k 为两个字符长度最小值),返回值两个字符串长度差值
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
// 1. 先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
if (c1 != c2) {
return c1 - c2;
}
k++;
}
//2. 如果前 k 个字符相等( k 为两个字符长度最小值),返回值两个字符串长度差值
return len1 - len2;
}
下面看一下一个简单的例子:
public static void main(String[] args) {
String s1 = new String("oops");
String s2 = new String("oops");
String s3 = new String("oops!");
String s4 = new String("Oops");
System.out.println(s1.compareTo(s2));
System.out.println(s2.compareTo(s3));
System.out.println(s3.compareTo(s2));
System.out.println(s2.compareTo(s4));
System.out.println(s3.compareTo(s4));
System.out.println(s2.compareToIgnoreCase(s4));
}
输出
0
-1
1
32
32
0
可见,对于完全相同的字符串,返回 0 ,对于前 k 个字符相同的字符串,输出字符串长度之差,有正负之分,而在前 k 个字符中,存在不同的字符,则根据字典序返回差值。
同样对于 compareToIgnoreCase 方法而言,忽略大小写
2.3 字符串的查找
字符串查找也是字符串中非常常见的操作,String类提供的常用查找的方法:
方法 | 功能 |
char charAt(int index) | 返回index位置上字符,如果index为负数或者越界,抛出 IndexOutOfBoundsException异常 |
int indexOf(int ch) | 返回ch第一次出现的位置,没有返回-1 |
int indexOf(int ch, int fromIndex) | 从fromIndex位置开始找ch第一次出现的位置,没有返回-1 |
int indexOf(String str) | 返回str第一次出现的位置,没有返回-1 |
int indexOf(String str, int fromIndex) | 从fromIndex位置开始找str第一次出现的位置,没有返回-1 |
int lastIndexOf(int ch) | 从后往前找,返回ch第一次出现的位置,没有返回-1 |
int lastIndexOf(int ch, int fromIndex) | 从fromIndex位置开始找,从后往前找ch第一次出现的位置,没有返 回-1 |
int lastIndexOf(String str) | 从后往前找,返回str第一次出现的位置,没有返回-1 |
int lastIndexOf(String str, int fromIndex) | 从fromIndex位置开始找,从后往前找str第一次出现的位置,没有返 回-1 |
见以下例子:
public static void main(String[] args) {
String s = "ACDEFGHIJKABCDLMNOPQRBCDSTGHUVWSAYZ";
System.out.println(s.charAt(15));
System.out.println(s.indexOf('B'));
System.out.println(s.indexOf('B',12));
System.out.println(s.indexOf("BCD"));
System.out.println(s.indexOf("BCD",20));
System.out.println(s.lastIndexOf('A'));
System.out.println(s.lastIndexOf('A',25));
System.out.println(s.lastIndexOf("ABC"));
System.out.println(s.lastIndexOf("GH",15));
}
输出
M
11
21
11
21
32
10
10
5
2.4 字符串与别的数据类型的转换
2.4.1 数值和字符串的转换
1. 数字转换成字符串
public static void main(String[] args) {
String s1 = String.valueOf(1234567);
String s2 = String.valueOf(1234.567);
String s3 = String.valueOf(true);
String s4 = String.valueOf('A');
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
}
输出
1234567
1234.567
true
A
2. 字符串转换成数字
public static void main(String[] args) {
int a = Integer.parseInt("12345");
double b = Double.parseDouble("12.66");
System.out.println(a);
System.out.println(b);
}
2.4.2 大小写转换
public static void main(String[] args) {
String s1 = "knock out";
System.out.println(s1.toUpperCase());
String s2 = "K.O.";
System.out.println(s2.toLowerCase());
}
2.4.3 字符串转换成数组
1. 字符串转数组
public static void main(String[] args) {
String s1 = "ooooooo";
char[] ch = new char[s1.length()];
ch = s1.toCharArray();
for (int i = 0; i < s1.length(); i++) {
System.out.print(ch[i]+" ");
}
}
输出
o o o o o o o
2. 数组转换成字符串
利用上述的例子,把 ch 这个数组重新转换成字符串:
String s2 = new String(ch);
System.out.println(s2);
2.4.4 字符串格式化
public static void main(String[] args) {
String s1 = String.format("%d-%d-%d",2022,11,29);
System.out.println(s1);
}
输出
2022-11-29
2.5 字符串的替换
使用一个指定的新的字符串替换掉已有的字符串数据,可用的方法如下:
方法 | 功能 |
String replaceAll(String regex, String replacement) | 替换所有的指定内容 |
String replaceFirst(String regex, String replacement) | 替换首个内容 |
public static void main(String[] args) {
String s = "aapphsaaaueaaauaaaks";
System.out.println(s.replaceAll("a","x"));
System.out.println(s.replaceFirst("pp","XXX0000xxxx"));
}
输出
xxpphsxxxuexxxuxxxks
aaXXX0000xxxxhsaaaueaaauaaaks
需注意的是:
由于字符串是不可变对象, 替换不修改当前字符串, 而是产生一个新的字符串。
2.6 字符串的拆分
可以将一个完整的字符串按照指定的分隔符划分为若干个子字符串,方法如下:
方法 | 功能 |
String[] split(String regex) | 将字符串全部拆分 |
String[] split(String regex, int limit) | 将字符串以指定的格式,拆分为limit组 |
public static void main(String[] args) {
String s = "What do you do for a living?";
String[] ret = s.split(" ");
for(String ss : ret){
System.out.print(ss+" ");
}
}
输出
What do you do for a living?
字符串还可以部分拆分:
public static void main(String[] args) {
String s = "Judy❤Nick❤They live together and forever!❤❤~~";
String[] ret = s.split("❤",3);
for(String ss : ret){
System.out.println(ss);
}
}
输出
Judy
Nick
They live together and forever!❤❤~~
拆分是特别常用的操作,一定要重点掌握。另外有些特殊字符作为分割符可能无法正确切分, 需要加上转义。
比如拆分IP地址时,遇到的 . ,这个符号就需要转义。
public static void main(String[] args) {
String s = "192.452.135.1";
String[] ret = s.split("\\.");
for(String ss : ret){
System.out.println(ss);
}
}
输出
192
452
135
1
需要注意的是:
1. 字符"|","*","+","."都得加上转义字符,前面加上 "\\"
2. 而如果是 "\" ,那么就得写成 "\\\\"
3. 如果一个字符串中有多个分隔符,可以用"|"作为连字符
public static void main(String[] args) {
String s = "192\\452\\135\\1";
System.out.println(s);
String[] ret = s.split("\\\\");
for(String ss : ret){
System.out.println(ss);
}
String s1 = "192*452*135*1";
System.out.println(s1);
String[] ret1 = s1.split("\\*");
for(String ss1 : ret1){
System.out.println(ss1);
}
}
输出
192\452\135\1
192
452
135
1
192*452*135*1
192
452
135
1
2.7 字符串的截取
从一个完整的字符串之中截取出部分内容。可用方法如下:
方法 | 功能 |
String substring(int beginIndex) | 从指定索引截取到结尾 |
String substring(int beginIndex, int endIndex) | 截取部分内容,左闭右开 |
public static void main(String[] args) {
String s = "helloBite!";
String ret = s.substring(5);
String ret1 = s.substring(0,5);
System.out.println(ret1);
System.out.println(ret);
}
输出
hello
Bite!
2.8 trim——去掉字符串开头和结尾的空白字符
public String trim(),其功能为去掉字符串中的左右空格,保留中间空格
public static void main(String[] args) {
String s = " I'm still fighting! ";
System.out.println("["+s.trim()+"]");
}
输出
[I'm still fighting!]
2.9 字符串的不变性
String是一种不可变对象,字符串中的内容是不可改变。为什么这么说呢?
1. String类在设计时就是不可改变的,String类实现描述中已经说明了
进入 String 类中,我们就可以看到:
String 这个类,被 final 修饰,同时成员变量 value 也被 final 修饰,这意味着:
1. String 类被 final 修饰,表明该类不能被继承
2. value 被 final 修饰,表明 value 自身的值不能改变,这个值就是字符数组的地址。这个地址不能变,意味着不能引用其它字符数组,但是其引用空间中的内容可以修改。
2. 所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象
拿 substring 方法举例:
可以看到,起始为 0 时, 返回的是同一字符串的地址,但如果起始不为 0 ,则会重新 new 一个String 对象 ,这时,就是另一个不同的地址了。
为什么 String 要设计成不可变的?(不可变对象的好处是什么?)
1. 方便实现字符串对象池。如果 String 可变,那么对象池就需要考虑写时拷贝的问题了
2. 不可变对象是线程安全的
3. 不可变对象更方便缓存 hash code,作为 key 时可更高效的保存到 HashMap 中
那如果想要修改字符串中内容,该如何操作呢?这就是下一节将要介绍的内容。
2.10 字符串的修改
学到这里,给大家一个建议,我们最好尽量避免直接对 String 类型对象进行修改,因为 String 类是不能修改的,所有的修改都会创建新对象,效率非常低下。
由于String的不可更改特性,为了方便字符串的修改,Java 中又提供 StringBuilder 和 StringBuffer类。这两个类大 部分功能是相同的,这里介绍 StringBuilder 常用的一些方法
2.10.1 StringBuilder
可以看到,StringBuilder 也是一个被 final 修饰的类。与 String 类不同的是,StringBuilder 实例的对象是可以修改的:
public static void main(String[] args) {
StringBuilder str = new StringBuilder("abcdefg ");
System.out.println(str);
str.reverse();
System.out.println(str);
str.append(12345).append(" hijkl ").append(14.25);
System.out.println(str);
}
输出
abcdefg
gfedcba
gfedcba12345 hijkl 14.25
查找一下 append 方法的具体实现就会发现,返回值为当前对象。
再看一看 reverse 方法的具体实现,返回值也是当前对象
与上面的例子相结合,可以知道 String 和 StringBuilder 最大的区别在于 String 的内容无法修改,而 StringBuilder 的内容可以修改。频繁修改字符串的情况考虑使用 StringBuilder 。
String和StringBuilder类不能直接转换。如果要想互相转换,可以采用如下原则:
1. String 变为 StringBuilder : 利用 StringBuilder 的构造方法或 append() 方法
2. StringBuilder 变为 String : 调用 toString() 方法
问题来了,请问 StringBuilder 和 StringBuffer 有什么区别呢?
让我们查看 StringBuffer 类中的具体实现,发现除构造方法外,其他的所有方法都有一个 synchronized 修饰,比如:
synchronized 可以理解成锁。在多线程情况下,使用 StringBuffer 安全性更高,但相对 StringBuilder 来说,效率更低,耗费更多资源。所以在需要频繁修改字符串的情况下,单线程选择 StringBuilder ,多线程选择 StringBuffer。
2.11 面试题
1. String、StringBuffer、StringBuilder 的区别
· String 的内容不可修改,StringBuffer 与 StringBuilder 的内容可以修改.
· StringBuffer 与 StringBuilder 大部分功能是相似的
· StringBuffer 采用同步处理,属于线程安全操作;而 StringBuilder 未采用同步处理,属于线程不安全操作
以下总共创建了多少个String对象【前提不考虑常量池之前是否存在】
String str = new String("ab"); // 会创建多少个对象
String str = new String("a") + new String("b"); // 会创建多少个对象
答:
2 5