背景
在Java中,final可以显示在三个地方:
将类定型为final会阻止该类的所有子类化。将方法设为final可以防止方法的子类覆盖它。将字段定为最终值可防止以后更改它。
误解
围绕最终方法和字段进行的优化。
最后一种方法使HotSpot易于通过内联进行优化。但是,即使该方法不是最终方法,HotSpot也会执行此操作,因为它假设在未证明之前没有被覆盖,因此它可以工作。有关此的更多信息
可以积极地优化最终变量,有关更多信息,请参见JLS第17.5.3节。
然而,这种理解应该知道,无论这些优化是关于制造类决赛。将课程定为决赛不会提高成绩。
类的最后方面也与不变性无关。一个人可以拥有一个不可变的类(例如BigInteger),它不是final的,或者是一个可变且最终的类(例如StringBuilder)。关于某个班级是否应该是最终班级的决定是设计问题。
最终设计
字符串是最常用的数据类型之一。它们被用作地图的键,它们存储用户名和密码,它们是您从键盘或网页上的字段中读取的内容。弦无处不在。
地图
首先考虑一下如果可以对String进行子类化会发生什么,那就是意识到某人可以构造一个可变的String类,否则该类似乎是String。这会使各地的地图混乱。
考虑以下假设代码:
Map t = new TreeMap<String, Integer>();
Map h = new HashMap<String, Integer>();
MyString one = new MyString("one");
MyString two = new MyString("two");
t.put(one, 1); h.put(one, 1);
t.put(two, 2); h.put(two, 2);
one.prepend("z");
通常,在Map上使用可变键是一个问题,但是我要尝试解决的问题是突然有许多关于Map中断的事情。条目不再位于地图上的正确位置。在HashMap中,哈希值已(应该)已更改,因此不再位于正确的条目处。在TreeMap中,树现在已断开,因为其中一个节点位于错误的一侧。
由于对这些键使用String很常见,因此应通过将String设置为final来防止此行为。
您可能对阅读Java中为什么String不可变感兴趣?有关字符串的不变性质的更多信息。
邪恶的弦
字符串有许多有害的选择。考虑一下我是否创建了一个在调用equal时始终返回true的String并将其传递给密码检查?还是为了使对MyString的分配将String的副本发送到某个电子邮件地址?
当您有能力继承String时,这是非常实际的可能性。
Java.lang字符串优化
在我之前提到final并不能使String更快。但是,String类(和中的其他类java.lang
)经常使用字段和方法的包级保护,以允许其他java.lang
类修改内部结构,而不是始终通过String的公共API。诸如不带范围检查的getChars或StringBuffer使用的lastIndexOf之类的函数,或共享基础数组的构造函数(请注意,多数民众赞成这是Java 6的事情,由于内存问题而更改)。
如果有人做了String的子类,它将无法共享这些优化(除非它也是其中的一部分java.lang
,但这是一个密封的包)。
设计扩展的难度更大
设计一些可扩展的东西很难。这意味着您必须公开内部的一部分内容才能进行其他修改。
可扩展String不能解决其内存泄漏问题。它的那些部分将需要暴露给子类,然后更改代码将意味着子类将中断。
Java以向后兼容而自豪,并且通过开放核心类进行扩展,人们失去了一些修复功能,同时仍然保持了第三方子类的可计算性。
Checkstyle有一个强制执行的规则(在编写内部代码时确实让我感到沮丧),称为“ DesignForExtension”,该规则强制每个类都为:
其合理性是:
这种API设计风格可以防止父类被子类破坏。缺点是子类的灵活性受到限制,特别是它们无法阻止超类中代码的执行,但这也意味着子类无法通过忘记调用super方法来破坏超类的状态。
允许扩展实现类意味着子类可能破坏它所基于的类的状态并使其变旧,从而使超类给出的各种保证均无效。对于像String这样复杂的东西,几乎可以肯定的是更改它的一部分会破坏某些东西。
开发商hurbis
作为开发人员的一部分。考虑每个开发人员创建带有自己的utils集合的自己的String子类的可能性。但是现在这些子类无法自由地相互分配。
WleaoString foo = new WleaoString("foo");
MichaelTString bar = foo; // This doesn't work.
这种方式导致疯狂。到处都转换为String并检查String是否是您的 String类的一个实例,如果不是,请基于该实例创建一个新的String,然后……就可以了。别。
我敢肯定,你可以写一个很好的String类......但留下写多个string实现那些谁写C ++,并有对付疯狂的人std::string
,并char*
和一些从升压和SString,和所有的休息。
Java字符串魔术
Java使用字符串可以做一些神奇的事情。这些使程序员更易于处理,但会引起语言上的一些不一致。在String上允许子类将对如何处理这些不可思议的事情进行一些非常重要的思考:
字符串文字(JLS 3.10.5)
拥有允许执行的代码:
String foo = "foo";
请勿将其与诸如Integer之类的数字类型的装箱混淆。你不能做1.toString()
,但你可以做"foo".concat(bar)
。
该+
运营商(JLS 15.18.1)
Java中没有其他引用类型允许在其上使用运算符。字符串很特殊。字符串连接操作员也工作在编译器级别,以便"foo" + "bar"
成为"foobar"
当它被编译,而不是在运行时。
字符串转换(JLS 5.1.11)
只需在String上下文中使用它们就可以将所有对象转换为String。
字符串实习(JavaDoc)
String类可以访问Strings池,从而使它具有对象的规范表示形式,该规范表示形式使用String文字填充为编译类型。
允许使用String的子类将意味着这些具有String的位(使程序更容易编程)在其他String类型可能的情况下将变得非常困难或无法实现。