经常面试会问到说说为什么String类型是不可以变的?可能很多人第一反应就是因为是final呀!其实个人感觉这个问题很宽泛,导致很多人可能第一反应不知道具体想回答哪一点?

1: 为什么String是不可以变的?

个人理解并不是不可以变,而是JDK的String类型没有提供可以修改String值的方法而已,相反StringBuilder与StringBuffer就提供了相应的修改方法!!!如果理解有问题欢迎反馈。如果真需要想要修改String实例的值的话可以通过反射实现,参见如下代码:

import java.lang.reflect.Field;

public class ReflectString {

    public static void main(String[] args) throws Exception {
        String val = "HelloWorld";
        Field value = val.getClass().getDeclaredField("value");
        value.setAccessible(true);
        char[] chars = (char[]) value.get(val);
        System.out.println("char数组赋值之前的val值 = " + val);
        for (int i = 0, size = chars.length; i < size; i++) {
            chars[i] = (char) ('a' + i);
        }
        System.out.println("char数组赋值之后的val值 = " + val);
    }
}

输出信息如下:

char数组赋值之前的val值 = HelloWorld
char数组赋值之后的val值 = abcdefghij

如上的例子是不是已经证明了String是可以变的了呢?!我个人觉着是可以的。如果歧义或者错误欢迎指点。

2:为什么String不提供可以改变String实例内容的方法呢?

这个问题应该需要从数据类型说起,Java中基本数据类型有:byte、short、int、long、float、double、以及boolean;对于Java中的基本类型每次重新赋值一个不同的值,该值都是一个新的实例,并不是在原有实例基础上修改而来的。所以为了包装类型的使用与基本类型使用保持一致,就必须要统一做到这一点,则包装类型就不能提供修改值的方法。否则会导致混乱。例如:

例1:基本类型的赋值

int a = 10; // 假设此时a=10中a所指向的内存地址是:addr1
        int b = a;//此时b的指向地址空间也是addr1
        a = 20; //此时对a赋值为20的话,此时a的指向地址空间不再是addr1,可能是addr2.但是b的指向一定还是addr1

例2:引用类型的赋值

Integer a = 10; // 假设此时a=10中a所指向的内存地址是:addr1
        Integer b = a;//此时b的指向地址空间也是addr1
        //假设,注意是假设Integer存在一个方法为setValue可以修改其值的话,进行如下操作:
        a.setValue(20);
        // 那么此时b输出的也是20了,明显是一个错误结果
        System.out.println(b);

所以个人理解:还是为了保证包装类型与基本类型使用保持一致;回过头来你可能会说String不存在基本类型,虽然String不存在基本类型,但是String如果支持改变的话依旧会存在例2的问题。虽然String不是一个基本类型,但是你可以将它认为是基本类型;String之所以是引用类型就是根据其类型特性所致,由于其长度不确定,因此没有办法使用一个固定长度的类型表示,所以只能使用引用类型。

拓展:为什么StringBuilder与StringBuffer提供了修改方法呢?见名知意,其后缀是Builder与Buffer,因此你可以知道它们不是一个字符串类型,而是一个字符串构建工具,因此当然可以提供修改方法了!!

 

3:为什么String是一个final class?设计的目的是什么呢?

其实你可以去翻阅JDK源码,不单单String是final class,8个基本类型对应的包装类型都是final class。那为什么要这么设计呢?

首先需要澄清定义一个class为final并不是说该class不可以改变,而是说该class不在允许被继承。见示例代码:

final class User {
    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public static void main(String[] args) {
        User u = new User();
        u.setAge(10);
        System.out.println(u.getAge());
        u.setAge(18);
        System.out.println(u.getAge());
    }
}

final用在字段上面的用法自行学习吧,不在介绍。继续说为什么是final class,String跟八个基本类型对应的包装类型都是JDK中最基础的数据类型,每一个类型的规范都是不尽相同的,JDK的所有实现都是按照每一个类型的规范前提下实现的,如果这些类型不是final class的话,允许你自己继承重写其方法,那么岂不是天下大乱?怎么乱?例如:例如某一处是4为整数类型,你继承Integer重写其方法改为8位整数类型,那岂不是错乱了。

总结:JDK内置的8个基本类型以及包装类型还有String都是约定俗成,乌龟的屁股:规定!你必须遵守的。任何事物没有能与不能,应该是允许还是不允许!