【Java SE】String类详解

发布于:2025-03-29 ⋅ 阅读:(32) ⋅ 点赞:(0)

参考笔记:java String类 万字详解(通俗易懂)-CSDN博客

本文可以和我的另一篇博客 "Java中String的内存原理" 结合一起看:【Java SE】Java中String的内存原理-CSDN博客


目录

1.基本介绍

2.String类的final关键字和value属性

3.String类的继承关系图、常用的构造器

3.1 继承关系图

3.2 常用的构造器

4.不同方式创建String类对象

4.1 直接赋值方式

 4.2 new方式赋值

4.3 其他赋值方式

5.String类常用方法

5.1 判断功能的方法

5.2 获取功能的方法

5.3 转换功能的方法

6.String类的不可变性(非常重要)

6.1 直接修改字符串

6.2 在方法中修改字符串


1.基本介绍

String 是引用数据类型,用于保存字符串,也就是一组字符序列。例如:"你好"、"12.97"、"boy"等

字符串的字符使用 Unicode 字符编码,一个字符(不区分字母还是汉字)占两个字节

2.String类的final关键字和value属性

JavaString 类部分源码如下:

① String 类用 final 关键字修饰,因此 String 类是不可被其他类继承的

String 类中的 value 属性及其源码描述,可以得知 String 类底层实际是一个字符数组char[ ] 。即 new 出的 String 对象在堆空间中,该对象的空间里有 value 属性,用于存放字符串的内容(实际指向的是字符串常量池地址)

案例:String str = new String("小马")

可以看到,字符串对象 str 的 value[0] = '小',value[1] = '马'

内存原理图

value 数组的访问控制修饰符是 private ,意味着外部无法直接获取该字符数组,所以在 的例子是无法使用 "str.value" 获取的,而且 String 没有提供 valuegetset 方法

value 数组使用 final 关键字修饰,而 value 的数据类型是 char[ ],即引用数据类型,所以用 final 关键字修饰表示 value 指向的地址是不可变的,但指向的地址存放的内容可变

结论:由于    的共同作用,Java 中的 String 具有不可变性,简单来说就是在字符串常量池中对于每个字符串常量,其内容一直不会被修改

只看文字描述不好理解,在 "6.String类的不可变性" 会举例子帮助理解

3.String类的继承关系图、常用的构造器

3.1 继承关系图

蓝色实线:继承extends

绿色虚线:实现implements

String 类除了继承 Object 类之外,还实现了 3 个接口。只需要记住关键的两个接口:

① Serializable接口:实现该接口使得 String 类型可串行化,串行化后,String 类型可以进行网络传输

② Comparable接口:实现该接口使得 String 类型的对象可以进行“比较”的操作

3.2 常用的构造器

String 类的所有构造器如下,本文只讲几个常用的:

① String():

        该构造器初始化一个 String 对象,使其指向一个空字符序列

 ② String(byte[] bytes):

        该构造器初始化一个 String 对象,并将指定字节数组中的数据转化成字符串

③ String(char[] value):

        该构造器初始化一个 String 对象,并将指定字符数组中的数据转化成字符串

④ String(char[] value, int offset, int count):

        该构造器初始化一个 String 对象,并将指定字符数组从 offset 下标开始的 count 个字符转化成字符串

⑤ String(String original):

        该构造器初始化一个 String 对象,使该对象的字符串内容与 original 相同

案例演示

public class demo {
    public static void main(String[] args) {
        //演示 : String类的常用构造器
        //1 —— String()
        String str_0 = new String();
        System.out.println(str_0);
        System.out.println("--------------------");

        //2 —— String(byte[] bytes)
        byte[] bytes = {65, 66, 67, 68};   //65~68对应ASCII码表A~D
        String str_1 = new String(bytes);
        System.out.println(str_1);
        System.out.println("--------------------");

        //3 —— String(char[] value)
        char[] value = {'唱', '跳', 'r', 'a', 'p', '篮', '球'};
        String str_2 = new String(value);
        System.out.println(str_2);
        System.out.println("--------------------");

        //4 —— String(char[] value, int offset, int count)
        String str_3 = new String(value, 0, 4);
        System.out.println(str_3);
        System.out.println("--------------------");

        //5 —— String(String original)
        String str_4 = new String("ftt!");
        System.out.println(str_4);
    }
}

4.不同方式创建String类对象

4.1 直接赋值方式

String 类在实际开发中的使用场景非常多,因此 Java 在底层提供了针对 String 类的优化,即可以不通过构造器来创建 String 对象,而是直接赋值一个字符串

eg : String str = "CYC";使用这种方式来创建 String 对象,JVM 会先在字符串常量池中查看是否有 "CYC" 字符串的数据空间:

        ① 若有,直接将空间地址返回给 str

        ② 若无,在字符串常量池种开辟 "CYC" 的数据空间,并将空间地址返回给 str

因此,直接赋值方式下,无论是还是 str 引用最终直接指向的都是字符串常量池中 "CYC" 的空间地址

内存原理图

 4.2 new方式赋值

String 类的 value 属性:private final char value[ ]

eg : String str = new String("CYC");

使用这种方式来创建 String 对象,JVM 会先在堆中给 String 对象开辟空间,这片空间中维护了 value 数组这一属性。然后 JVM 在字符串常量池中查看是否有 "CYC" 字符串的数据空间:

        ① 若有,直接将空间地址返回给 value

        ② 若无,先在字符串常量池种开辟 "CYC" 的数据空间,然后将空间地址返回给 value

因此,new 方式赋值下,无论是  还是 str 引用直接指向的都是在堆中开辟的空间地址value 引用最终指向的都是字符串常量池中 "CYC" 的空间地址

内存原理图

4.3 其他赋值方式

其他赋值方式和内存原理图可以看我之前写的一篇博客的 "4.3 new和直接赋值混合"、"4.4 字符串拼接" 部分:

【Java SE】Java中String的内存原理-CSDN博客

5.String类常用方法

String 类中的方法非常多,本文仍然是只讲常用的

5.1 判断功能的方法

① boolean equals(Object anObject) :判断两个字符串的内容是否相同

源码如下,还是比较简单的,我也作了注释

public boolean equals(Object anObject) {
    if (this == anObject) {//如果引用地址相同,那肯定不用说,字符串内容肯定也相同
        return true;
    }
    
    if (anObject instanceof String) {//判断传进来的anObject是否是String类型
        String anotherString = (String)anObject;//向下转型
        int n = value.length;
        if (n == anotherString.value.length) {//判断两个字符串的长度是否相等,不相等肯定返回false
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {//一个字符一个字符作比较
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

② boolean equalsIgnoreCase(String anotherString):判断两个字符串的内容是否相同,不区分大小写

③ boolean startsWith(String prefix):判断当前字符串是否以 prefix 开头

④ boolean isEmpty():判断当前字符串是否为空

案例

public class demo {
    public static void main(String[] args) {
        //演示 : String常用方法之判断功能的方法
        //1 —— boolean equals(Object anObject)
        String str_0 = "cyan";
        System.out.println("\"Cyan\"字符串与str_0字符串的内容相等吗?" + "Cyan".equals(str_0));
        System.out.println("================");

        //2 —— boolean equalsIgnoreCase(String anotherString)
        System.out.println("\"Cyan\"字符串与str_0字符串的内容相等吗?" + "Cyan".equalsIgnoreCase(str_0));
        System.out.println("================");

        //3 —— boolean startWith(String prefix)
        System.out.println("str_0字符串是否以\"cy\"开头?" + str_0.startsWith("cy"));
        System.out.println("str_0字符串是否以\"Cy\"开头?" + str_0.startsWith("Cy"));
        System.out.println("================");

        //4 —— boolean isEmpty()
        System.out.println("str_0字符串是否为空?" + str_0.isEmpty());
    }
}

5.2 获取功能的方法

① int length():获取当前字符串的长度

② char charAt(int index):获取字符串指定位置索引的字符

③ int indexOf(String str):获取指定字符(串) 第一次出现的索引,若返回 -1,则表示没有该指定字符(串) 

④ int indexOf(String str, int t):功能与 方法相同,但默认从字符串索引第 t 位开始找

⑤ int lastIndexOf(String str):获取指定字符(串)最后一次出现的索引,若没有,返回 -1

⑥ int compareTo(String anotherString):返回两个字符串对象的比较结果,有 3 种情况:

        (1)字符串长度相同,内容相同:返回

        (2)字符串长度相同,内容不同:从两个字符串的第一个字符开始比较,返回第一个不相等的字符的 ASCII 码差值

        (3)字符串长度不相同,但较长字符串的前面部分正好是较短字符串:返回两个字符串的长度差

⑦ String substring(int beginIndex):获取指定索引位置(包含该位置) 之后的字符串。

⑧ String substring(int beginIndex, int endIndex):获取从索引 beginIndex 开始,到索引 endIndex 之间的字符串(区间为前闭后开)

案例

public class demo {
    public static void main(String[] args) {
        //演示 : String常用方法之获取功能的方法
        //1 —— int length()
        String str_0 = "We are the best.";
        System.out.println("当前字符串str_0 : " + str_0);
        int length = str_0.length();
        System.out.println("str_0字符串的长度为:" + length);
        System.out.println("========================");

        //2 —— char charAt(int index)
        char ch_0 = str_0.charAt(0);
        char ch_1 = str_0.charAt(5);
        System.out.println("str_0字符串中,索引为0的字符是:" + ch_0);
        System.out.println("str_0字符串中,索引为5的字符是:" + ch_1);
        System.out.println("========================");

        //3 —— int indexOf(String str)
        int i_0 = str_0.indexOf('e');
        System.out.println("字符\'e\'在字符串str_0中首次出现时的索引为:" + i_0);
        int i_2 = str_0.indexOf("are");
        System.out.println("字符串\"are\"在字符串str_0中首次出现时的索引为:" + i_2);
        System.out.println("========================");

        //4 —— int indexOf(String str, int t)
        int i_1 = str_0.indexOf('e', 7);
        System.out.println("从字符串str_0索引为7的地方开始找,字符\'e\'在字符串str_0中首次出现时的索引为:" + i_1);
        System.out.println("========================");

        //5 —— int lastIndexOf(String str)
        int i_3 = str_0.lastIndexOf('t');
        System.out.println("字符\'t\'在字符串str_0中最后一次出现时的索引为:" + i_3);
        System.out.println("========================");

        //6 —— int compareTo(String anotherString)
        //第一种情况 : 俩字符串相同
        int i_4 = str_0.compareTo("We are the best.");
        System.out.println("compareTo方法返回0说明俩字符串相等: " + i_4);
        //第二种情况 : 俩字符串长度相同,内容不同
        int i_5 = str_0.compareTo("You are so good.");  //W : 77, Y : 79.
        System.out.println("\'W\'和\'Y\'的ASCII码差值为:" + i_5);
        //第三种情况 : 俩字符串长度不相同,但长的字符串的前一部分与短的字符串相同。
        int i_6 = str_0.compareTo("We are the best.we are the king!");
        int i_7 = "We are the best.we are the king!".compareTo(str_0);
        System.out.println("str_0字符串与传入字符串的长度差值为:" + i_6);
        System.out.println("传入字符串与str_0字符串的长度差值为:" + i_7);
        System.out.println("========================");

        //7 —— String substring(int beginIndex)
        String str_1 = str_0.substring(11); //"best."
        System.out.println("str_0字符串中,从索引11开始到结束的字符串为:" + str_1);
        System.out.println("========================");

        //8 —— String substring(int beginIndex, int endIndex)
        String str_2 = str_0.substring(7, 10);  //[7,10)——注意区间的格式。
        System.out.println("str_0字符串中,从索引7到10(不包括10)的字符串为:" + str_2);
    }
}

5.3 转换功能的方法

① byte[] getBytes():将字符串转换成字节数组,字节数据中存放每个字符的 ASCII 码值

② char[] toCharArray():将字符串转换成字符数组

③ static String valueOf(...):将指定类型数据转换成字符串

④ String replace(char oldChar, char newChar):将字符串中的 oldChar 字符全部替换为 newChar ,然后返回一个新的字符串。原字符串不受影响

⑤ String[] split(String regex):以传入的字符(串)为标准来切割原字符串,返回切割后的字符串数据(保存在数组中),以 String 类型数组作为接收。原字符串不受影响

⑥ String trim():去掉字符串两端的空白字符(可用于登录账号和密码的处理)

⑦ String concat(String str):将传入的字符(串)拼接到当前字符串的末尾

⑧ String toUpperCase():将当前字符串全部转换成大写后返回

⑨ String toLowerCase():将当前字符串全部转换成小写后返回

⑩ static String format(String format, Object... args):将指定参数以指定的输出控制格式来输出。形参 "String format" 即输出格式控制,是包含占位符的 String 类型。形参 "Object... args" 即要输出的参数,如下所示:

案例 

public class demo {
    public static void main(String[] args) {
        //演示 : String常用方法之转换功能的方法
        //1 —— byte[] getBytes()
        String str_0 = "ABCD";  //A~D对应ASCII码值 = 65~68
        byte[] bytes = str_0.getBytes();
        for (int i = 0; i < bytes.length; i++) {
            System.out.println("字节数组的第" + (i + 1) + "个元素 = " + bytes[i]);
        }
        System.out.println("-----------------------");

        //2 —— char[] toCharArray()
        char[] chars = str_0.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            System.out.println("字符数组的第" + (i + 1) + "个元素 = " + chars[i]);
        }
        System.out.println("-----------------------");

        //3 —— static String valueOf(...)
        String str_1 = String.valueOf(1111);
        String str_2 = String.valueOf('h');
        String str_3 = String.valueOf(123.6666);
        System.out.println("int类型转String类型:" + str_1);
        System.out.println("char类型转String类型:" + str_2);
        System.out.println("double类型转String类型:" + str_3);
        System.out.println("-----------------------");

        //4 —— String replace(char oldChar, char newChar)
        String str_4 = "You_are_so_beautiful!";
        String str_5 = str_4.replace('_', ' ');    //以' '替换掉'_'
        System.out.println("原字符串:" + str_4);
        System.out.println("新字符串:" + str_5);
        System.out.println("-----------------------");

        //5 —— String[] split(String regex)
        String str_6 = "What hahaa hahacoincidence haha!";
        String[] strings = str_6.split("haha");     //以"haha"来切割字符串。
        for (String string : strings) {
            System.out.print(string);
        }
        System.out.println("\n-----------------------");

        //6 —— String trim()
        String str_7 = "      hey,man!    ";
        String str_8 = str_7.trim();
        System.out.println("去掉两端空白字符前的字符串:" + str_7);
        System.out.println("去掉两端空白字符后的字符串:" + str_8);
        System.out.println("-----------------------");

        //7 —— String concat(String str)
        String str_9 = "I ";
        String str_10 = str_9.concat("love ").concat("programming!");
        System.out.println("拼接后的字符串str_10 = " + str_10);
        System.out.println("-----------------------");

        //8 —— String toUpperCase()
        String str_11 = "i love you!";
        System.out.println("大写之前的字符串:" + str_11);
        System.out.println("大写之后的字符串:" + str_11.toUpperCase());
        System.out.println("-----------------------");

        //9 —— String toLowerCase()
        System.out.println("小写之前的字符串:" + str_11.toUpperCase());
        System.out.println("小写之后的字符串:" + str_11.toUpperCase().toLowerCase());
        System.out.println("-----------------------");

        //10 —— static String format(String format, Object... args)
        String name = "Cyan";
        int age = 21;
        String hobby = "programming!";
        String sss = String.format("My name is %s, and I'm %d years old, my hobby is %s", name, age, hobby);
        System.out.println(sss);
    }
}

6.String类的不可变性(非常重要)

在本文的 "2.String类的final关键字和value属性"     的共同作用下,Java 中的 String 具有不可变性,简单来说就是在字符串常量池中对于每个字符串常量,其内容一直不会被修改

结论:String 是不可变的,对于 String 的任何修改操作都会生成新对象,而原引用不受影响

下面举一些例子来帮助理解 

6.1 直接修改字符串

示例代码

public class test {
    public static void main(String[] args) {
        String s = "123";
        System.out.println("\"123\"地址对应的hashCode值:"+s.hashCode());

        /*
        (1)正确解读:s = "小马" 等价于 String s = "小马",创建了个新的字符串对象,其引用是字符串常量池中"小马"的空间地址。
                    "123" 仍然在字符串常量池中,地址也未变
        (2)错误解读:s = "小马" 使得原引用中的 "123" 被替换为 "小马" 。PS:这是很多人容易理解错的地方           
         */
        s = "小马";
        System.out.println("\"小马\"地址对应的hashCode值:"+s.hashCode());
    }
}

运行结果:

内存原理GIF图

6.2 在方法中修改字符串

虽然 String 类是引用数据类型,但由于 String 类的不可变性,在方法中修改字符串有以下特点:

① 方法内对 String 参数的修改(如拼接、替换)会生成新对象,原对象内容不变

② 方法外部的原始引用仍然指向原对象

 示例代码

public class test {
    public static void main(String[] args) {
        String str = "Hello";
        changeString(str);
        System.out.println(str); // 输出仍然是Hello
    }
    //方法内部修改字符串
    public static void changeString(String str) {
        str = "World";//生成新对象"World",内部的str指向新对象,外部的str不受影响
    }
}

在此例中,方法 changeString 接收了 str 引用的拷贝,即 str 也是指向 "Hello"

然后在方法内部,str 被重新赋值为 "World" ,但这只是改变了 changeString 方法内部的 str 的引用,外部的 str 仍然指向 "Hello" 。这证明了虽然传递的是引用的拷贝,但由于 String 的不可变性,原对象不会被修改

内存原理GIF图