在我们创建字符串时,我们会这么写String str1=“abcd”,如果类比int a=1的话,那么我们思考一下:String是不是与int一样是一种数据类型?
答:不是!String是一种类!让我们认识一下String类吧!
一、构造字符串
String构造字符串有三种方法:直接赋值法、利用字符数组构造字符串、调用构造方法
1、直接赋值法:
public class Test1 {
public static void main(String[] args) {
//直接赋值法:
String str1="abc";
}
}
2、利用字符数组构造字符串:
public class Test1 {
public static void main(String[] args) {
//直接赋值法:
String str1="abc";
//利用字符数组构造字符串
char[] array={'a','b','c'};
String str2=new String(array);
System.out.println(str2);//打印str2
}
}
>>先将字符串的每一个字符存入字符数组中
>> 然后在实例化String类的时候,调用构造函数,传入array字符数组
看看打印出来的str2是否未abc:
3、 调用构造方法:
其实,方式2也调用了构造方法,唯一不同的是,其实在调用String类构造方法的时候,也可以直接传入字符串!
public class Test1 {
public static void main(String[] args) {
//直接赋值法:
String str1="abc";
//利用字符数组构造字符串
char[] array={'a','b','c'};
String str2=new String(array);
System.out.println("str2:"+str2);
//调用构造方法
String str3=new String("abc");
System.out.println("str3:"+str3);
}
}
看看打印出来的效果:
二、 String类中的方法
1、lenth方法
在有些时候,我们想要知道字符串的长度,那么就可以调用String类中的lenth方法!
int length(String str)
参数:String类的引用类型数据
功能:计算字符串内容的长度
返回值:
返回字符串的长度
让我们看看实例:
public class Test2 {
public static void main(String[] args) {
String str1="abcd";
//求字符串长度
System.out.println("str1:"+str1.length());
}
}
程序运行结果:
2、isEmpty方法
bollean isEmpty(String str)
参数:String类的引用类型数据
功能 :判断一个字符串是否未空字符
返回值:
为空:返回true
不为空:返回false
让我们看看实例:
已知str1为非空字符串,那么调用isEmpty方法时,方法判断该str1is not empty(不是空字符串),因此返回false
str2为空字符串,调用该方法时,方法判断该str2 is empty(是空字符串),因此返回true
public class Test2 {
public static void main(String[] args) {
String str1="abcd";//非空字符串
String str2="";//空字符串
String str3=null;//str3不指向任何对象
System.out.println("str1:"+str1.isEmpty());//false
System.out.println("str2:"+str2.isEmpty());//true
}
}
程序运行结果:
提问;str3不指向任何对象,那么调用该方法时会出现什么结果?
答:报错!
public class Test2 {
public static void main(String[] args) {
String str1="abcd";//非空字符串
String str2="";//空字符串
String str3=null;//str3不指向任何对象
System.out.println(str3.isEmpty());
}
}
3、equals方法
在正式介绍这个方法之前,我们一起来思考一个问题!
public class Test2 {
public static void main(String[] args) {
//情况1:
String str1="abc";
String str2="abc";
System.out.println(str1==str2);//打印结果是true or false?
//情况2:
String str3=new String("hello");
String str4=new String("hello");
System.out.println(str3==str4);//打印结果是true or false?
}
}
程序运行结果:
为什么会这样?
按照正常逻辑:
str1与str2指向的字符串内容相同,打印true没问题 !那么,str3与str4指向的字符串内容也相同,为什么返回false?
其实,这种逻辑是错误的,我们知道,str1、str2等都是引用数据类型,它们储存的内容都是一个地址,而不是字符串的内容。它们存储的是字符串的地址!
那么,按照这个逻辑推断:
str1与str2存储的地址是同一个地址!
str3与str4存储的地址不是同一个地址!
这是为什么?
这里涉及到字符串常量池的知识。
规定:只要是双引号引起来的字符串常量,都会存在一个字符串常量池中。
这个字符串常量池有独特的存储逻辑:
1、当要新存入一个字符串之前,会先检查这个内存(字符串常量池)有没有这个字符串?
2、如果没有,将这个字符串存进去
3、如果有,就不重复存储了
让我来示范一下情况1与情况2的内部原理:
情况1:
情况2:
通过解决这个问题,我们目前至少知道了一个事实:不能通过String类的引用数据类型(str1、str2等)来比较字符串内容是否相同!
那么新的问题来了:该用什么方法来比较字符串内容是否相同?
答:使用equals方法!
equals方法:
equals方法:
功能:比较字符串内容是否相同
返回值:
相同:返回true
不同:返回false
让我们看看实例:
public class Test3 {
public static void main(String[] args) {
String str1="abc";
String str2="abc";
String str3="a";
System.out.println("str1 and str2 "+str1.equals(str2));
System.out.println("str1 and str3 "+str1.equals(str3));
}
}
程序运行结果:
可以看见:
str1与str2指向的字符串内容相同:返回true
str1与str3指向的字符串内容不同:返回false
equalsIgnoreCase方法
bollean equlasIgnoreCase(String str)
参数:String类的引用类型数据
功能:比较字符串内容是否相同(忽略字母大小写)
返回值:
相同:返回true
不同:返回false
让我们看看实例:
public class Test3 {
public static void main(String[] args) {
String str1="ABC";//大写
String str2="abc";//小写
String str3="a";
System.out.println("str1 and str2 "+str1.equalsIgnoreCase(str2));
System.out.println("str1 and str3 "+str1.equals(str3));
}
}
程序运行:
可以看到,str1指向的字符串为“ABC”为大写;而str2指向的字符串为”abc“小写,调用这个方法返回的结果是true,可知:这个方法可以忽略大小写的区别!
4、compoTo方法
int compareTo(String str)
参数:String类的引用类型数据
功能:比较字符串的大小
返回值:
1、先按字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
2、如果前k个字符相等,返回值是两个字符串的长度差值(k为其中两个字符串中的最小长度)
让我们看看实例:
public class Test3 {
public static void main(String[] args) {
String str1="abc";
String str2="abc";
String str3="a";
String str4="abd";
//字符串"abc"与"abc"相同,返回0
System.out.println("str1 and str2 "+str1.compareTo(str2));
//字符串"abc"与字符串"a",前1个字符相同,输出长度差值2
System.out.println("str1 and str3 "+str1.compareTo(str3));
//字符串"abc"与字符串"abd",前两个字符相同,最后一个字符按照字典次序大小比较,差值为-1
System.out.println("str1 and str4 "+str1.compareTo(str4));
}
}
程序运行:
compareToIgnoreCase方法:
int compareTOIgnoreCase (String str)
参数:String类的引用类型数据
功能:与compareTo方法相同,不过新增加了忽略大小写的功能
5、charAt方法
charAt()是一个字符串查找方法!查找方法类似与数组访问!
char charAt(int index)
参数:数字下标
功能:输入下标,返回对应的字符
返回值:一个字符
让我们看看实例:
public class Test3 {
public static void main(String[] args) {
//依次打印出来str1中的每一个字符!!
String str1="abc";
char ch= str1.charAt(0);//"a"的下标为0
System.out.println(ch);
ch=str1.charAt(1);//"b"的下标为1
System.out.println(ch);
ch=str1.charAt(2);//"c"的下标为2
System.out.println(ch);
}
}
程序运行:
7、indexOf方法
charAt()方法是通过传入下标返回对应的字符。
而indexOf ()方法恰恰相反,通过传入字符返回下标!
int indexOf(int ch)
参数;可以传入整型数据,也可以传入一个字符,最终都会根据其ASCII码值寻找字符
功能:传入字符,返回其下标
返回值:
>>存在该字符:
返回该字符在字符串中的下标
>>不存在该字符:
返回一个负数
让我们来看看实例:
public class Test3 {
public static void main(String[] args) {
String str1="abcc";
//'a'的下标是0
int index=str1.indexOf('a');
System.out.println(index);
//'b'的下标是1
index=str1.indexOf('b');
System.out.println(index);
//'c'的下标是2
//注意:虽然有两个'c',但是由于这个方法的查找逻辑是从前往后,因此返回的下标是2,而不是3
index=str1.indexOf('c');
System.out.println(index);
}
}
程序运行:
另外,还存在两个参数的indexOf()方法!
int indexOf(int ch,int fromIndex)
参数;可以传入整型数据,也可以传入一个字符,最终都会根据其ASCII码值寻找字符
功能:传入字符,传入起始下标,从起始下标开始,从前往后查找字符
返回值:
>>存在该字符:
返回该字符在字符串中的下标
>>不存在该字符:
返回一个负数
让我们看看实例:
public class Test3 {
public static void main(String[] args) {
String str1="abcc";
//从下标为2的地方开始寻找'c',返回下标2
int index=str1.indexOf('c',2);
System.out.println(index);
//从下标为2的地方开始寻找'a',找不到,返回一个负数
index =str1.indexOf('a',2);
System.out.println(index);
}
}
程序运行结果:
同理:转大写的方法为toUpperCase方法
8、toLowerCase方法
这个方法的功能是将字符串中的大写字符转换为小写字符!
让我们看看例子:
public class Test3 {
public static void main(String[] args) {
String str1="AbcD";
String str2=str1.toLowerCase();
System.out.println(str2);
}
}
程序运行结果:
三、字符串类型转换
1、其他类型数据转换为字符串数据
public class Test3 {
public static void main(String[] args) {
//整型数据转换为字符串
String str1=String.valueOf(1234);
//浮点型数据转换为字符串
String str2=String.valueOf(12.34);
//bollean类型数据转换为字符串
String str3=String.valueOf(true);
System.out.println(str1);
System.out.println(str2);
System.out.println(str3);
}
}
程序运行结果:
、
2、字符串数据转换为数字类型数据
让我们看看实例:
public class Test3 {
public static void main(String[] args) {
//字符串转换为整型数据
int date1=Integer.parseInt("1234");
//字符串转换为浮点型数据
double date2=Double.parseDouble("12.34");
System.out.println(date1+1);
System.out.println(date2+1);
}
}
程序运行结果:
date1+1可以完成计算且不会报错,说明字符串"1234"已经成功转换为整型数据!
3、字符串转数组
有时候我们操作字符串可能不太方便,这个时候就可以将字符串转换为数组!
让我们来看看例子:
import java.util.Arrays;
import java.util.Locale;
public class Test3 {
public static void main(String[] args) {
String str1="AbcD";
//把字符串转为数组
char [] array=str1.toCharArray();
System.out.println(Arrays.toString(array));
}
}
程序运行结果:
四、字符串内容替换方法
1、replace方法:
String replace (char oldChar,char newChar)
功能:用新字符替换旧字符
参数:新字符 旧字符
返回:
返回一个String类的引用类型数据
让我们看看例子:
public class Test3 {
public static void main(String[] args) {
String str1="abab";
String str2=str1.replace('a','d');
System.out.println("str2:"+str2);
System.out.println("str1:"+str1);//不会修改之前的字符串内容
}
}
程序运行结果:
注意:这个字符串替换后,是不会修改原来的字符串“abab"的!
replace方法的重载方法:
除了用新字符替换旧字符,replace方法也有另外一个重载方法,它可以实现新字符串替换旧字符串!
String replace(String oldString,String newString)
参数:旧字符串 新字符串
功能:用新字符串替换旧字符串
让我们看看例子:
public class Test3 {
public static void main(String[] args) {
String str1="abcab";
String str2=str1.replace("ab","kkk");//用新字符串替换旧字符串
System.out.println(str2);
}
}
程序运行:
可以看到,字符串中的”ab"整体被替换为“kkk”
五、字符串分割
1、 split方法
假如我们有一个字符串"acb&dfde&dr"想要以特殊字符为分割线,分割数组,该怎么做?
答:调用split方法!
String [] split(String regex)
参数:分割字符
功能:根据传入的分割字符分割字符串,返回一个数组
返回值:String类的数组
让我们看看实例:
public class Test3 {
public static void main(String[] args) {
String str1="acb&dfde&dr";
//以"&"作为分割字符
String[] strings=str1.split("&");
//利用循环打印数组内容
for (String s:strings){
System.out.println(s);
}
}
}
程序运行:
注: 字符 | * , . + 作为分割字符时,前面要加上”\\"转义字符,否则无法分割
如果一个字符串里面有多种分割符,可以用|作为连接符,连接不同的分割符
public class Test3 {
public static void main(String[] args) {
String str1="acb&dfde<dr";
String[] strings=str1.split("&|<");
for (String s:strings){
System.out.println(s);
}
}
}
split的重载方法
String [] split (String regesx,int limit)
参数:分割字符 最多分的组数
功能:根据分割字符最多分割成limit组字符串
返回值:返回String类的数组
让我们看看例子:
public class Test3 {
public static void main(String[] args) {
String str1="acb&dfde&dr";
//分割字符串
String[] strings=str1.split("&",2);
//打印数组
for (String s:strings){
System.out.println(s);
}
}
}
程序运行:
对比之前的split方法,这个方法只将这串字符串分割为2组!
六、字符串截取
String substring(int beginIndex)
参数:传入起始下标
功能:从起始下标开始,截取自下标以后的字符串
返回值:返回字符串
让我们看看实例:
public class Test3 {
public static void main(String[] args) {
String str1="abcdef";
String str2=str1.substring(1);//从下标1开始截取
System.out.println(str2);
str2=str1.substring(2);//从下标2开始截取
System.out.println(str2);
}
}
程序运行:
substring的重载方法:
String substring(int beginIndex, int endIndex)
参数:起始下标 终止下标
功能:从起始下标开始截取字符串,直到终止下标前一个字符结束(左闭右开)
让我们看看实例:
public class Test3 {
public static void main(String[] args) {
String str1="abcdef";
String str2=str1.substring(1,3);//截取下标[1,3)左闭右开
System.out.println(str2);
str2=str1.substring(1,4);//截取下标[1,4)
System.out.println(str2);
}
}
程序运行:
七、 去除字符串空格
trim()方法是用来去除字符串前后空格的,不包括字符串中间的空格!
让我们直接看看例子:
public class Test3 {
public static void main(String[] args) {
String str1=" abcd ef ";
System.out.println(str1);
String str2=str1.trim();
System.out.println(str2);
}
}
程序运行结果:
如图,分别打印了str1和str1去除空格后的str2。
可以发现:该方法只去除了字符串前后的空格,而没有去除字符串中间的空格!
八、字符串的不可变性
在我们之前学习的方法过程中,以转换大小写为例子!
public class Test3 {
public static void main(String[] args) {
String str1="abc";
String str2=str1.toUpperCase();
System.out.println("str1: "+str1);
System.out.println("str2: "+str2);
}
}
在这个代码运行结果出来之前,我们可以思考一下:有两种可能!
猜想1:
这个toUpperCase()方法最终会返回一个字符串,返回的该字符串是str1指向的字符串。程序将str1指向的字符串内容修改后,再返回其字符串的地址。
猜想2:
程序重新产生了一个新的String类的对象,其内容为转换大写后的字符串!然后再返回这个全新的字符串。
看看程序运行的结果:
根据结果:我们得知:
1、str1指向的字符串内容没有改变!
2、toUpperCase方法重新产生了一个对象!猜想2正确!
前面介绍这么多!只为了引入现在的正题:字符串的不可变性 !
或许你会觉得奇怪,为什么不修改字符串的内容,而要重新生成一个新的对象?
说实话,不是toUpperCase方法不想改变字符串的内容 ,而是无法字符串的内容无法修改!
因为,字符串具有不可变性!
为什么不可变!我们一起来找找原因!
1、进入String类:
一个String类其中有两个成员变量,每当执行String str =“abc"的时候,这个字符串其实就是一个byte[ ]类型的数组value(第二个红色框)。
如图:
value 的内容是一个地址,这个地址指向字符串的地址!
那么:我们猜想,字符串的不可变性是不是因为这个value被final修饰呢?
因为我们知道,被final修饰的变量是不可改变的!
答:不是!
final修饰的value的内容是一个地址,不可改变的是value这个内容,而不是value指向的这个数组不可改变!
举个例子给大家看看!
public class Test3 {
public static void main(String[] args) {
byte [] value1=new byte[]{'a','b','c'};//创建一个byte类型的数组
final byte [] value2=new byte[]{'a','b','c'};//创建一个byte类型的数组(final修饰)
byte [] value3=new byte[]{'a'};
value1=value3;//(1)可以执行
value1[0]='c';//(2)可以执行
value2=value1;//(3)不可以执行,对比(1)(3):说明被final修饰的value2不可以修改它的内容
value2[0]=value1[0];//(4)可以执行,但是value2指向的数组的内容可以修改!
}
}
通俗点讲!value2就好像一个家庭地址!如果签订长期居住的合同(final修饰),这个家庭地址不可改变!但是家里面的家具等东西(数组)都是可以替换的!
直到这里!我们得知:字符串的不可变性不是由于这个byte数组被final修饰!
真正原因在于权限!
这个value是由private修饰的,因此在这个类之外无法直接访问value,同时String类中又没有getValue方法,无法在其它类中获取value,因此,字符串具有不可改变性!
九、StringBuilder类和StringBuffer类
关于字符串的类其实不只有String类,还有StringBuilder类和StringBuffer类!
但是使用它们构造字符串只能通过调用构造方法!
public class Test3 {
public static void main(String[] args) {
StringBuilder str1=new StringBuilder("abc");
StringBuilder str2=new StringBuilder("abc");
System.out.println(str1);
System.out.println(str2);
}
}
它们都有一个字符串拼接方法append()方法。这个方法会拼接两段字符串!并且返回一个字符串!
举个例子,顺便说说这两个类的拼接与Stirng类的字符串有什么不同!
与String类的字符串不同的是:StringBulider类和StringBuffer类的字符串的拼接不会再产生一个新的对象!它是在原来的字符串上修改字符串的内容!
append方法使用:
public class Test3 {
public static void main(String[] args) {
StringBuilder str1=new StringBuilder("hello");
StringBuilder str2=new StringBuilder("hello");
str1.append("world");
str2.append("world");
System.out.println("str1: "+str1);
System.out.println("str2: "+str2);
}
}
程序运行结果:
可以看到,这个append方法直接修改了原字符串的内容!