String为何不可变?(Java源码解析)

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在初始化以后是不可变的。java

如何修改已经初始化的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();
        }
    }
}
复制代码

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

总结一下,如何才能自定义一个不可变类呢?bash

(1) 类使用final修饰符修饰。
(2)类的全部字段使用private final修饰。
(3)XXXSetter方法,getXXX方法返回拷贝的对象,不返回对象自己。
(4)构造器初始化成员变量时,使用深拷贝。ide

深拷贝是什么?

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

如何实现深拷贝?

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

/*
* 实现深拷贝
* */
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());//李华
    }
}
复制代码
相关文章
相关标签/搜索