当我知道在Java中类java.lang.String
被声明为final时,我一直在想这是为什么。当时我没有找到任何答案,但是这篇帖子:How to create a replica of String class in Java?让我想起了我的疑问。
确实,String提供了我所需的所有功能,我从来没有想过需要扩展String类的任何操作,但你永远不知道别人可能需要什么!
那么,有人知道设计者决定将其设置为final的意图吗?
当我知道在Java中类java.lang.String
被声明为final时,我一直在想这是为什么。当时我没有找到任何答案,但是这篇帖子:How to create a replica of String class in Java?让我想起了我的疑问。
确实,String提供了我所需的所有功能,我从来没有想过需要扩展String类的任何操作,但你永远不知道别人可能需要什么!
那么,有人知道设计者决定将其设置为final的意图吗?
不可变对象 作为字符串的实现方式非常有用。您应该阅读关于 不可变性 的内容以了解更多信息。
不可变对象 的一个优点是:
您可以通过将它们指向单个实例来共享重复的字符串。
(来自 这里)。
如果 String 不是 final 类型的,那么您可以创建一个子类,并且当“作为字符串查看”时两个字符串看起来很相似,但实际上是不同的。
这是一篇不错的文章,概述了上面回答中提到的两个原因:
这可能是该文章中最详细的评论。它与Java中的字符串池和安全问题有关。它涉及如何决定什么内容进入字符串池。假设两个字符串的字符序列相同,则我们存在一个竞争条件以确定哪个先到达,并伴随着安全问题。如果不是这样,那么字符串池将包含冗余字符串,从而失去了最初具有的优势。请自行阅读文章,好吗?
扩展String会对equals和intern造成混乱。JavaDoc说equals:
将此字符串与指定对象进行比较。当且仅当参数不为null且是表示与此对象相同字符序列的String对象时,结果为true。
假设java.lang.String
不是final的,那么SafeString
可以等于String
,反之亦然;因为它们表示相同的字符序列。
如果您将intern
应用于SafeString
,那么SafeString
会进入JVM的字符串池吗?然后,ClassLoader
和所有对象都将引用SafeString
,这些对象将被锁定在JVM的生命周期内。你将面临一个竞争条件,即谁能首先intern一个字符序列——也许是你的SafeString
赢了,也许是一个String
,或者是由不同类加载器(因此是不同的类)加载的SafeString
。
secretKey.intern().getClass().getClassLoader()
访问你整个环境(沙盒)。SafeString
!= String
,那么SafeString.intern
!= String.intern
,并且必须将SafeString
添加到池中。然后,池将成为一个<Class,String>
的池,而不是<String>
,你需要进入池中的是一个新的类加载器。String是不可变或final的最重要原因,是因为它被类加载机制使用,并具有深刻和基本的安全方面。
如果String是可变的或非final的,那么加载"java.io.Writer"的请求可能会被更改为加载"mil.vogoon.DiskErasingWriter"。
String
是 Java 中一个非常核心的类,许多事情都依赖于它按照某种方式工作,例如它是不可变的。
将该类设置为 final
可以防止可能会破坏这些假设的子类出现。
请注意,即使现在,如果您使用反射,您仍然可以破坏字符串(更改其值或哈希码)。反射可以通过安全管理器来停止。如果 String
不是 final
,那么任何人都可以这样做。
其他未声明为 final
的类允许您定义有些错误的子类(例如,您可能会有一个向错误位置添加元素的 List
),但至少 JVM 并不依赖于这些类进行其核心操作。
final
并不保证其不可变性。它只能确保一个类的不变量(其中之一可以是不可变性)不会被子类修改。 - Kevin Brock除了其他答案中提到的明显原因外,使String类成为final的想法也可能与虚拟方法性能开销有关。请记住,String是一个重型类,将其设置为final意味着肯定没有子实现,永远不会有间接调用开销。当然,现在我们有了像虚拟调用和其他优化技术,这些技术总是可以为您做出这些优化。
String
具有特殊的语言支持。您可以编写String
文字,并且有+
运算符的支持。允许程序员对String
进行子类化,会鼓励诸如以下的黑客行为:class MyComplex extends String { ... }
MyComplex a = new MyComplex("5+3i");
MyComplex b = new MyComplex("7+4i");
MyComplex c = new MyComplex(a + b); // would work since a and b are strings,
// and a string + a string is a string.
除了已经提到的许多优点之外,我想再补充一个 - Java中为什么String是不可变的原因之一是为了允许字符串缓存其哈希码。在Java中,由于String是不可变的,它会缓存其哈希码,而且不会在每次调用String的hashcode方法时重新计算,这使得它非常快速,可以用作Java中哈希映射中的键。
简而言之,因为String是不可变的,一旦创建就无法更改其内容,这保证了在多次调用时String的hashCode相同。
如果您查看String类,可以看到它声明为
/** Cache the hash code for the string */
private int hash; // Default to 0
hashcode()
函数的定义如下:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
可能是为了简化实现而这么做。如果你设计一个类,将会被该类的用户继承,那么你需要考虑到一整套新的使用案例来设计。如果他们对X受保护的字段做了这个或那个,会发生什么?将其设为final,他们可以专注于正确地使公共接口工作并确保其稳定。