144

当我了解到该类java.lang.String在 Java 中被声明为 final 时,我想知道为什么会这样。当时我没有找到任何答案,但是这篇文章:如何在 Java 中创建 String 类的副本?让我想起了我的查询。

当然,String 提供了我曾经需要的所有功能,而且我从没想过需要扩展 String 类的任何操作,但您仍然永远不会知道某人可能需要什么!

那么,有没有人知道设计师在决定将其定稿时的意图是什么?

4

16 回答 16

89

将字符串实现为不可变对象非常有用。您应该阅读有关不变性的内容以了解更多信息。

不可变对象的一个​​优点是

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

(从这里)。

如果 String 不是最终的,您可以创建一个子类并拥有两个在“视为字符串”时看起来相似但实际上不同的字符串。

于 2010-01-15T01:17:46.710 回答
60

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

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

这可能是那篇文章中最详细的评论。它与Java中的字符串池和安全问题有关。它关于如何决定进入字符串池的内容。如果两个字符串的字符序列相同,则假设两个字符串相等,那么我们就有一个竞争条件,即谁先到达那里,以及安全问题。如果不是,那么字符串池将包含冗余字符串,从而失去了首先拥有它的优势。自己读一下,好吗?


扩展 String 会对 equals 和 intern 造成严重破坏。JavaDoc 说等于:

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

假设java.lang.String不是最终的, aSafeString可以等于 a String,反之亦然;因为它们代表相同的字符序列。

如果你申请intern一个SafeString--会发生什么会SafeString进入JVM 的字符串池?然后,在 JVM 的生命周期内,所持有的引用ClassLoader和所有对象SafeString都将被锁定到位。你会得到一个关于谁可能是第一个实习一系列字符的竞争条件——也许你SafeString会赢,也许是 a String,或者也许 aSafeString由不同的类加载器(因此是不同的类)加载。

如果你赢得了池中的比赛,这将是一个真正的单例,人们可以通过反射和secretKey.intern().getClass().getClassLoader().

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

如果实现了等于SafeString!=String那么SafeString.intern!= String.intern,则SafeString必须将其添加到池中。然后该池将变为一个池<Class, String>而不是,<String>而您进入该池所需的只是一个新的类加载器。

于 2010-01-15T02:14:59.020 回答
27

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

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

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

于 2011-05-21T03:55:31.987 回答
15

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

创建类final可以防止可能破坏这些假设的子类。

请注意,即使是现在,如果您使用反射,您也可以破坏字符串(更改它们的值或哈希码)。可以使用安全管理器停止反射。如果String不是final,每个人都可以做到。

其他未声明的类final允许您定义一些损坏的子类(例如,您可能有一个List添加到错误位置的子类),但至少 JVM 的核心操作不依赖这些子类。

于 2010-01-15T01:23:18.953 回答
6

正如布鲁诺所说,这是关于不变性。它不仅与字符串有关,还与任何包装器有关,例如 Double、Integer、Character 等。这有很多原因:

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

基本上它是为了让您作为程序员可以确保您的字符串永远不会被更改。同样,如果您知道它是如何工作的,则可以改善内存管理。尝试一个接一个地创建两个相同的字符串,例如“hello”。如果您进行调试,您会注意到它们具有相同的 ID,这意味着它们是完全相同的对象。这是因为 Java 让你去做。如果字符串是可变的,这将是不可能的。他们可以拥有和我一样的东西,等等,因为他们永远不会改变。因此,如果您决定创建 1,000,000 个字符串“hello”,那么您真正要做的就是创建 1,000,000 个指向“hello”的指针。以及所有字符串上的函数,或任何包装器,都会导致创建另一个对象(再次查看对象 ID - 它会改变)。

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

高温高压

参考:

于 2010-01-15T01:36:53.593 回答
2

可能是为了简化实施。如果您设计了一个可由该类的用户继承的类,那么您将在设计中考虑一组全新的用例。如果他们对 X 受保护的字段执行此操作或执行此操作会发生什么?最终,他们可以专注于让公共界面正常工作并确保它是可靠的。

于 2010-01-15T01:19:51.850 回答
2

除了其他答案中提到的原因(安全性、不变性、性能)之外,还应注意它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.
于 2010-04-22T12:51:46.720 回答
2

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

[编辑] 啊,越来越无能为力的反对票。答案非常严肃。我不得不多次围绕愚蠢的 String 实现进行编程,导致严重的性能和生产力损失

于 2011-03-07T21:45:03.687 回答
2

已经提到了很多优点,我想再添加一个 - 为什么 String 在 Java 中不可变的原因之一是允许String 缓存其哈希码,Java 中的不可变 String 缓存其哈希码,并且不计算每个当我们调用 String 的 hashcode 方法时,这使得它在 Java 中的 hashmap 中用作 hashmap 键非常快。

简而言之,因为 String 是不可变的,所以一旦创建,任何人都无法更改其内容,这保证了 String 的 hashCode 在多次调用时是相同的。

如果你看到Stringclass has 被声明为

/** 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;
}

如果它已经是计算机,则返回该值。

于 2014-01-10T12:06:53.937 回答
2

除了其他答案中提出的明显原因外,将 String 类设为 final 的想法也可能与虚拟方法的性能开销有关。记住 String 是一个沉重的类,使这个最终,意味着肯定没有子实现,意味着永远没有间接调用开销。当然,现在我们有诸如虚拟调用之类的东西,它们总是为你做这些优化。

于 2015-08-20T13:03:21.790 回答
1

好吧,我有一些不同的想法,我不确定我是否正确,但在 Java 中,String 是唯一可以被视为原始数据类型的对象,我的意思是我们可以创建一个 String 对象为String name="java ”。现在,像其他按值复制而不是按引用复制的原始数据类型一样, String 预计具有相同的行为,这就是为什么 String 是最终的。这就是我的想法。如果完全不合逻辑,请忽略。

于 2010-12-08T20:08:45.610 回答
1

字符串的最终确定性也将它们作为标准进行了辩护。在 C++ 中,您可以创建字符串的子类,因此每个编程商店都可以拥有自己的字符串版本。这将导致缺乏强有力的标准。

于 2012-12-10T18:05:50.773 回答
1

假设您有一个Employee具有方法的类greet。调用该greet方法时,它只会打印Hello everyone!. 这就是方法的预期行为greet

public class Employee {

    void greet() {
        System.out.println("Hello everyone!");
    }
}

现在,让GrumpyEmployee子类Employee和覆盖greet方法如下所示。

public class GrumpyEmployee extends Employee {

    @Override
    void greet() {
        System.out.println("Get lost!");
    }
}

现在在下面的代码中看看sayHello方法。它将Employee实例作为参数并调用 greet 方法,希望它会说Hello everyone!But what we get is Get lost!。这种行为上的改变是因为Employee grumpyEmployee = new GrumpyEmployee();

public class TestFinal {
    static Employee grumpyEmployee = new GrumpyEmployee();

    public static void main(String[] args) {
        TestFinal testFinal = new TestFinal();
        testFinal.sayHello(grumpyEmployee);
    }

    private void sayHello(Employee employee) {
        employee.greet(); //Here you would expect a warm greeting, but what you get is "Get lost!"
    }
}

如果上课了,这种情况是可以避免的。现在你可以想象如果Class 没有被声明为.EmployeefinalStringfinal

于 2018-07-06T21:33:25.660 回答
0

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

于 2017-02-09T13:49:26.747 回答
0

大多数答案都与不变性有关——为什么不能就地更新 String 类型的对象。这里有很多很好的讨论,Java 社区最好采用不变性作为主体。(没有屏住呼吸。)

然而,OP 的问题是为什么它是最终的——为什么它不能被扩展。这里的一些人确实接受了这一点,但我同意 OP 的观点,即这里存在真正的差距。其他语言允许开发人员为类型创建新的名义类型。例如,在 Haskell 中,我可以创建以下在运行时与文本相同的新类型,但在编译时提供绑定安全。

newtype AccountCode = AccountCode Text
newtype FundCode = FundCode Text

因此,我将提出以下建议作为对 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)

于 2017-10-10T23:31:15.650 回答
-2

如果你创建一个字符串一次它会认为,它是一个对象,如果你想修改它,这是不可能的,它会创建一个新的对象。

于 2016-02-24T14:00:21.707 回答