目录
JDK中String类的声明
引言
1. 创建字符串的四种方式。
字符串的字面量
2. 字符串的比较相等
不区分大小写的比较equalsIgnoreCase()方法
3. 关于字符串的常量池问题
当使用直接赋值法产生字符串对象时,JVM会维护一个字符串的常量池。
手工入池: String类提供的intern()方法。
4. 字符串的不可变性
5. 如何修改字符串的内容
5.1 StringBuilder类,修改对象的内容
6. 字符串的其他常见操作
6.1 字符串比较
6.2 字符和字符串的相互转换
6.3 字符串和字节的相互转换
6.4 字符串查找
6.5 字符串替换操作
6.6 字符串的拆分操作 split()
6.7 字符串的截取处理 substring()
6.8 其他的常用方法
问题:为何String类被final修饰?
答:被final修饰的类无法被继承,String类不存在子类。
这样的话就可以保证所有使用JDK的人用到的String类仅此一个,大家都相同。
假设现在String允许继承,每个人都可以继承String类,修改其方法等实现。MyString1和MyString2、MyString3都是String完全不同的子类,都可以向上转型变为String类。
String str1 = new MyString1(); String str1 = new MyString2(); String str1 = new MyString3();
继承和方法覆写在带来灵活性的同时,也会带来很多子类行为不一致所导致的问题。
什么时候会用到final修饰类
这个类不希望有别的版本,到此为止。所有使用者用的这个类完全相同,没有别的实现。
方式一:直接赋值
String str = "hello world";
方式二:通过构造方法产生对象
String str2 = new String("hello world");
方式三:通过字符数组产生对象
char[] data = new char[] {'a','b','c'}; String str = new String(data);
方式四:通过String的静态方法valueOf(任意数据类型)——>转为字符串
String str = String.valueOf(10);
字面量:直接写出来的数值就称之为字面量
10 ——> int字面量
10.1 ——> double字面量true ——> boolean字面量
"abc"——> String字面量,就是一个字符串的对象。
String引用数据类型。
String str = "hello world";——>字符串字面量,也是字符串的对象。
//2. 字符串字面量,也是字符串的对象。 String str = "hello world"; String str1 = str; str1 = "Hello"; System.out.println(str); //输出结果 hello world
"Hello”也是字符串的字面量,是一个新的字符串对象。
str1实际上指向了新的字符串对象"Hello",str仍旧指向原字符串对象"hello world"。
所以修改str1的内容并没有导致str的内容被修改。
引用数据类型在比较是否相等时,使用equals()方法比较。
JDK常用类,都已经覆写了equals方法,可以直接使用。如:String,Integer。
引用数据类型 使用“=="比较的仍然是数值(地址是否相等)。
字符串比较相等
String str1 = "hello"; String str2 = "hello"; System.out.println(str1 == str2); //输出结果 true
此处str1与str2并不是内容相同,只是说明str1和str2指向了相同的地址空间(原因在本文后续介绍)。
String str3 = new String("hello"); String str4 = new String("hello"); System.out.println(str3 == str4); //输出结果 false
此处相等证明了str3与str4的地址不同。
String str3 = new String("hello"); String str4 = new String("hello"); System.out.println(str3.equals(str4)); //输出结果 true
equals方法比较str3与str4的内容,相等返回true。
不区分大小写的比较equalsIgnoreCase()方法
String str3 = new String("hello"); String str4 = new String("Hello"); System.out.println(str3.equals(str4)); //false System.out.println(str3.equalsIgnoreCase(str4)); //true //输出结果 false true
牵扯到用户输入就一定要做判空处理。
因为我们要比较的特定内容——"张三",本身就是字符串的字面量,一定不是空对象。
若用户忘记输入userName,会产生空指针异常。
把要比较的内容"张三"放在equals的前面,可以方便处理userName为null的问题。
当使用直接赋值法产生字符串对象时,JVM会维护一个字符串的常量池。
a. 若该对象在堆中还不存在,则产生一个新的字符串对象加入字符串的常量池中。
b. 当继续使用直接赋值法产生字符串对象时,JVM发现该引用指向的内容在常量池中已经存在了,则此时不再新建字符串对象,而是复用已有对象。
直接赋值创建对象
3个引用指向了相同的内存。
构造方法创建对象
3个引用指向了不同的内存。
常量池设计的原因
所谓的"池"都是类似的思想——共享设计模式,节省空间(内存是一个非常紧俏的资源)。
字符串产生之后大部分情况都是用来进行输出处理,即打印内容。所以"hello"只有一个就行了。
调用intern()方法会将当前字符串引用指向的对象保存到字符串常量池中。
a. 若当前常量池中已经存在了该对象,则不再产生新的对象,返回常量池中的String对象。
b. 若当前常量池中不存在该对象,则将该对象入池,返回入池后的地址。
题目1:求输出结果
//str1指向堆中普通的字符串对象 String str1 = new String("hello"); str1.intern(); String str2 = "hello"; System.out.println(str1 == str2);
答:false。
1. "hello"就是字符串字面量。第一次出现,由JVM产生此字符串对象并且保存到常量池中。
2. new String在堆中产生—个普通的字符串对象,值从常量池中拷贝过来。
3. str2 = "hello"; 复用常量池中的"hello"。
如此修改即可输出true
题目2:求输出结果
char[] date = {'a', 'b', 'c'}; String str1 = new String(date); str1.intern(); String str2 = "abc"; System.out.println(str1 == str2);
答:true
1. new String(date); 只是创建了字符串数组,没有创建字符串对象,常量池没有保存对象。
2. String str1; 此时内存池还没任何字符串常量,str1是new出来的所以不入池,只在堆上产生了一个普通的字符串对象,值是由字符数组data赋值来的。
3. str1.intern; 将普通字符串对象移动入池,在常量池中保存"abc"。
4. str2复用常量池的"abc"。
故str1与str2指向同一个地址。
题目3:求输出结果
public class Example{ String str = new String("good"); char[ ] ch = { 'a' , 'b' , 'c' }; public static void main(String args[]){ Example ex = new Example(); ex.change(ex.str,ex.ch); System.out.print(ex.str + " and "); System.out.print(ex.ch); } public void change(String str,char ch[ ]){ str = "test ok"; ch[0] = 'g'; } }
答:good and gbc
只是修改了形参str的指向,将其指向了"test ok",实参仍指向"good" 。而形参ch修改ch[0] = g,实参的值也被修改了。
所谓的字符串不可变指的是字符串对象的内容不能变,而不是字符串引用不能变。
String str = "hello"; str = str + "world"; str += "!"; System.out.println(str);
这里的不可变指的是,例如:"hello"、"world"、"helloworld"、"!"、"helloworld!",这些字符串对象一旦声明后就无法修改其内容。
问题:为何字符串的对象无法修改内容而其他类的对象能修改内容?
字符串其实就是一个字符数组——>char[],字符串保存的值实际上在数组中保存。此处数组用private修饰,String外部无法访问这个value数组,String并没有提供关于value属性的getter()和setter()方法。对于String类的外部而言,value完全无法使用。因此字符串对象的内容无法被修改,String类的外部拿不到这个value数组。
a. 在运行时通过反射破坏value数组的封装(了解,不推荐)
b. 更换使用StringBuilder或者StringBuffer类——与String已经不是一个类型了。
若需要频繁进行字符串的拼接,使用StringBuilder类的append()方法,StringBuilder类可以修改对象的内容。StringBuffer使用方法和StringBuilder完全—样,线程安全,性能较差。
若需要频繁进行字符串的拼接,使用StringBuilder类的append()方法。
package string_test; public class StringBuilder { public static void main(String[] args) { java.lang.StringBuilder sb = new java.lang.StringBuilder(); sb.append("hello"); sb.append("world"); sb.append("!"); System.out.println(sb); } } //输出结果 helloworld!
关于StringBuilder类的具体使用:
StringBuilder类和String类是两个独立的类。因为String的对象无法修改内容,为了方便字符串的拼接操作,产生了StringBuilder类,StringBuilder类的对象是可以修改内容的。
String类转换为StringBuilder类的两种方法://方法1 StringBuilder sb = new StringBuilder("hello"); //方法2 sb.append("hello");
StringBuilder类转换为String类的方法:
StringBuilder sb = new StringBuilder("hello"); sb.append("123"); String str = sb.toString(); System.out.println(str); //输出结果 hello123
因为StringBulider类可以修改内容,除了拼接append方法外,还具备一些String类不具备的修改内容的功能:
a. 字符串反转操作,StringBuilder类提供的reverse();StringBuilder sb = new StringBuilder("hello"); sb.reverse(); System.out.println(sb); //输出结果 olleh
b. 删除指定范围的数据,delete(int start,int end):删除从start索引开始end之前的所有内容。[start,end),左闭右开。
StringBuilder sb = new StringBuilder("helloworld"); sb.delete(5,10); System.out.println(sb); //输出结果 world
c. 插入操作,insert(int start,各种数据类型):将新元素插入当前sb对象,插入后新数值的起始索引为:start。
StringBuilder sb = new StringBuilder("world"); sb.insert(3,999); //999的起始索引为:3 System.out.println(sb); //输出结果 wor999ld
小结:
请解释String、StringBuilder、StringBuffer的区别:
1.String的对象无法修改,俩SB的对象内容可以修改。
2.StringBuffer是线程安全的操作,性能较差;StringBuilder是线程不安全,性能较高。
本节3个重点:
a. 关于字符串常量池以及intern()方法的理解问题。
b. 理解字符串不可变的意义。
c. StringBuilder的使用。要使用String类,就采用直接赋值的方式。要比较内容是否相等使用equals()方法。
public int compareTo():比较两个字符串大小关系
也实现了Comparable接口,覆写字符串的compareTo()方法,按照字符串内部的每个
compareTo()方法,对数组进行ASClI的比较。String str1 = "abc"; String str2 = "ABC"; System.out.println(str1.compareTo(str2)); //输出结果 32
'a' - 'A' = 97 - 65 = 32。
按照"字典序"排列字符串,就是按照字符串内部的字符的ASCII码大小排序,A(65)就要出现在a(97)之前。
字符串的内部实际上就是使用字符数组来存储的。
字符转字符串
1. 通过构造方法将字符转换为字符串。
char[] ch = new char[]{'a', 'b', 'c'}; String str = new String(ch); System.out.println(str); //输出结果 abc
2. public String(char value[],int offset,int count):将字符数组的部分内容转为字符串对象
char[] ch = new char[]{'a', 'b', 'c'}; String str = new String(ch); String str = new String(ch,1,2); //1:字符数组开始的索引 2:转换字符个数 System.out.println(str); //输出结果 bc
字符串转字符
1. public char charAt(int index):取出字符串中指定索引的字符
String str1 = "hello"; System.out.println(str1.charAt(1)); //输出结果 e
2. public char toCharArray():将字符串中的内容转为字符数组
String str1 = "hello"; System.out.println(str1.charAt(1)); char[] ch = str1.toCharArray(); System.out.println(ch); //输出结果 hello
问题1:若此时改变字符数组的内容会不会影响到字符串对象的内容?
String str1 = "hello"; System.out.println(str1.charAt(1)); char[] ch = str1.toCharArray(); ch[0] = 'H'; System.out.println(ch); System.out.println(str1); //输出结果 hello
此时产生了一个新的字符数组ch,将字符串str1的内容复制过去,更改ch[0] = 'H'。str1的内容没有被改变。
String对象不可变!内容改不了!问题2:如何判断—个字符串的对象是由纯数字组成的?
"123" => true (数字型字符串,由纯数字组成)
123a" = > false(不是由纯数字组成的字符串)// 传入一个String对象,判断是否由纯数字组成 public static boolean isNumber(String str) { // "123" => 转为字符数组去处理 // 1.str => char[] char[] data = str.toCharArray(); // 2.循环遍历data中的每个字符,判断这个字符是否是数字字符 // ['0' ... '9'] for (char c : data) { // // 找到反例,此时字符c不是数字字符 // if (c < '0' || c > '9') { // return false; // } if (!Character.isDigit(c)) { return false; } } return true; } public static void main(String[] args) { String str1 = "123"; String str2 = "123a"; System.out.println(isNumber(str1)); System.out.println(isNumber(str2)); } //输出结果 true false
Character.isDigit(c):判断字符c是否是数字返回true或者false
在处理一个逻辑需要返回true或者false,思路就是在循环中找反例,有一个反例直接return false。
将字符串保存到文件中或是通过网络传输都要用到字节数组。
byte——>String
public String():将字节数组转变为字符串
byte[] date = new byte[]{97,98,99}; String str = new String(date); System.out.println(str); //输出结果 abc
String——>byte
public byte getBytes():按照当前默认的字符编码将字符串转为字节数组
String str = "你好世界"; byte[] date = str.getBytes(); System.out.println(Arrays.toString(date)); //输出结果 [-28, -67, -96, -27, -91, -67, -28, -72, -106, -25, -107, -116]
public byte getBytes(String charsetName) throws UnsupportedEncodinaException:按照指定的编码格式转为字节数组。
public static void main(String[] args)throws Exception { String str = "你好世界"; byte[] date = str.getBytes(); byte[] date1 = str.getBytes("gbk"); //按照GBK编码将字符串转为字节数组 System.out.println(Arrays.toString(date)); System.out.println(Arrays.toString(date1)); } //输出结果 [-28, -67, -96, -27, -91, -67, -28, -72, -106, -25, -107, -116] [-60, -29, -70, -61, -54, -64, -67, -25]
在UTF-8编码下,一个汉字3个字节;在GBK编码下,一个汉字2个字节。
public boolean contains():判断是否包含一个子字符串
public boolean startsWith():判断是否以指定字符串开头
public boolsan endsWith():判断是否以指定字符串结尾
public static void main(String[] args) { String str1 = "hello world"; System.out.println(str1.contains("world")); System.out.println(str1.startsWith("hello")); System.out.println(str1.startsWith("hello1")); System.out.println(str1.endsWith("world")); System.out.println(str1.endsWith("world1")); } //输出结果 true true false true false
问题:替换操作是否会修改原字符串的内容?
答:String类的所有针对字符串的操作方法都不会修改原字符串,而是产生了一个新的字符串。字符串的不可变性。
public String replaceAll():替换所有的指定内容
public String replaceFirst():替换首个内容public static void main(String[] args) { String str = "helloworld"; System.out.println(str.replaceAll("l", "_")); System.out.println(str.replaceFirst("l", "_")); } //输出结果 he__owor_d he_loworld
public String split(String regex):将字符串全部拆分
public String split(String regex,int limit):将字符串部分拆分,该数组长度就是limit极限
public static void main(String[] args) { String str = "hello world hello China"; String[] str1 = str.split(" "); System.out.println(Arrays.toString(str1)); String[] str2 = str.split(" ",2); System.out.println(Arrays.toString(str2)); } //输出结果 [hello, world, hello, China] [hello, world hello China]
拆分IP地址
String str = "192.168.1.1"; String[] str1 = str.split("."); System.out.println(Arrays.toString(str1)); //输出结果 []
若字符串中没有指定拆分的子串,拆分后仍然得到原字符串
a. 指定格式在字符串中不存在,例如 "." 。
String str = "192.168.1.1"; String[] str1 = str.split("-"); System.out.println(Arrays.toString(str1)); System.out.println(str1.length); //输出结果 [192.168.1.1] 1
str1长度为1,说明是原字符串。
b. 指定格式是个特殊字符,需要转义处理,例如 "\\." 。
String str = "192.168.1.1"; String[] str1 = str.split("\\."); System.out.println(Arrays.toString(str1)); System.out.println(str1.length); //输出结果 [192, 168, 1, 1] 4
str1长度为4,说明原字符串被拆分。
public String substring(int beginIndex):从指定索引截取到结尾
public String substring(int beginlndex,int endIndex):截取部分内容[beginlndex,endIndex),左闭右开
public static void main(String[] args) { String str = "helloworld"; System.out.println(str.substring(5)); System.out.println(str.substring(0,5)); } //输出结果 world hello
public String trim():普通去掉字符串中的左右空格,保留中间空格
public String toUpperCase():字符串转大写
public String toLowerCase():字符串转小写public int length():取得字符串长度
public static void main(String[] args) { String str1 = " hello "; //只会去掉str1的左右空格 System.out.println(str1.trim()); System.out.println("hello".toUpperCase()); System.out.println("HELLO".toLowerCase()); System.out.println("Hello".length()); } //输出结果 hello HELLO hello 5
public boolean isEmpty():普通判断是否为空字符串,但不是null,而是长度为0
问题:若String str = null; 此时还能否使用str.isEmpty);不能:成员方法,只能判断字符串的长度是否为0,不能判断null。
public static void main(String[] args) { String str = "abc"; String str1 = "a"; String str2 = ""; System.out.println(upperFirstCase(str)); System.out.println(upperFirstCase(str1)); System.out.println(upperFirstCase(str2)); } public static String upperFirstCase(String str) { //判空处理 if (str == null || str.isEmpty()) { return null; } //边界条件 if (str.length()==1){ str.toUpperCase(); return str; } //length>1 //截取 + 大写 return str.substring(0,1).toUpperCase() + str.substring(1); } //输出结果 Abc a null
如有Bug,望请指正,感谢浏览。