为什么String类在Java中声明为final?

从我得知java.lang.String类在Java中被声明为final时,我想知道为什么是这样? 当时我没有find任何答案,但是这篇文章: 如何在Java中创build一个String类的副本? 提醒了我的查询。

当然,String提供了我所需要的所有function,从来没有想过任何需要扩展类String的操作,但是你永远不会知道某人可能需要什么!

那么,有没有人知道devise师决定最后决定什么?

将string实现为不可变对象是非常有用的。 你应该阅读关于不可变性的内容来更多地了解它。

不可变对象的一个优点是

您可以通过将它们指向单个实例来共享重复项。

(从这里 )。

如果string不是最终的,你可以创build一个子类,并有两个string看起来相似,当“看作string”,但实际上是不同的。

这是一个很好的文章 ,概述了上述答案中已经提到的两个原因:

  1. 安全性 :系统可以发布只读信息的敏感部分,而不必担心会被更改
  2. 性能 :不可变数据对于使事物线程安全非常有用。

这可能是那篇文章中最详细的评论。 它与Java中的string池和安全问题有关。 它关于如何决定进入string池的内容。 假设两个string是相同的,如果他们的字符序列是相同的,那么我们有一个竞争条件谁先到达那里,并伴随着它的安全问题。 如果不是,那么string池将包含多余的string,从而失去了首先使用它的优势。 只是自己读出来,会吗?


扩展string将会和equals和intern一起破坏。 JavaDoc说等于:

将该string与指定的对象进行比较。 当且仅当参数不是null并且是表示与此对象相同的字符序列的String对象时,结果为true。

假设java.lang.String不是final的, SafeString可以等于一个String ,反之亦然; 因为他们会代表相同的字符序列。

如果您将intern应用于SafeString会发生什么情况 – SafeString会进入JVM的string池吗? ClassLoaderSafeString持有引用的所有对象将在JVM的生命周期中被locking。 你会得到一个竞赛条件,谁可能是第一个实习一系列字符 – 也许你的SafeString会赢,也许一个String ,或者一个不同的类加载器(因此不同的类)加载的SafeString

如果你赢了比赛,这将是一个真正的单身人士,人们可以通过reflection和secretKey.intern().getClass().getClassLoader()来访问你的整个环境(沙箱secretKey.intern().getClass().getClassLoader()

或者JVM可以通过确保只有具体的String对象(并且没有子类)被添加到池中来阻止这个漏洞。

如果equals实现了这样的SafeString != String那么SafeString.intern != String.internSafeString将不得不被添加到池中。 然后池将成为一个<Class, String>而不是<String>池,所有你需要input池将是一个新的类加载器。

String是不可变的或最终的绝对最重要的原因是它被类加载机制所使用,因此具有深刻而根本的安全性方面。

如果String是可变的或不是最终的,加载“java.io.Writer”的请求可能已经被改变为加载“mil.vogoon.DiskErasingWriter”

参考: 为什么string在Java中是不可变的

String是Java中的一个非常核心的类,许多事情都依赖于它以某种方式工作,例如是不可变的。

使类final可以防止可能打破这些假设的子类。

请注意,即使是现在,如果您使用reflection, 您可以中断string (更改它们的值或散列码)。 reflection可以通过安全经理来停止。 如果String不是final ,每个人都可以做到。

其他没有被声明为final允许你定义有些破碎的子类(例如,你可能会有一个List来增加错误的位置),但是至lessJVM并不依赖于它的核心操作。

正如布鲁诺所言,这是关于不变性的。 这不仅关于string,而且关于任何包装,例如双精度,整数,字符等。这有很多原因:

  • 线程安全
  • 安全
  • 由Java本身pipe理的堆(与以不同方式收集垃圾的普通堆不同)
  • 内存pipe理

基本上,所以你作为程序员可以确保你的string永远不会被改变。 而且,如果你知道它是如何工作的,可以改善内存pipe理。 尝试一个接一个地创build两个相同的string,例如“hello”。 你会注意到,如果你debugging,他们有相同的ID,这意味着他们是完全相同的对象。 这是因为Java让你做到这一点。 如果string是可变的,这将是不可能的。 他们可以有我一样的,等等,因为他们永远不会改变。 所以,如果你决定创build1,000,000个string“hello”,你真正要做的就是创build1,000,000个指向“hello”的指针。 同样,在string或任何包装器上分配任何函数都会导致创build另一个对象(再次查看对象ID – 它将会改变)。

在Java中的最终结果并不一定意味着对象不能改变(它不同于例如C ++)。 这意味着它指向的地址不能改变,但你仍然可以改变它的属性和/或属性。 因此,在某些情况下理解不变性与最终的区别可能是非常重要的。

HTH

参考文献:

这可能是为了简化实施。 如果你devise一个类可以被类的用户inheritance,那么你就有一套全新的用例来考虑你的devise。 如果他们这样做,或者用X预测的领域会发生什么? 最终他们可以专注于让公共接口正常工作,并确保它是稳定的。

有很多好的观点已经引起了我想添加另一个原因之一为什么string在Java中是不可变的是允许stringcaching其哈希码 ,是不可变的在Java中的stringcaching其哈希码, 并不计算每个我们称之为String的hashcode方法 ,这使得它非常快速,因为hashmap key在Java中的hashmap中使用。

简而言之,因为String是不可变的,所以一旦创build,就不能改变它的内容,这就保证了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; } 

如果它已经是电脑只是返回值。

除了其他答案(安全性,不变性,性能)中提到的原因之外,还应该注意的是, 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是唯一的对象,可以被视为原始数据types,以及我的意思是我们可以创build一个string对象为String name =“java “ 。 现在像其他的原始数据types,它是通过值 复制复制引用string预计将具有相同的行为,所以这就是为什么string是最终的。 那是我的想法。 如果它完全不合逻辑,请忽略。

string的最终性也作为一个标准来保护它们。 在C ++中,您可以创buildstring的子类,所以每个编程商店都可以拥有自己的string版本。 这会导致缺乏一个强有力的标准。

确保我们没有得到更好的实施。 它当然应该是一个接口。

啊,越来越无能的投票。 答案是非常严肃的。 我不得不多次为愚蠢的String实现编程,导致严重的性能和生产力损失

除了其他答案中提出的显而易见的原因之外,还有一个想法使得String class final也可能与虚拟方法的性能开销有关。 记住string是一个沉重的类,使这个最终,意味着没有确定的子实现,意味着没有间接调用开销。 当然,我们现在有虚拟调用等其他东西,总是为你做这些优化。

如果你创build一个string一次它会考虑到,如果你想修改它是一个对象,这是不可能的,它会创build新的对象。

JVM知道什么是不可变的吗? 答案是否,常量池包含所有不可变的字段,但所有不可变的字段/对象不存储在常量池中。 只有我们以一种实现不变性及其特征的方式实施它。 CustomString可以在不使用MarkerInterface的情况下最终实现,它将为其池提供java特殊行为,但function仍在等待!

大多数答案都与不变性有关 – 为什么Stringtypes的对象不能被更新。 这里有很多很好的讨论,而Java社区将采取不变性作为委托人。 (不要屏住呼吸。)

然而,OP的问题是关于为什么它是最终的 – 为什么不能延长。 这里有些人确实承认这一点,但我同意OP的观点,认为这里存在真正的差距。 其他语言允许开发人员为types创build新的名义types。 例如在Haskell中,我可以在运行时创build以下与文本相同的新types,但在编译时提供绑定安全性。

 newtype AccountCode = AccountCode Text newtype FundCode = FundCode Text 

所以我会提出以下build议作为对Java语言的改进:

 newtype AccountCode of String; newtype FundCode of String; AccountCode acctCode = "099876"; FundCode fundCode = "099876"; acctCode.equals(fundCode); // evaluates to false; acctCode.toString().equals(fundCode.toString()); // evaluates to true; acctCode=fundCode; // compile error getAccount(fundCode); // compile error 

(或者也许我们可以开始从Java中解脱出来)