面试题:String为什么要用final修饰,有什么好处

1. 首先回答为什么

为了保证String对象的不可变

2. 那么final是怎么保证String对象的不可变呢?

想要回答这个问题,得先知道final的作用:

  1. final修饰的类不可被继承
  2. final修饰的引用在初始化后不可重新赋值
  3. final修饰的方法不可重写

接着要明确String的底层是什么,在jdk8中,String的底层是一个char数组,在String类源码中,有一个成员变量value,它是一个char数组引用,指向了存储Sting对象内容的char数组,value引用使用了final修饰:
在这里插入图片描述
使用了final引用修饰使得value引用指向的char数组对象不会被改变,但这还不能保证String对象的不可变,虽然对象不变,但是数组的元素却还是可以改变的,对char数组元素的改变同样会使得String对象改变,我们可以用一段代码来测试一下

class Main{
    public static void main(String[] args) {
        //首先声明一个final修饰的引用,并初始化
        final char[] value = {'1','2','3','4'};
        //改变前
        System.out.println(value[0]);
        //改变下标为0的字符
        value[0] = 'a';
        //改变后
        System.out.println(value[0]);
        System.out.println();

    }
}

执行结果如下:
面试题:String为什么要用final修饰,有什么好处_第1张图片
可以看到虽然value被final修饰,但是数组元素还是可以改变

因此,为了保证String对象内容的不可变,String类的设计师们在实现String提供的各种方法时都避免了对value数组元素的改变,举个例子,下面是substring方法的实现源码

 public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        //若beginIndex不等于0,新建了一个对象返回
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }

在方法的最后是新建了一个对象返回,避免了对原对象value数组的改变,再看看replace方法

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;
    }

同样也是创建新对象返回

可见为了使得value数组不被改变,String的设计师们煞费苦心,但是这样还有一个风险,那就是继承

java是支持继承的,倘若我新写了一个类,去继承String类,改写它各种方法的底层实现,那么设计师们的努力不就白费了吗?

所以,为了防止这种情况发生,String类使用了final关键字进行修饰,它可以保证String类不可继承,从而保证了其各种方法不会被重写

看到这里,我们可以回答为什么final可以使得String对象不可变了,final的修饰使得String类的实现逻辑不改变,而实现逻辑保证了String对象的底层value数组的不改变,从而实现了String对象的不可变

说了这么多,String不可变的好处都有啥?

3. String设计成不可变的好处

  1. 提高效率
    String类经常作为哈希表中的key,经常要使用到其hash值,基于String不可变的特性,可以对其hash值进行缓存,减少重复运算,String类有一个成员变量hash,对hash值进行了缓存
    在这里插入图片描述

  2. 提高资源的利用率
    String是最常用的对象,为了节省内存,基于String不可变的特性,可以实现字符串常量池。
    创建String对象前,jvm会先检查字符串常量池中是否存在该对象,若存在则直接返回其引用,否则新建一个对象并缓存进常量池,再返回引用。
    字符串常量池避免了重复String对象的创建,节省了内存资源,同时由于减少了对象创建的次数,也提高了程序的执行效率

  3. 保证线程安全
    String是不可变的,因此不必担心String对象会被其他线程改变,天生线程安全

你可能感兴趣的:(Java相关,java)