16

这个 Java 教程 说不可变对象在创建后无法更改其状态。

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

4

9 回答 9

13

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

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

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

于 2013-03-07T15:55:44.593 回答
8

术语“不可变”非常模糊,无法给出精确的定义。

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

观察不变性:

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

有什么关系?真正的不可变对象根本不会改变它们的内部状态,因此本质上是线程安全的。如果对象“同时”在两个线程上调用,则在后台可变的对象可能仍需要具有复杂的线程代码,以保护其内部可变状态免受损坏。

于 2013-03-08T05:37:21.020 回答
3

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

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

于 2013-03-07T15:53:42.360 回答
1

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

虽然确实可以访问和修改类的private... 和final... 变量,但对对象执行此操作是不必要且极其不明智的事情String。人们普遍认为,没有人会疯狂到足以做到这一点。

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

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

于 2013-03-07T15:51:50.290 回答
1

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

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

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

于 2014-04-01T20:41:54.487 回答
0

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

于 2013-03-07T15:45:50.717 回答
0

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

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

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

于 2013-03-07T15:46:10.403 回答
0

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

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

于 2013-03-07T15:46:52.523 回答
0

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

于 2013-07-10T06:20:54.173 回答