【探究为什么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[]数组。

目录
相关文章
|
22天前
|
Java
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
本文深入探讨了Java中方法参数的传递机制,包括值传递和引用传递的区别,以及String类对象的不可变性。通过详细讲解和示例代码,帮助读者理解参数传递的内部原理,并掌握在实际编程中正确处理参数传递的方法。关键词:Java, 方法参数传递, 值传递, 引用传递, String不可变性。
45 1
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
|
19天前
|
安全 Java 测试技术
Java零基础-StringBuffer 类详解
【10月更文挑战第9天】Java零基础教学篇,手把手实践教学!
19 2
|
21天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
17 1
|
24天前
|
数据可视化 Java
让星星月亮告诉你,通过反射创建类的实例对象,并通过Unsafe theUnsafe来修改实例对象的私有的String类型的成员属性的值
本文介绍了如何使用 Unsafe 类通过反射机制修改对象的私有属性值。主要包括: 1. 获取 Unsafe 的 theUnsafe 属性:通过反射获取 Unsafe类的私有静态属性theUnsafe,并放开其访问权限,以便后续操作 2. 利用反射创建 User 类的实例对象:通过反射创建User类的实例对象,并定义预期值 3. 利用反射获取实例对象的name属性并修改:通过反射获取 User类实例对象的私有属性name,使用 Unsafe`的compareAndSwapObject方法直接在内存地址上修改属性值 核心代码展示了详细的步骤和逻辑,确保了对私有属性的修改不受 JVM 访问权限的限制
48 4
|
29天前
|
存储 安全 Java
【一步一步了解Java系列】:认识String类
【一步一步了解Java系列】:认识String类
24 2
|
30天前
|
存储 分布式计算 NoSQL
大数据-40 Redis 类型集合 string list set sorted hash 指令列表 执行结果 附截图
大数据-40 Redis 类型集合 string list set sorted hash 指令列表 执行结果 附截图
24 3
|
1月前
|
存储 编译器 程序员
【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路
【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路
59 2
|
1月前
|
C语言 C++
C++番外篇——string类的实现
C++番外篇——string类的实现
19 0
|
1月前
|
C++ 容器
C++入门7——string类的使用-2
C++入门7——string类的使用-2
20 0
|
1月前
|
C语言 C++ 容器
C++入门7——string类的使用-1
C++入门7——string类的使用-1
21 0