Java的String为什么不可变?(String源码解析)

版权声明:如笔记。一点一滴,伴我成长。可转载 https://blog.csdn.net/Butterfly_resting/article/details/89408266

String的源码解析

public final class String{
    private final char value[];//容器,存放字符串的
    private int hash;//哈希值
    private static final long serialVersionUID = -6849794470754667710L;
    private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
    
    //分配一个新的 String,将参数value[]的内容拷贝到String的value[]中
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    public String(int[] codePoints, int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= codePoints.length) {
                this.value = "".value;
                return;
            }
        }
        // count+offset超过原数组的长度
        if (offset > codePoints.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }


        final int end = offset + count;


        // Pass 1: 计算char []的精确大小
        int n = count;
        for (int i = offset; i < end; i++) {
            int c = codePoints[i];
            if (Character.isBmpCodePoint(c))//判断codePoints[i] 是否为BMP范围内的编码,即c是否在['\u0000','\uFFFF']
                continue;
            else if (Character.isValidCodePoint(c))//确定c是否有效Unicode指定值[0x000000,0X10FFFF]
                n++;//有效就+1.
            else throw new IllegalArgumentException(Integer.toString(c));
        }


        // Pass 2:分配并填写char []
        final char[] v = new char[n];//新建一个不可变的数组大小为n(上步求得)的数组


        for (int i = offset, j = 0; i < end; i++, j++) {
            int c = codePoints[i];
            if (Character.isBmpCodePoint(c))//判断codePoints[i] 是否为BMP范围内的编码
                v[j] = (char)c;//赋值给v[j]
            else    //否则需要用两个char来表示
                Character.toSurrogates(c, v, j++);//将指定字符(Unicode代码点)转换为存储在{@code char}数组中的* UTF-16表示形式。
        }


        this.value = v;//赋值,内容可变,但是value引用的地址不变
        /*
        char[] c = new char[]{'1','2','3','4','5'};
        final char value[];
        value = c;
        System.out.println(value);//12345
        */
    }
    ...
    public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */


            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                char buf[] = new char[len]; //新建同样长度的数组存放原数组的值
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }
    ...
}

从源码可见,我们可以知道一下信息:
a)String是最终类,因为是final修饰的class,不可被继承,也无重写一说。
b)实际存储字符串的是一个数组,并且是final修饰的,分配空间之后内存地址不变。
c)所有成员变量都是private final修饰的,并且没有提供对应的XXXSetter方法,不允许外部修改这些字段,并且 只能对它们赋值一次。
d)涉及value数组的操作(上面只提供了部分源码)都使用了拷贝数组元素的方法,保证了不能在内部修改字符数组
所以说String在初始化之后是不可变的。

如何修改已经初始化的String字符串的值

即使是不可变类,通过反射仍然可以改变其属性的值。
IllegalArgumentException - 如果指定对象不是声明底层字段(或者其子类或实现者)的类或接口的实例,或者解包转换失败。因为JVM在编译时期, 就把final类型的String进行了优化, 在编译时期就会把String处理成常量。,所以无法直接修改String str = "123"值,而是通过为声明底层字段(或者其子类或实现者)的类或接口的实例来修改String str = “123”。
示例:

import java.lang.reflect.Field;
class People{
    String str = "123";
}
public class StringDemo {
    public static void main(String[] args) {
        People p = new People();
        System.err.println(p.str);//123
        try {
            Field field = People.class.getDeclaredField("str");
            field.setAccessible(true);
            field.set(p,"0");
            System.err.println(p.str);//0   修改String值成功
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

如何才能自定义一个不可变类呢

总结一下,如何才能自定义一个不可变类呢?
1) 类使用final修饰符修饰
2)类的所有字段使用private final修饰
3)不提供XXXSetter方法,getXXX方法返回拷贝的对象,不返回对象本身。
4)构造器初始化成员变量时,使用深拷贝。

深拷贝是什么?

‘深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。 简而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。

如何实现深拷贝?

实现对象拷贝的类,必须实现Cloneable接口,并覆写clone().
注:如果没有实现Cloneable接口,将出现 CloneNotSupportedException运行时异常。
示例:

/*
* 实现深拷贝
* */
class Teacher implements Cloneable {
    private String name;
    private int age;


    public String getName() {
        return name;
    }


    public void setName(String name) {
        this.name = name;
    }


    public int getAge() {
        return age;
    }


    public void setAge(int age) {
        this.age = age;
    }
    public Object clone() throws CloneNotSupportedException
    {
        return super.clone();
    }
}
class Student_One implements Cloneable{
    private String name;
    private Teacher teacher;//添加教师的引用


    public Teacher getTeacher() {
        return teacher;
    }


    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }


    public String getName() {
        return name;
    }


    public void setName(String name) {
        this.name = name;
    }


    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();//调用obeject的clone默认为浅拷贝
    }
}
class Student_Two implements Cloneable{
    private String name;
    private Teacher teacher;//添加教师的引用


    public Teacher getTeacher() {
        return teacher;
    }


    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }


    public String getName() {
        return name;
    }


    public void setName(String name) {
        this.name = name;
    }


    @Override
    public Object clone() throws CloneNotSupportedException {
        //return super.clone();//调用obeject的clone默认为浅拷贝
        Student_Two student = (Student_Two) super.clone();
        student.setTeacher((Teacher) student.getTeacher().clone());//T复制一份eacher对象并重新set进来
        return student;
    }
}
public class StringDemo1 {
    public static void main(String[] args) throws CloneNotSupportedException {
        //新建一个老师对象
        Teacher teacher = new Teacher();
        teacher.setAge(25);
        teacher.setName("李华");
        Student_One student_one = new Student_One();
        student_one.setName("同学甲");
        System.err.println();
        student_one.setTeacher(teacher);
        //拷贝一个Student_One对象(浅拷贝)
        Student_One student_one1 = (Student_One)student_one.clone();
        System.err.println(student_one1.getTeacher().getName());//李华  原拷贝对象
        //修改老师的名字,会把拷贝的对象的老师名称也一同修改了,因为它们指向的是同一块地址,也就是同一个对象
        teacher.setName("黄珊");
        System.err.println(student_one1.getTeacher().getName());//黄珊


        //重新设置老师名为为李华
        teacher.setName("李华");
        Student_Two student_two = new Student_Two();
        student_two.setTeacher(teacher);
        student_two.setName("同学乙");
        //拷贝一个Student_Two对象
        Student_Two student_two1 = (Student_Two) student_two.clone();
        System.err.println(student_two1.getTeacher().getName());//李华  原拷贝对象
        //修改老师的名字,打印发现并没有影响原拷贝对象的值,所以为深拷贝,是不同的两个对象
        teacher.setName("黄珊");
        System.err.println(student_two1.getTeacher().getName());//李华
    }
}

猜你喜欢

转载自blog.csdn.net/Butterfly_resting/article/details/89408266