采访者:你好,请问下图看清楚了吗?我可以。面试官:嗯,好的。呃,你能告诉我为什么String要修饰成final吗?我:final的意思就是不能继承,不能改写。最后修改String类是因为Java设计者不希望客户端程序员继承String类,有可能重写String类中的方法。使用String对象的最佳实践应该是关联或依赖,而不是继承。面试官:嗯,你没有进入正题,可以谈谈吗?我:嗯,好的。具体来说,主要从安全和性能两个方面将String类定义为final的,也就是说String被设计成final的,安全性和性能兼顾。我们可以看到在上图中,有两个final。一个final是修改String类,一个final是修改char数组。我们知道String的本质其实就是这个char数组。先说finalchar[]的final。之所以用final修改char数组还是要从我们日常的实际开发说起。在日常的实际开发中,开发者会用到大量的字符串对象。可以说我们无时无刻不在和字符串打交道。大量的字符串很容易被创建,这涉及到一个非常严重的问题,即性能开销。我们知道分配给Java虚拟机的内存是有限的。如果毫无节制地创建字符串对象,那么缺点很明显:内存被快速占用,程序执行速度慢!!!于是Java的设计者采用了一个非常有效的解决方案,即:共享字符串。共享字符串对象的方法是将字符串对象存放在虚拟机方法区的常量池中。不同的类,不同的方法,甚至不同的线程都可以使用同一个字符串对象,而无需再在内存中开辟新的内存空间,从而大大减少内存消耗,提高程序运行效率。因此,字符串共享是解决内存消耗和巨大性能开销的必然选择。但是到目前为止,还不能解释为什么这个char数组要用final修饰。使用最终修改的原因来自另一个必须考虑的问题:安全性。什么是安全?这里的安全是指线程安全,这个很容易理解。首先,我们确定了一个大前提:字符串必须是共享的,否则内存会瞬间爆满,性能会严重下降。但是共享的问题是不同的线程可能会修改这个共享对象。比如thread_1正在循环一个List,将每个元素与“abc”进行比较,而thread_2也在使用这个“abc”对象,如果thread_2改变了这个共享字符串,结果会怎样?显然,thread_1的结果将是无效的预测!!所以,解决共享变量安全最好的办法就是禁止修改共享对象,所以字符串对象的char数组一定要用final来修饰,因为final意味着禁止修改。面试官:嗯,没想到你的想法这么透彻!嗯,这就是char数组final的作用,那么为什么要给String类本身加上一个final呢?我:嗯,这是另一个Java设计者需要考虑的问题。既然共享字符数组已经确定为final,不能更改,那为什么还要给String加上一个final呢?究其原因,还是性能和安全两个方面。但是此时需要考虑的性能和安全性与finalchar[]的final并不相同。首先,如果假设String是可以继承的,那么方法也可以重写,这就涉及到C++中的一个概念,叫做:虚函数表。面试官:哦?你知道C++吗?我可以。同样是面向对象的语言,Java和C++有一些共同点。首先,虚函数就是可以定义一个父类的指针,指向子类对象。当通过父类的指针调用函数时,可以在运行时决定是调用父类的函数还是子类的函数。虚函数是多态性的基础。前面说了,如果String可以被继承,那么必然会有人通过创建String引用并指向String子类对象来使用子类的方法,比如这样写:Stringaa=newSubString("abcd");aa.length();这看起来没什么问题,但问题出在性能上。前面说过,在程序开发的过程中,字符串对象是非常常用的。当上面的代码调用对象aa.length()时,虚拟机会去虚函数表中查找并确定应该调用哪个子类的length()方法。在大量使用字符串对象的场景下,势必会降低程序的效率。第二是安全。这种安全性被解释为语义安全性。面向对象语言本身就要求语义清晰,表达清晰。String的每个方法都围绕着一个char数组,所有方法的语义都是最直接有效的。重写String的方法意味着:不同的语义或错误的语义。这将直接导致String行为的不确定性,使用String对象的代码将是不安全的代码。所以Java设计者会禁止任何人继承String类,主要是为了不改变String对象的操作语义,保证使用String对象的代码是绝对安全的。面试官:原来如此,没想到你的理解力这么好!年薪50万,明天来上班吧!总结当面试官问String为什么是final的时候,你要回答两个方面:***是finalcharvalue[]final;第二个是期末班的期末。这两个总决赛必然与安全和性能这两个方面息息相关。1、finalcharvalue[]的final要把握几个要点:value[]数组的final是用来限制字符数组的修改的。字符串会被广泛使用,性能上的考虑迫使Java语言设计者将char[]设计为共享的,又因为字符串再次被共享,迫使设计者考虑线程安全,这就需要对final进行修改,以避免在并发场景下出现不可预知的行为。2、final类的final要把握几个关键点:类上的final是用来限制子类的生成(或限制多态性/或限制行为改变)。字符串的使用很频繁。如果以多态的方式使用String子类对象及其方法,性能会有一定程度的下降(??多态实现原理:底层虚函数表),String中的方法也将面临被重写的危险通过Override,导致不安全的程序语义甚至逻辑错误,这有悖于Java一直强调的安全理念。
