调用java.lang.String不可变是正确的吗?

时间:2013-03-07 15:42:57

标签: java immutability

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 immutable是否正确?

9 个答案:

答案 0 :(得分:13)

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

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

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

答案 1 :(得分:8)

“不可变”一词含糊不清,不允许精确定义。

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

  

观察不变性:

     

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

     

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

答案 2 :(得分:3)

创建后,String实例上的所有方法(使用相同参数调用)将始终提供相同的结果。您无法更改其行为(使用任何公共方法),因此它将始终表示同一实体。它也是final并且不能被子类化,所以保证所有实例的行为都是这样的。

因此从公共视图该对象被视为不可变。在这种情况下,内部状态并不重要。

答案 3 :(得分:1)

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

虽然您可以访问并修改某个类的private ...和final ...变量,但对{{{{{{{{{{{{ 1}}对象。通常假设没有人会疯狂地做到这一点。

从安全角度来看,修改String状态所需的反射调用都会执行安全检查。除非您错过了沙盒的实施,否则将阻止对不受信任的代码进行调用。所以你应该担心这是不受信任的代码破坏沙盒安全性的一种方式。

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

答案 4 :(得分:1)

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

不变性是一个抽象概念,因此不能绝对意义地应用于任何具有物理形式的东西(参见ship of Theseus)。像对象,变量和方法这样的编程语言结构在物理上存在于存储介质中的位。数据降级是发生在所有存储介质上的物理过程,因此不能说任何数据都不是真正不可变的。此外,在实践中几乎总是可能破坏旨在防止特定数据突变的编程语言特征。相反,数字3是3,一直是3,并且总是3。

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

答案 5 :(得分:0)

它不能从外部修改而且它是最终类,因此它不能被子类化并且变得可变。 Theese是不可变性的两个要求。反思被认为是一种黑客,它不是一种正常的发展方式。

答案 6 :(得分:0)

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

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

在string的情况下,特定实现选择懒惰地计算哈希码的事实对于应用程序是不可察觉的。我会更进一步,并说一个内部变量由对象增加 - 但从未暴露,从未以任何其他方式使用 - 在“不可变”对象中也是可以接受的。

答案 7 :(得分:0)

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

它的设计是不可改变的。如果你使用Reflection(获取声明的Field并重置其可访问性),那么你就是在规避它的设计。

答案 8 :(得分:0)

是的,这是对的。当您在示例中修改字符串时,会创建一个新的String,但旧的String会保持其值。