【探究为什么String类是不可变类型:String类仿写】

简介: 【探究为什么String类是不可变类型:String类仿写】

【探究为什么String类是不可变类型:String类仿写】

01.介绍

我们都知道String类是不可变类型,但很少人思考为什么它是不可变类型,然后我抱着这个想法去搜索了一下

搜索结果

1.因为String类是被final修饰的

2.因为String类里的 char value[],是被value修饰的

其中大多数都是说因为char value[] 加了final 所以String类是不可变类型

这个是String源代码

可以看出确实如上面描述的那样,可是我依然不理解为什么这样就是不可变类型,可能是我比较愚笨,所以采用了最朴素的方法来证明它是不可变的,那就是仿写一个,看看加不加final的区别

02.仿写

class 与 value都不加final

class A{
    private char[] val;
    public A(){
        this.val=new A(new char[]{' '}).val;
    }
    public A(char[] ss){
        this.val=ss;
    }
    public A(A ss){
        this.val=ss.val;
    }
    public void change(A ss){
        this.val=ss.val;
    }
    public char[] tostring() {
        return this.val;
    }
}
public class TestString {
    public static void main(String[] args) {
        A b = new A(new char[]{'s'});
        A a = new A(b);
        test1(a);
        System.out.println(a.tostring());
    }

    static void test1(A a){
        A b = new A(new char[]{'a','b'});
        a.change(b);
    }
}

这里我们那 A类模拟String类,val数组模拟value数组,change()方法模拟 String直接赋值,tostring方法模拟toString方法

结果

ab

解释

在我们class与val都不加final的情况下,我们先创建了一个 A对象a并附初值为's',然后我们调用test1()方法 传入对象a 然后我们改变对象a为"ab",这一步我们模拟的是 String a = "s",调用方法test1() 然后a="ab",我们都知道这时我们打印 主函数的String a对象结果肯定是"s" 因为是String是不可变类型,但这里我们 A类a对象 则打印了ab 说明此时A类是可变类型

val加final,class不加final

class A{
    final private char[] val;
    public A(){
        this.val=new A(new char[]{' '}).val;
    }
    public A(char[] ss){
        this.val=ss;
    }

    public A(A ss){
        this.val=ss.val;
    }

//    public void change(A ss){
//        this.val=ss.val;
////        return new A(ss); //注意:此时这段代码报错 因为此时val不能再被改变 所以不能用this.val
//    }

    public A change(A ss){
        return new A(ss); //改用这个代码
    }

    public char[] tostring() {
        return this.val;
    }
}
public class TestString {
    public static void main(String[] args) {
        A b = new A(new char[]{'s'});
        A a = new A(b);
        test1(a);
        System.out.println(a.tostring());
    }

    static void test1(A a){
        A b = new A(new char[]{'a','b'});
        a = a.change(b);
    }
}

结果

s

解释

这里我们把val用final修饰了 也就代表此时val数组的地址不能再被改变 所以不能用 this.val,所以我们此时change()方法的策略是 当赋新值时 返回一个新的A类,所以我们的test1()方法里的a = a.change(b);这个代码等价于 a="ab",我们再来看此时的结果,发现确实 现在输出的值是 's'了 现在是不可变类型了。

这时我才你会想问那么 对class加final的作用是什么?下面来解释

val加final,class不加final 会出现的问题

class A{
    final private char[] val;
    public A(){
        this.val=new A(new char[]{' '}).val;
    }

    public A(char[] ss){
        this.val=ss;
    }

    public A(A ss){
        this.val=ss.val;
    }
    
    public A change(A ss){
        return new A(ss);
    }

    public A toUp(A ss){
        for (int i=0;i<ss.val.length;i++) {
            if (97<=ss.val[i]&&ss.val[i]<=122){
                ss.val[i]= (char) (ss.val[i]-32);
            }
        }
        return ss;
    }

    public char[] tostring() {
        return this.val;
    }

}

class B extends A{
    final private char[] val;
    public B(){
        this.val=new B(new char[]{' '}).val;
    }
    public B(char[] ss){
        this.val=ss;
    }

    public B(B ss){
        this.val=ss.val;
    }

    @Override
    public A toUp(A ss){
        return ss;
    }
    @Override
    public char[] tostring() {
        return this.val;
    }
}
public class TestString {
    public static void main(String[] args) {
//        A a = new A(new char[]{'s','a'}); 
//        A aa = a; // 没使用多态
//        aa = aa.toUp(aa);
//        System.out.println(aa.tostring());
        
        B b = new B(new char[]{'s','a'});
        A aa = b; // 使用多态
        aa = aa.toUp(aa);
        System.out.println(aa.tostring());
    }
}

toUp方法 是把小写字母变为大写,但是我们在A的子类B中重写这个方法 使其直接返回 也就是使这个方法失效

结果

SA 没使用多态的答案

sa 使用多态的答案

可以看出 此时我们创建了一个子类B,并且使用多态 成功改变了父类A中的改变大小写的方法

解释

如果我们不对类加final,则说明此类的可以继承,也就是有子类,有子类就说明可以重写父类方法,而此时我们用多态 把子类对象赋值给父类对象,此时父类对象调用被重写的方法 就会导致 父类原有法方法被改变,就像这个例子里的toUp方法,很显然我们不希望出现这种事,所以String类 用final修饰class是很有必要的。

03.总结

综上我们明白了为什么 java类是不可变类型 且为什么 java类与java的成员变量value为什么一定要用final修饰。

再来谈谈这样实现的其他好处

第一个好处:String类的设计还可以保证线程安全,首先因为成员变量 value被final修饰 也就是不可变 所以多个线程操作公共String不会出现线程安全问题,你可能会问 String类型有replace,substring 等方法可以改变值啊,但其实不会,因为这些方法和我上面举例的toUp()方法一样会返回一个新的String对象 也就不可能出现 线程不安全的情况。

第二个好处:因为string类在java中被大量应用,所以它的内存占用优化就显得很重要,而string的不可变性才使得jvm可以实现字符串常量池,创建String对象之前 jvm会先检查字符串常量池中是否存在该对象,若存在则直接返回其引用,否则新建一个对象并缓存进常量池,再返回其引用。

第三个好处:因为==private final char value[]==中final修饰的是字符数组,也就是final只是限制了其引用 并没有限制其值 所以我们可以对value里的值进行修改,进而实现了如replace,substring等方法,同时用private修饰 也防止了在类外 String对象直接操作value[]数组。

目录
相关文章
|
4天前
|
Java 编译器 ice
【Java开发指南 | 第十五篇】Java Character 类、String 类
【Java开发指南 | 第十五篇】Java Character 类、String 类
23 1
|
4天前
|
C语言 C++
【C++】string类(常用接口)
【C++】string类(常用接口)
21 1
|
2天前
|
JavaScript 前端开发
TypeScript内置类型一览(Record<string,any>等等)(下)
TypeScript内置类型一览(Record<string,any>等等)
|
2天前
|
JavaScript
TypeScript内置类型一览(Record<string,any>等等)(中)
TypeScript内置类型一览(Record<string,any>等等)
|
2天前
|
JavaScript
TypeScript内置类型一览(Record<string,any>等等)(上)
TypeScript内置类型一览(Record<string,any>等等)
|
3天前
|
C语言 C++ 容器
C++ string类
C++ string类
9 0
|
4天前
|
编译器 C++
【C++】继续学习 string类 吧
首先不得不说的是由于历史原因,string的接口多达130多个,简直冗杂… 所以学习过程中,我们只需要选取常用的,好用的来进行使用即可(有种垃圾堆里翻美食的感觉)
9 1
|
4天前
|
算法 安全 程序员
【C++】STL学习之旅——初识STL,认识string类
现在我正式开始学习STL,这让我期待好久了,一想到不用手撕链表,手搓堆栈,心里非常爽
16 0
|
4天前
|
存储 安全 测试技术
【C++】string学习 — 手搓string类项目
C++ 的 string 类是 C++ 标准库中提供的一个用于处理字符串的类。它在 C++ 的历史中扮演了重要的角色,为字符串处理提供了更加方便、高效的方法。
18 0
【C++】string学习 — 手搓string类项目
|
4天前
|
C++
【C++】string类(介绍、常用接口)
【C++】string类(介绍、常用接口)
18 2