java并发编程-final关键字

final的作用

1、final可以修饰类,方法和变量。
2、final修饰的类,不能被继承,即它不能拥有自己的子类。
3、final修饰的方法,不能被重写。
4、final修饰的变量,无论是类属性、对象属性、形参还是局部变量,都需要进行初始化操作。 

不变性

如果对象在被创建之后,其状态就不能修改了,那么它就具备“不变性”。
举个简单例子:

public class User {
    //使用final修饰变量
    final int age = 20;

    public static void main(String[] args) {
        User user = new User();
        //Cannot assign a value to final variable 'age'
        user.age = 3;
    }
}

在我们尝试修改User对象里边的age时,则会编译不通过,所以像这样的 User 对象就具备不变性,也就意味着它的状态是不能改变的。

final在修饰对象时,仅仅是引用地址不可变,对象本身的内容还是可以变化的,也就是说加了final并不代表就一定不可变。
再看一个例子:

public class TestFinal {

    static final int[] arr = {1, 2, 3, 4, 5};

    static int[] arr2 = {2,3,4,5,6};

    public static void main(String[] args) {
        //Cannot assign a value to final variable 'arr'
        //TestFinal.arr = arr2;
        for (int i = 0; i < arr.length; i++) {
            arr[i] = arr[i] * 10;
            System.out.println(arr[i]);
        }
    }
}

打印结果:

10
20
30
40
50

首先看注释部分,我们想把arr的引用指向arr2,但是由于arr是final的,所以编译不通过。继续往下看,在for循环中我们对arr数组中每个元素做了乘10的操作,并赋值给对应的数组元素,再打印该元素,从结果中看到数组的内容从1,2,3,4,5变成了10,20,30,40,50。从这个例子看出final修饰的arr数组它的引用地址是不可变的,但是内容是可变的。

String类的不变性

在 Java 中,字符串是一个常量,我们一旦创建了一个 String 对象,就无法改变它的值,除了反射外,它的内容也就不可能发生变化。

String name = "wang";
name = "peng";

看上去好像是改变了字符串的值,但其背后实际上是新建了一个新的字符串“peng”,并且把 name 的引用指向这个新创建出来的字符串“peng”,原来的字符串对象“wang”保持不变。同样String的一些方法,比如subString(),replace()等也是同样的原理,他们都会去创建一个新的字符串对象。
通过查看String源码:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

首先,可以看到这里面有个非常重要的属性,即 private final 的 char 数组,数组名字叫 value。它存储着字符串的每一位字符,同时 value 数组是被 final 修饰的,也就是说,这个 value 一旦被赋值,引用就不能修改了;并且在 String 的源码中可以发现,除了构造函数之外,并没有任何其他方法会修改 value 数组里面的内容,而且 value 的权限是 private,外部的类也访问不到,所以最终使得 value 是不可变的。

同样因为String类也是被final修饰的,那么其他类也无法通过继承的方式去修改value的值来破坏String类的不变性。这就是 String 具备不变性的原因。

String不可变的好处

字符串常量池

String 不可变的第一个好处是可以使用字符串常量池。在 Java 中有字符串常量池的概念,比如两个字符串变量的内容一样,那么就会指向同一个对象,而不需创建第二个同样内容的新对象,例如:

String s1 = "abc";
String s2 = "abc";

s1和s2的引用都是指向常量池中的"abc",这样做的好处就是可以节省大量的内存空间。上边代码中再加一行s1 = "ABC"

String s1 = "abc";
String s2 = "abc";
s1 = "ABC";

如果String是可变的话,那么s2理应同样变成ABC,这就和我们预期不符了,同样也就没办法实现字符串常量池的功能了,因为对象内容可能会不停变化,没办法再实现复用了。所以实际上,由于 String 具备不可变的性质,即使加上s1="ABC",那么s2依旧是"abc"。不变性使得不同的字符串之间不会相互影响,符合我们预期。

用作 HashMap 的 key

String 不可变的第二个好处就是它可以很方便地用作 HashMap (或者 HashSet) 的 key。通常建议把不可变对象作为 HashMap的 key,比如 String 就很合适作为 HashMap 的 key。

对于 key 来说,最重要的要求就是它是不可变的,这样我们才能利用它去检索存储在 HashMap 里面的 value。由于 HashMap 的工作原理是 Hash,也就是散列,所以需要对象始终拥有相同的 Hash 值才能正常运行。如果 String 是可变的,这会带来很大的风险,因为一旦 String 对象里面的内容变了,那么 Hash 码自然就应该跟着变了,若再用这个 key 去查找的话,就找不回之前那个 value 了。

缓存 HashCode

String 不可变的第三个好处就是缓存 HashCode。
在 Java 中经常会用到字符串的 HashCode,在 String 类中有一个 hash 属性,代码如下:

    /** Cache the hash code for the string */
    private int hash; // Default to 0

这是一个成员变量,保存的是 String 对象的 HashCode。因为 String 是不可变的,所以对象一旦被创建之后,HashCode 的值也就不可能变化了,我们就可以把 HashCode 缓存起来。这样的话,以后每次想要用到 HashCode 的时候,不需要重新计算,直接返回缓存过的 hash 的值就可以了,因为它不会变,这样可以提高效率,所以这就使得字符串非常适合用作 HashMap 的 key。

而对于其他的不具备不变性的普通类的对象而言,如果想要去获取它的 HashCode ,就必须每次都重新算一遍,相比之下,效率就低了。

线程安全

String 不可变的第四个好处就是线程安全,因为具备不变性的对象一定是线程安全的,我们不需要对其采取任何额外的措施,就可以天然保证线程安全。

由于 String 是不可变的,所以它就可以非常安全地被多个线程所共享,这对于多线程编程而言非常重要,避免了很多不必要的同步操作。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值