【问题标题】:Is it correct to call java.lang.String immutable?调用 java.lang.String 不可变是否正确?
【发布时间】:2013-02-22 21:36:39
【问题描述】:

This Java tutorial 表示不可变对象在创建后无法更改其状态。

java.lang.String 有一个字段

/** Cache the hash code for the string */
private int hash; // Default to 0

在第一次调用hashCode()方法时初始化,所以创建后会发生变化:

    String s = new String(new char[] {' '});
    Field hash = s.getClass().getDeclaredField("hash");
    hash.setAccessible(true);
    System.out.println(hash.get(s));
    s.hashCode();
    System.out.println(hash.get(s));

输出

0
32

调用String不可变对吗?

【问题讨论】:

  • 反射黑客不计入不变性。
  • 正如@Perception 所说,反射黑客不应该算在内。私有字段中哈希值的缓存不会影响任何非私有方法或状态。
  • 我的一生都是个谎言!
  • 通过类似的技巧,您可以将所有值为 0 的整数神奇地变为 1。将零称为零仍然正确吗?

标签: java immutability


【解决方案1】:

更好的定义是不是对象没有改变,而是它不能被观察改变。它的行为永远不会改变:.substring(x,y) 将始终为该字符串返回相同的内容equals 和所有其他方法。

该变量在您第一次调用.hashcode() 时计算,并被缓存以供进一步调用。这基本上就是他们在函数式编程语言中所说的“memoization”。

反射并不是真正用于“编程”的工具,而是用于元编程(即用于生成程序的编程程序),因此它并不算数。这相当于使用内存调试器更改常量的值。

【讨论】:

    【解决方案2】:

    “不可变”一词含糊不清,无法给出精确定义。

    我建议阅读 Eric Lippert 博客中的 Kinds of Immutability。虽然它在技术上是一篇 C# 文章,但它与提出的问题非常相关。特别是:

    观察不变性:

    假设你有一个对象,它每次都具有以下属性 你在上面调用一个方法,查看一个字段等,你会得到同样的结果 结果。从调用者的角度来看,这样的对象将是 不可变的。但是你可以想象在幕后对象 正在做延迟初始化,记住函数调用的结果 哈希表等。对象的“胆量”可能是完全可变的。

    这有什么关系?真正不可变的对象永远不会改变它们的 内部状态,因此本质上是线程安全的。一个 在幕后可变的对象可能仍然需要 复杂的线程代码以保护其内部可变 如果在两个线程上调用对象“在 同一时间”。

    【讨论】:

    • 我猜除非你是绝对的,否则“不变性”的问题取决于从谁(即哪个对象的)角度来询问它。
    【解决方案3】:

    创建后,String 实例上的所有方法(使用相同的参数调用)将始终提供相同的结果。你不能改变它的行为(使用任何公共方法),所以它总是代表同一个实体。而且它是final,不能被子类化,所以保证所有的实例都是这样的。

    因此从公众的角度来看该对象被认为是不可变的。在这种情况下,内部状态并不重要。

    【讨论】:

    • 啊,是的,但是在这种情况下,他正在使用狡猾的技巧来更改hash 变量,并且hash 变量可以通过hashcode() 观察到。所以根据你的定义,String 是可变的。
    • 即使在我的定义中,我也没有重复 反射不算数的第一条评论。
    【解决方案4】:

    是的,将它们称为不可变是正确的。

    虽然您确实可以访问并修改类的 private ... 和 final ... 变量,但对 String 对象执行此操作是不必要且极其不明智的事情。通常假设没有人会疯狂到这样做。

    从安全的角度来看,修改字符串状态所需的反射调用都执行安全检查。除非您错误地实现了您的沙箱,否则对于不受信任的代码的调用将被阻止。因此,您应该担心这是不受信任的代码可能破坏沙盒安全性的一种方式。

    还值得注意的是,JLS 声明使用反射来更改 final,可能会破坏事物(例如在多线程中)或可能没有任何效果。

    【讨论】:

    • 我使用了公共方法 hashCode() 来更改哈希字段。 Docs 说不可变对象在创建后无法保持状态。哈希字段是否是 String 实例状态的一部分?
    • @EvgeniyDorofeev:除了少数例外(例如delay 方法),方法执行需要时间这一事实不被视为其行为的一部分。散列字段为零的string 对象与散列字段不为零的对象之间唯一可观察到的区别是执行其hashCode() 方法所需的时间量。请注意,如果计算出的哈希值在某种程度上取决于第一次调用 hashCode() 的时间,那么 表示可变状态,但事实并非如此。
    • @EvgeniyDorofeev - 如果您正在谈论 (String),答案取决于您的观点。见阿尼的回答。还要记住,Java 教程不是规范。它是(更多)权威文件中信息的简化版本。如果您查看 JLS,immutability 不是核心属性。相反,它是从类 API 设计及其实现方式中出现的类的属性。 Java 教程的主要目的是帮助初学者......不是一个权威的文本。
    【解决方案5】:

    从使用反射的开发人员的角度来看,调用String 不可变是正确的。每天都有实际的 Java 开发人员使用反射来编写真正的软件。将反射视为“黑客”是荒谬的。 然而,从不使用反射的开发者的角度来看,调用String immutable 是正确的。假设String 是不可变的是否有效取决于上下文。

    不变性是一个抽象概念,因此不能在绝对意义上适用于任何具有物理形式的事物(参见ship of Theseus)。诸如对象、变量和方法之类的编程语言构造在存储介质中以比特的形式物理存在。数据退化是发生在所有存储介质上的物理过程,因此不能说数据是真正不可变的。此外,在实践中几乎总是有可能颠覆旨在防止特定数据突变的编程语言功能。相比之下,数字 3 是 3,一直是 3,也永远是 3。

    应用于程序数据时,不变性应被视为有用的假设,而不是基本属性。例如,如果假设String 是不可变的,则可以缓存其哈希码以供重用,并避免以后再次重新计算其哈希码的成本。几乎所有重要的软件都依赖于某些数据在特定时间段内不会发生变异的假设。软件开发人员通常假设程序的code segment 在执行时不会更改,除非他们正在编写自修改代码。了解哪些假设在特定环境中有效是软件开发的一个重要方面。

    【讨论】:

      【解决方案6】:

      它不能从外部修改,它是一个最终类,所以它不能被子类化和可变。 Theese 是不变性的两个要求。反射被认为是一种 hack,它不是一种正常的开发方式。

      【讨论】:

        【解决方案7】:

        一个类可以是不可变的,但仍然具有可变字段,只要它不提供对其可变字段的访问。

        它在设计上是不可变的。如果您使用反射(获取声明的字段并重置其可访问性),则您正在规避它的设计。

        【讨论】:

          【解决方案8】:

          反射将允许您更改任何私有字段的内容。因此在 Java 中调用任何对象不可变是否正确?

          不变性是指由应用程序发起或可感知的更改。

          在字符串的情况下,特定实现选择延迟计算哈希码的事实对于应用程序来说是无法察觉的。我会更进一步,说一个由对象递增的内部变量——但从不暴露并且从不以任何其他方式使用——在“不可变”对象中也是可以接受的。

          【讨论】:

          • 其实我是用公共方法来改变字符串的状态
          • @EvgeniyDorofeev - 不在您展示的示例中。至少,不是 String 类的公共方法。如果 String 类的公共方法允许您更改其状态,那么我同意:它不是不可变的。但我不知道有什么这样的方法。
          • 但我在示例中调用了公共方法 hashCode() 并且哈希字段发生了变化。
          • @EvgeniyDorofeev - 我似乎错过了返回零的hashCode() 的第一个电话......让我看看......不,我没有看到它。我看到你访问一个类的内部值,然后调用hashCode()。这与我的回答并不矛盾。
          【解决方案9】:

          是的,它是正确的。当您像在示例中那样修改 String 时,会创建一个新 String 但旧的 String 保持其值。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-08-19
            • 1970-01-01
            • 2011-04-05
            • 2011-05-20
            • 2014-06-28
            • 2020-06-05
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多