- String类专门用来表示字符串类型
- 字符串构造的主要三种方法【学习一个类,先学习他的构造方法】
public class TestDemo1 {
public static void main(String[] args) {
String s1 = "Hello";
String s2 = new String("Hello");
char[] array = {'H','e','l','l','o'};
String s3 = new String(array);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
}
}
对于第三种方法,传入的是字符数组,转变成字符串,对应源码:
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
- String是引用类型,内部并不存储字符本身,可以看到String类一个字符串对象里面包含2个成员变量,其中value是字符数组类型,字符串实际保存在char类型的数组中
那么上述s1和s2是如何存储的呢?
- 字符串长度以及字符串是否为空
public static void main(String[] args) {
String s1 = "Hello";
System.out.println(s1.length());
System.out.println("Hello".length());
String s2 = "";
System.out.println(s2.isEmpty());
String s3 = null;
System.out.println(s3.isEmpty());
}
运行结果:
分析:①字符串长度的比较调用的是length()方法,而获取数组的长度是 int[] array = {1,2,3}; System.out.println(array.length);
,一个有小括号,一个没有小括号,并且在Java中没有所谓的’\0’。
②在Java中双引号“”引起来的也是String类型对象,也是字符串对象,因此可以直接使用"Hello".length()
,可以看到点号.前面是个常量,因此不一定非得是变量调用字符串方法。
③s2字符串的长度为0,因此调用isEmpty()得到的返回值为true
④s3赋值为null,表示不指向任何对象,因此会抛出空指针异常
注意:长度为0和指向null,这两种空是不一样的。
- String对象的比较
①==比较是否引用同一个对象
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
String s3 = s1;
String s4 = new String("abc");
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s1 == s4);
}
运行结果:
分析:双引号引起来的值就存在字符串常量池当中,如果常量池中有abc就再重新存一份,直接返回字符串常量池中的对象即可。
对于基本类型变量,= =比较两个变量中存储的值是否相同;对于引用类型变量, = =比较两个引用变量引用的是否为同一个对象。
②boolean equals(Object anObject)方法比较对象内容是否相同:按字符比较
String类重写了父类Object中equals方法,Object中的quals默认按照==比较,String重写equals方法后,则可以比较不同对象的内容是否相同,如果相同也可以返回true。
public static void main(String[] args) {
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
String类重写equals代码:
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、从前往后逐个字符进行比较
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
运行结果:
如果要忽略大小写进行比较,使用equalsIgnoreCase()方法:
public static void main(String[] args) {
String s1 = new String("abc");
String s2 = new String("Abc");
System.out.println(s1.equalsIgnoreCase(s2));
}
运行结果:
③int compareTo(String s)方法比较字符串对象内容的大小:按照字典序进行比较
注意:equals返回的是boolean类型,而compareTo返回的是int类型。
比较方法:先按照字典次序大小比较,如果出现不相等的字符,则返回两个字符的ASCII大小差值;如果两个字符串的前K个字符都相等(K是两个字符串长度中的最小值),那么返回两个字符串长度的差值。
public static void main(String[] args) {
String s1 = new String("abcd");
String s2 = new String("abcd");
String s3 = new String("abc");
System.out.println(s1.compareTo(s2));
System.out.println(s1.compareTo(s3));
}
运行结果:
s1和s2字符串内容相同,所以返回0,s1和s3前3个字符相同,s1比s3长度多1,所以返回1.
忽略大小写比较:s1.compareToIgnoreCase(s2)
public static void main(String[] args) {
String s1 = new String("abcd");
String s2 = new String("Abcd");
System.out.println(s1.compareTo(s2));
System.out.println(s1.compareToIgnoreCase(s2));
}
运行结果:
- 字符串查找
①char charAt(int index) 返回index位置上字符
public static void main(String[] args) {
String s = "abcdf";
System.out.println(s.charAt(2));
}
运行结果:
分析:s.charAt(2)
返回下标为2的那个字符
②int indexOf(int ch) 返回ch第一次出现的位置,没有ch则返回-1
public static void main(String[] args) {
String s = "abcdf";
System.out.println(s.indexOf('b'));
System.out.println(s.indexOf('h'));
}
运行结果:
分析:
字符b在字符串中的下标是1,而字符串中没有h字符,所有第二条输出语句打印-1。
③int indexOf(int ch, int fromIndex) 从fromIndex位置开始找ch第一次出现的位置,没有则返回-1
public static void main(String[] args) {
String s = "abcdfasdfghsfdadbf";
System.out.println(s.indexOf('b',6));
}
运行结果:
分析:indexOf传入2个参数,则是从第6个位置开始找b字符
④int indexOf(String str) 【不仅可以找字符,还可以找字符串】返回str第一次出现的位置,没有返回-1
public static void main(String[] args) {
String s = "abcdfasdfghsfdadbf";
System.out.println(s.indexOf("dfg"));
}
运行结果:
分析:在字符串s中第一次出现子串dfg的下标位置是7
⑤int indexOf(String str, int fromIndex) 从fromIndex位置开始找str第一次出现的位置,没有返回-1
public static void main(String[] args) {
String s = "abcdfasdfghsfdadfgbf";
System.out.println(s.indexOf("dfg",9));
}
运行结果:
分析:从下标为9的位置开始找dfg
上述是从前往后找也可以从后往前找
⑥int lastIndexOf(int ch) 从后往前找,返回ch第一次出现的位置
public static void main(String[] args) {
String s = "abcdfasdfghsfdadfgbf";
System.out.println(s.lastIndexOf('b'));
}
运行结果:
⑦int lastIndexOf(int ch, int fromIndex)从fromIndex位置开始找,从后往前找ch第一次出现的位置,没有返回-1
public static void main(String[] args) {
String s = "abcdfasdfghsfdadfgbf";
System.out.println(s.lastIndexOf('b',6));
}
运行结果:
分析:从下标为6的位置开始,从后往前找
⑧int lastIndexOf(String str)从后往前找,返回str子串第一次出现的位置,没有返回-1
public static void main(String[] args) {
String s = "abcdfasdfghsfdadfgbf";
System.out.println(s.lastIndexOf("dad"));
}
运行结果:
⑨int lastIndexOf(String str, int fromIndex)从fromIndex位置开始找,从后往前找str第一次出现的位置,没有返回-1
public static void main(String[] args) {
String s = "abcdfasdfghsfdadfgbf";
System.out.println(s.lastIndexOf("dad",10));
}
运行结果:
- 字符串与数值转化
①数值/对象转换成字符串 ——用String类的valueOf方法
class Student{
}
public class TestDemo2 {
public static void main(String[] args) {
int a = 10;
double b = 12.3;
String s1 = String.valueOf(a);
String s2 = String.valueOf(b);
String s3 = String.valueOf(new Student());
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
}
}
运行结果:
②字符串转为基本数据类型数值——利用基本数据类型对应的包装类中的方法
public static void main(String[] args) {
String s1 = "123";
String s2 = "12.34";
int a = Integer.parseInt(s1);
double b = Double.parseDouble(s2);
System.out.println(s1);
System.out.println(s2);
}
运行结果:
分析:parseInt和parseDouble两个方法都由static修饰,所以可以直接通过包装类名调用,而不用实例化对象。
- 字符串大小写转换
public static void main(String[] args) {
String s1 = "ABDFGSSDFsdfdgh";
String s2 = s1.toLowerCase();
String s3 = s1.toUpperCase();
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
}
}
运行结果:
分析:s1.toLowerCase()
将字符串中的大写字母全部转换成小写字母,s1.toUpperCase()
将字符串中的小写字母全部转换成大写字母,并且可以发现s1中字符串的内容是不变的(String类的不可变性)。
- 数组和字符串之间的转换
①将数组转换成字符串,构造字符串对象,new String的时候传入字符数组进行构造
public static void main(String[] args) {
char[] chars = {'a','b','c'};
String s = new String(chars);
System.out.println(s);
}
②将字符串转换成数组,用到了String类中的toCharArray()方法
public static void main(String[] args) {
String s = "hello";
char[] chars = s.toCharArray();
for (char i:chars) {
System.out.println(i);
}
}
运行结果:
- 字符串的格式化,用到String类中的format方法
public static void main(String[] args) {
String s = String.format("%d-%d-%d",2022,11,21);
System.out.println(s);
}
运行结果:
- 字符串替换
使用一个指定的新的字符串替换掉已有的字符串数据
replace的四种方法:
public static void main(String[] args) {
String s = "abcdabd";
String s1 = s.replace('a','x');
String s2 = s.replace("abc","ooo");
String s3 = s.replaceFirst("ab","00");
String s4 = s.replaceAll("ab","00");
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
}
运行结果:
注意:由于字符串是不可变对象,所以替换不修改当前字符串,而是产生一个新的字符串。
- 字符串拆分
将一个完整的字符串按照指定的分隔符划分成为若干个字符串
String[] split(String regex)将字符串全部拆分
String[] split(String regex, int limit)将字符串以指定的格式,拆分为limit组
注意:返回值类型是String[],字符串数组类型。
例如:以空格分割字符串,得到字符串数组
public static void main(String[] args) {
String str = "Hello World Shuai Wang";
String[] strings = str.split(" ");
for (String x: strings) {
System.out.println(x);
}
}
运行结果:
在上述例子中,可以给split方法再添加一个参数,指定拆分成几组。
public static void main(String[] args) {
String str = "Hello World Shuai Wang";
String[] strings = str.split(" ",2);
for (String x: strings) {
System.out.println(x);
}
}
运行结果:分成2组
例子:拆分IP地址,有些特殊字符作为分隔符可能无法正确拆分,需要加上转义。
public static void main(String[] args) {
String str = "192.168.0.1";
String[] strings = str.split("\\.");
for (String x:strings) {
System.out.println(x);
}
}
运行结果:
分析:此处以点号.进行分割,需要加2个反斜杠,第一个反斜杠表示:点号.需要转义;第二个反斜杠表示:刚刚那个反斜杠需要转义。(如果没有加反斜杠,则没有打印结果)
注意:
①字符“|”,“*”,“+”都需要加上转义字符,前面加上“\”【2个斜杠才能表示1个斜杠】
②如果是“\”,而需要写成“\\”【4个斜杠才能表示2个斜杠】
③如果一个字符串中有多个分隔符,可以用“|”作为连字符
例子:多次拆分
①用到连字符|
public static void main(String[] args) {
String str = "name=zhangsan&age=21";
String[] strings = str.split("=|&");
for (String x:strings) {
System.out.println(x);
}
}
②用到for循环多次拆分
public static void main(String[] args) {
String str = "name=zhangsan&age=21";
String[] strings = str.split("=");
// name zhangsan&age 21
for (int i = 0; i < strings.length; i++) {
//对数组的每个元素再进行分割
String[] ret = strings[i].split("&");
//对数组每个元素分割之后进行打印
for (String x: ret) {
System.out.println(x);
}
}
}
运行结果:
- 字符串截取
从一个完整的字符串里面截取出部分内容
public static void main(String[] args) {
String str = "abcdsasffsa";
String s1 = str.substring(2);
String s2 = str.substring(2,7);
System.out.println(s1);
System.out.println(s2);
}
运行结果:
分析:
String substring(int beginIndex)表示从指定索引截取到结尾
String substring(int beginIndex, int endIndex)表示截取某个区间,截取部分内容,区间为左闭右开。
- 观察trim()方法的使用
trim会去掉字符串开头和结尾的空白字符(空格、换行、制表符等),保留中间空格
public static void main(String[] args) {
String str = " Hello World ";
String s1 = str.trim();
System.out.println(str);
System.out.println(s1);
}
运行结果:
- 字符串的不可变性
String是一种不可变对象,字符串中的内容是不可改变的,字符串不可被修改。
其原因是:
并不是因为String类被final修饰所以字符串不可被修改。被fianl修饰只能说明String类不可以被继承。
真正的原因是:value被final修饰,而且权限是private。value被final修饰,表明value自身的值是不能改变的,即它引用的数组对象的地址是不能改变的,但是它引用的数组对象里面的内容还是可以修改的。又因为被private修饰,所以无法在类外进行修改其引用的数组对象里面的内容。因此String不可变。
public static void main(String[] args) {
final int[] array = {1,2,3,4};
array = new int[6];
array[0] = 6;
}
代码中,array数组被final修饰,代表array引用所指向的对象地址不能改变,所以 array = new int[6];
会报错,而其对象的内容可以修改,例如 array[0] = 6;
可以编译运行成功。
即:final修饰类表示该类不想被继承,final修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象中的内容是可以修改的。
- 字符串修改:拼接过程中会产生新的临时对象。
public static void main(String[] args) {
String str = "Hello";
str += "World";
System.out.println(str);
}
观察其反汇编程序
可以发现其实际代码为:
public static void main(String[] args) {
String str = "Hello";
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(str);
stringBuilder.append("World");
str = stringBuilder.toString();
System.out.println(str);
}
分析:这种字符串拼接的方式不推荐使用,因为其效率非常低,中间创建了很多临时对象。因此:尽量避免对String的直接修改,如果需要修改尽量使用StringBuffer和StringBuilder。
- StringBuffer和StringBuilder
由于String的不可更改特性,为了方便字符串的修改,Java中提供了StringBuffer和StringBuilder类。
①常用方法——append()
public static void main(String[] args) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("abc").append("hello");
stringBuilder.append("World");
stringBuilder.append(123);
System.out.println(stringBuilder);
}
append方法的源码:
public StringBuilder append(String str) {
super.append(str);
return this;
}
可以看到return的是this,返回的就是当前对象,此时不会产生临时对象。
②常用方法——toString() 将所有的字符转换成String类型并返回
public static void main(String[] args) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("abc").append("hello");
stringBuilder.append("World");
stringBuilder.append(123);
String str = stringBuilder.toString();
System.out.println(str);
}
toString的源码:
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
分析:可以发现toString创建了一个新的String对象。
- String和StringBulider/StringBuffer类的互相转换
①String类变成StringBulider:通过append方法或者StringBulider的构造方法
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("abc").append("hello");
stringBuilder.append("World");
②StringBulider变成String:通过调用StringBulider的toString()方法
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("abc");
String str = stringBuilder.toString();
System.out.println(str);
-
String、StringBulider、StringBuffer的区别
①String的内容不可修改,StringBulider和StringBuffer的内容可以修改
②StringBulider和StringBuffer大部分的功能是相似的
③StringBuffer采用同步处理,属于线程安全操作;StringBuilder未采用同步处理,属于线程不安全操作
分析:synchronized(锁)用在多线程情况下,所以StringBuffer更安全。【但是频繁的加锁和释放锁都是需要耗费系统资源的】 -
以下总共创建了多少个String对象【不考虑常量池之前是否存在】
String str = new String("ab");
String str = new String("a") + new String("b");
分析:第一条语句产生2个String对象,第二条语句产生5个String对象+1个StringBulider对象
①“ab”双引号引起来的是一个字符串对象,new String
new了一个字符串对象,所以一个2个
②“a”和"b"2个双引号引起来,则产生了2个字符串对象,2个new则new了2个字符串对象,+字符串拼接会产生一个StringBulider对象,StringBulider对象最后调用toString方法会new一个String对象,所以会产生5个String对象,1个StringBulider对象,总共6个对象。