Java中的String类——万字详解

目录

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 其他的常用方法


JDK中String类的声明

引言

问题:为何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修饰类
这个类不希望有别的版本,到此为止。所有使用者用的这个类完全相同,没有别的实现。

1. 创建字符串的四种方式。

方式一:直接赋值

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";——>字符串字面量,也是字符串的对象。

Java中的String类——万字详解_第1张图片


//2. 字符串字面量,也是字符串的对象。
String str = "hello world";
String str1 = str;
str1 = "Hello";
System.out.println(str);
//输出结果
hello world

"Hello”也是字符串的字面量,是一个新的字符串对象。

str1实际上指向了新的字符串对象"Hello",str仍旧指向原字符串对象"hello world"。

所以修改str1的内容并没有导致str的内容被修改。

Java中的String类——万字详解_第2张图片

2. 字符串的比较相等

引用数据类型在比较是否相等时,使用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

Java中的String类——万字详解_第3张图片

牵扯到用户输入就一定要做判空处理。 

因为我们要比较的特定内容——"张三",本身就是字符串的字面量,一定不是空对象。

若用户忘记输入userName,会产生空指针异常。

把要比较的内容"张三"放在equals的前面,可以方便处理userName为null的问题。

3. 关于字符串的常量池问题

当使用直接赋值法产生字符串对象时,JVM会维护一个字符串的常量池。

a. 若该对象在堆中还不存在,则产生一个新的字符串对象加入字符串的常量池中。

b. 当继续使用直接赋值法产生字符串对象时,JVM发现该引用指向的内容在常量池中已经存在了,则此时不再新建字符串对象,而是复用已有对象


直接赋值创建对象

Java中的String类——万字详解_第4张图片

3个引用指向了相同的内存。

Java中的String类——万字详解_第5张图片


构造方法创建对象

Java中的String类——万字详解_第6张图片

3个引用指向了不同的内存。 

Java中的String类——万字详解_第7张图片


常量池设计的原因

所谓的"池"都是类似的思想——共享设计模式,节省空间(内存是一个非常紧俏的资源)。

字符串产生之后大部分情况都是用来进行输出处理,即打印内容。所以"hello"只有一个就行了。

手工入池: String类提供的intern()方法。 

调用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"。

Java中的String类——万字详解_第8张图片

如此修改即可输出true

Java中的String类——万字详解_第9张图片


题目2:求输出结果

char[] date = {'a', 'b', 'c'};
String str1 = new String(date);
str1.intern();
String str2 = "abc";
System.out.println(str1 == str2);

答:true

Java中的String类——万字详解_第10张图片

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,实参的值也被修改了。

4. 字符串的不可变性

所谓的字符串不可变指的是字符串对象的内容不能变,而不是字符串引用不能变。

String str = "hello";
str = str + "world";
str += "!";
System.out.println(str);

这里的不可变指的是,例如:"hello"、"world"、"helloworld"、"!"、"helloworld!",这些字符串对象一旦声明后就无法修改其内容。

Java中的String类——万字详解_第11张图片


问题:为何字符串的对象无法修改内容而其他类的对象能修改内容?
字符串其实就是一个字符数组——>char[],字符串保存的值实际上在数组中保存。

Java中的String类——万字详解_第12张图片

此处数组用private修饰,String外部无法访问这个value数组,String并没有提供关于value属性的getter()和setter()方法。对于String类的外部而言,value完全无法使用。因此字符串对象的内容无法被修改,String类的外部拿不到这个value数组。

Java中的String类——万字详解_第13张图片

5. 如何修改字符串的内容

a. 在运行时通过反射破坏value数组的封装(了解,不推荐)
b. 更换使用StringBuilder或者StringBuffer类——与String已经不是一个类型了。
若需要频繁进行字符串的拼接,使用StringBuilder类的append()方法,StringBuilder类可以修改对象的内容。

StringBuffer使用方法和StringBuilder完全—样,线程安全,性能较差

5.1 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!

Java中的String类——万字详解_第14张图片


关于StringBuilder类的具体使用:
StringBuilder类和String类是两个独立的类。因为String的对象无法修改内容,为了方便字符串的拼接操作,产生了StringBuilder类,StringBuilder类的对象是可以修改内容的。

Java中的String类——万字详解_第15张图片
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()方法。

6. 字符串的其他常见操作

6.1 字符串比较

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)之前。

6.2 字符和字符串的相互转换

字符串的内部实际上就是使用字符数组来存储的。

字符转字符串

1. 通过构造方法将字符转换为字符串。

Java中的String类——万字详解_第16张图片

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。

6.3 字符串和字节的相互转换

将字符串保存到文件中或是通过网络传输都要用到字节数组。

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个字节。

6.4 字符串查找

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

6.5 字符串替换操作

问题:替换操作是否会修改原字符串的内容?

答: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

6.6 字符串的拆分操作 split()

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,说明原字符串被拆分。

6.7 字符串的截取处理 substring()

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

6.8 其他的常用方法

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,望请指正,感谢浏览。

你可能感兴趣的:(JavaSE,java,开发语言)