为什么 String 是 immutable 类

二哥,你能给我说说为什么 String 是 immutable 类(不能变工具)吗?我想研究它,想知道为什么它就不能变了,这种强烈的愿望就像想研究众多的星空一样。但无奈自身功力有限,始终以为雾里看花终隔一层。二哥你的文章总是充满趣味性,我想一定能够说明了,我也一定能够看明了,能在接下来写一写吗?

收到读者小 R 的私信后,我就总感受自己有一种义不容辞的责任,非要把 immutable 类说明了,否则我就怎么地——你说了算!

为什么 String 是 immutable 类

01、什么是不能变类

一个类的工具在通过组织方式建立后若是状态不会再被改变,那么它就是一个不能变(immutable)类。它的所有成员变量的赋值仅在组织方式中完成,不会提供任何 setter 方式供外部类去修改。

还记得《神雕侠侣》中小龙女的古墓吗?随着那一声巨响,仅有的通道就被无情地关闭了。别较真谁人密道,我这么说只是为了打开你的想象力,让你对不能变类有一个更直观的印象。

自从有了多线程,生产力就被无限地放大了,所有的程序员都爱它,由于壮大的硬件能力被充实地利用了。但与此同时,所有的程序员都对它心生忌惮,由于一不小心,多线程就会把工具的状态变得杂乱不堪。

为了珍爱状态的原子性、可见性、有序性,我们程序员可以说是竭尽所能。其中,synchronized(同步)关键字是最简朴最入门的一种解决方案。

如果说类是不能变的,那么工具的状态就也是不能变的。这样的话,每次修改工具的状态,就会发生一个新的工具供差别的线程使用,我们程序员就不必再忧郁并发问题了。

02、常见的不能变类

提到不能变类,险些所有的程序员第一个想到的,就是 String 类。那为什么 String 类要被设计成不能变的呢?

1)常量池的需要

字符串常量池是 Java 堆内存中一个特殊的存储区域,当建立一个 String 工具时,假云云字符串在常量池中不存在,那么就建立一个;如果已经存,就不会再建立了,而是直接引用已经存在的工具。这样做能够削减 JVM 的内存开销,提高效率。

2)hashCode 的需要

由于字符串是不能变的,所以在它建立的时刻,其 hashCode 就被缓存了,因此异常适合作为哈希值(比如说作为 HashMap 的键),多次挪用只返回同一个值,来提高效率。

3)线程平安

就像之前说的那样,若是工具的状态是可变的,那么在多线程环境下,就很容易造成不能预期的效果。而 String 是不能变的,就可以在多个线程之间共享,不需要同步处置。

因此,当我们挪用 String 类的任何方式(比如说 trim()substring()toLowerCase())时,总会返回一个新的工具,而不影响之前的值。

String cmower = "缄默王二,一枚有趣的程序员";
cmower.substring(0,4);
System.out.println(cmower);// 缄默王二,一枚有趣的程序员

虽然挪用 substring() 方式对 cmower 进行了截取,但 cmower 的值没有改变。

除了 String 类,包装器类 Integer、Long 等也是不能变类。

【Android】Retrofit源码学习

03、自界说不能变类

看懂一个不能变类也许容易,但要建立一个自界说的不能变类生怕就有点难了。但知难而进是我们作为一名优异的程序员不能或缺的品质,正由于不容易,我们才气真正地掌握它。

接下来,就请和我一起,来自界说一个不能变类吧。一个不能变诶,必须要知足以下 4 个条件:

1)确保类是 final 的,不允许被其他类继续。

2)确保所有的成员变量(字段)是 final 的,这样的话,它们就只能在组织方式中初始化值,而且不会在随后被修改。

3)不要提供任何 setter 方式。

4)若是要修改类的状态,必须返回一个新的工具。

根据以上条件,我们来自界说一个简朴的不能变类 Writer。

public final class Writer {
    private final String name;
    private final int age;

    public Writer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }
}

Writer 类是 final 的,name 和 age 也是 final 的,没有 setter 方式。

OK,听说这个作者分享了许多博客,广受读者的喜好,因此某某出版社找他写了一本书(Book)。Book 类是这样界说的:

public class Book {
    private String name;
    private int price;

    public String getName() {
        return name;
    }

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

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

2 个字段,分别是 name 和 price,以及 getter 和 setter,重写后的 toString() 方式。然后,在 Writer 类中追加一个可变工具字段 book。

public final class Writer {
    private final String name;
    private final int age;
    private final Book book;

    public Writer(String name, int age, Book book) {
        this.name = name;
        this.age = age;
        this.book = book;
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public Book getBook() {
        return book;
    }
}

并在组织方式中追加了 Book 参数,以及 Book 的 getter 方式。

完成以上事情后,我们来新建一个测试类,看看 Writer 类的状态是否真的不能变。

public class WriterDemo {
    public static void main(String[] args) {
        Book book = new Book();
        book.setName("Web全栈开发进阶之路");
        book.setPrice(79);

        Writer writer = new Writer("缄默王二",18, book);
        System.out.println("订价:" + writer.getBook());
        writer.getBook().setPrice(59);
        System.out.println("促销价:" + writer.getBook());
    }
}

程序输出的效果如下所示:

订价:Book{name='Web全栈开发进阶之路', price=79}
促销价:Book{name='Web全栈开发进阶之路', price=59}

糟糕,Writer 类的不能变性被破坏了,价钱发生了转变。为了解决这个问题,我们需要为不能变类的界说规则追加一条内容:

若是一个不能变类中包含了可变类的工具,那么就需要确保返回的是可变工具的副本。也就是说,Writer 类中的 getBook() 方式应该修改为:

public Book getBook() {
    Book clone = new Book();
    clone.setPrice(this.book.getPrice());
    clone.setName(this.book.getName());
    return clone;
}

这样的话,组织方式初始化后的 Book 工具就不会再被修改了。此时,运行 WriterDemo,就会发现价钱不再发生转变了。

订价:Book{name='Web全栈开发进阶之路', price=79}
促销价:Book{name='Web全栈开发进阶之路', price=79}

04、总结

不能变类有许多优点,就像之前提到的 String 类那样,尤其是在多线程环境下,它异常的平安。只管每次修改都市建立一个新的工具,增加了内存的消耗,但这个瑕玷相比它带来的优点,显然是微不足道的——无非就是捡了西瓜,丢了芝麻。

为什么 String 是 immutable 类

若是以为文章对你有点辅助,请微信搜索「 缄默王二 」第一时间阅读,回复【666】【1024】更有我为你经心准备的 500G 高清教学视频(已分门别类),以及大厂手艺牛人整理的面经一份。

原创文章,作者:28x29新闻网,如若转载,请注明出处:https://www.28x29.com/archives/1868.html