英文:
In Java, is passing an object's non-primitive containing field to a method passed as an object handle and if so how does that affect its mutability?
问题
如果作为对象句柄传递了包含非原始类型字段的对象,并且这些字段引用了该字段的对象,那么在最初传递的字段被更新/更改后,这些对象是否容易在此后被更改?
public class MutableDog
{
public String name;
public String color;
public MutableDog(String name, String color)
{
this.name = name;
this.color = color;
}
}
public class ImmutableDog // 这些对象的字段是否真的安全免受更改?
{
private final String name;
private final String color;
public ImmutableDog(MutableDog doggy)
{
this.name = doggy.name;
this.color = doggy.color;
}
public String getColor()
{
return this.color;
}
}
public static void main(String[] args)
{
MutableDog aMutableDog = new MutableDog("Courage", "Pink");
ImmutableDog anImmutableDog = new ImmutableDog(aMutableDog);
aMutableDog.color = "Pink/Black";
anImmutableDog.getColor().equals(aMutableDog.color); // true or false?
}
实质上,ImmutableDog
是否 真正 是不可变的?在示例中,使用了字符串。使用可变对象(例如 Collection
)会有所不同吗?
这个问题是对 这个 回答的回应。
英文:
If objects' non-primitive containing fields are passed as object handles that reference that field's object, is it susceptible to being changed after the fact if the originally passed field is updated/changed?
public class MutableDog
{
public String name;
public String color;
public MutableDog(String name, String color)
{
this.name = name;
this.color = color;
}
}
public class ImmutableDog // are fields of these objects truly safe from changing?
{
private final String name;
private final String color;
public ImmutableDog(MutableDog doggy)
{
this.name = doggy.name;
this.color = doggy.color;
}
public String getColor()
{
return this.color;
}
}
public static void main(String[] args)
{
MutableDog aMutableDog = new MutableDog("Courage", "Pink");
ImmutableDog anImmutableDog = new ImmutableDog(aMutableDog);
aMutableDog.color = "Pink/Black";
anImmutableDog.getColor().equals(aMutableDog.color); // true or false?
}
Essentially, is ImmutableDog
truly immutable? In the example, Strings are used. Would using a mutable object, such as a Collection
, make a difference?
This question is in response to this answer.
答案1
得分: 2
ImmutableDog
是真正的不可变的,即使它可以接收来自可变对象的字符串。这是因为String
是不可变的。这也展示了不可变性的一个重要好处 - 您可以在不担心它们会突然改变的情况下传递不可变对象。
您可能会认为ImmutableDog
中的字段可以通过设置MutableDog
实例的字段来改变:
aMutableDog.color = "Pink/Black";
然而,"Pink/Black"
是一个不同的字符串实例,与分配给ImmutableDog
的那个不同,所以ImmutableDog
不会改变。
另一方面,如果ImmutableDog
具有可变类型的字段,则它不再是真正的不可变的。
例如,这是您相同的代码,但使用了StringBuilder
:
public class MutableDog
{
public StringBuilder name;
public StringBuilder color;
public MutableDog(StringBuilder name, StringBuilder color)
{
this.name = name;
this.color = color;
}
}
public class ImmutableDog // 这些对象的字段是否真的安全,不会改变?
{
private final StringBuilder name;
private final StringBuilder color;
public ImmutableDog(MutableDog doggy)
{
this.name = doggy.name;
this.color = doggy.color;
}
public String getColor()
{
return this.color.toString();
}
}
public static void main(String[] args)
{
MutableDog aMutableDog = new MutableDog("Courage", "Pink");
ImmutableDog anImmutableDog = new ImmutableDog(aMutableDog);
aMutableDog.color.append(" and Black");
anImmutableDog.getColor().equals(aMutableDog.color);
}
现在,不可变狗的颜色将似乎发生变化。您仍然可以通过在构造函数中复制字符串构建器来防御:
public ImmutableDog(MutableDog doggy)
{
this.name = new StringBuilder(doggy.name);
this.color = new StringBuilder(doggy.color);
}
然而,这仍然允许您(无意中)修改ImmutableDog
类内部的字符串构建器。
所以在不可变类中不要存储可变类。
英文:
ImmutableDog
is truly immutable, even though it can receive strings from a mutable object. This is because String
s are immutable. This also demonstrate one of the great benefits of immutability - you can pass immutable objects around without worrying that they will suddenly change.
You might be thinking that the fields in ImmutableDog
could be somehow changed by setting the fields of the MutableDog
instance:
aMutableDog.color = "Pink/Black";
However, "Pink/Black"
is a different string instance from the one assigned to ImmutableDog
here, so ImmutableDog
won't change.
<hr>
On the other hand, if ImmutableDog
has a field of a mutable type, then it is not truly immutable anymore.
For example, here's your same code, but with StringBuilder
:
public class MutableDog
{
public StringBuilder name;
public StringBuilder color;
public MutableDog(StringBuilder name, StringBuilder color)
{
this.name = name;
this.color = color;
}
}
public class ImmutableDog // are fields of these objects truly safe from changing?
{
private final StringBuilder name;
private final StringBuilder color;
public ImmutableDog(MutableDog doggy)
{
this.name = doggy.name;
this.color = doggy.color;
}
public String getColor()
{
return this.color.toString();
}
}
public static void main(String[] args)
{
MutableDog aMutableDog = new MutableDog("Courage", "Pink");
ImmutableDog anImmutableDog = new ImmutableDog(aMutableDog);
aMutableDog.color.append(" and Black");
anImmutableDog.getColor().equals(aMutableDog.color);
}
Now the immutable dog's color will appear to change. You can still defend against this by copying the string builder in the constructor:
public ImmutableDog(MutableDog doggy)
{
this.name = new StringBuilder(doggy.name);
this.color = new StringBuilder(doggy.color);
}
However, this will still allow you to (accidentally) modify the string builder inside the ImmutableDog
class.
So just don't store mutable classes in immutable classes.
答案2
得分: 2
很简单:在Java中,任何引用类型最终都会指向某个对象。
如果该对象具有可变状态,那么对它的任何引用都可以用来更改该状态。
因此,你是正确的:只是在每个字段声明之前加上private final
并不能使该类本身变成不可变的。
在你的示例中,String类是不可变的(除了使用Unsafe
进行非常模糊的技巧之外)。在分配了name
和color
之后,无法更改引用,也无法更改它们所指向的对象。
但当然了:如果类型是List
,例如,底层对象很可能在其他地方被改变。如果你想防止这种情况,你必须为传入的列表创建一个副本,并保留对该副本的引用。
英文:
It is very simple: any reference type in java, in the end, points to some object.
If that object has mutable state, then any reference to it can be used to change that state.
Thus, you are correct: just putting private final
before each field declaration doesn't necessarily make that class itself immutable.
In your example, the String class is immutable (besides very obscure tricks using Unsafe
). After name
and color
are assigned, the references can't be changed, and the objects they point to can't be changed either.
But of course: if the type was List
for example, it would very well be possible that the underlying object is changed elsewhere. If you want to prevent that, you have to create a copy of that incoming list for example, and keep a reference to that.
答案3
得分: 1
以下是翻译好的内容:
在所有情况和情境下都是真正的不可变的吗?不是。 在一定程度上是不可变的吗?是。
只要你只做变量赋值,不可变的狗确实总是保持不变的。这通常是由于传值语义,这在这里有很好的解释。
当你复制ImmutableDog
中color
的引用时,实际上是在复制句柄。然后,通过为可变的狗分配Pink/Black
值来修改颜色时,可变的狗的句柄会改变,但不可变的狗仍然保持原始句柄,指向原始颜色。
对于String
类型,情况更进一步,因为字符串是不可变的。因此,在字符串上调用任何方法都保证不会修改原始值。因此,从字符串的角度来看,是的,不可变的狗是真正不可变的,是可以信任的。
**集合确实会有所不同。**如果我们稍微更改类的设计:
public class MutableDog {
public String name;
public List<String> acceptedMeals;
public MutableDog(String name, List<String> acceptedMeals) {
this.name = name;
this.acceptedMeals = new ArrayList<>(acceptedMeals);
}
}
public class ImmutableDog {
private final String name;
private final Iterable<String> acceptedMeals;
public ImmutableDog(MutableDog doggy) {
this.name = doggy.name;
this.acceptedMeals = doggy.acceptedMeals;
}
public Iterable<String> getAcceptedMeals() {
return this.acceptedMeals;
}
}
并执行以下代码:
public static void main(String[] args) throws IOException {
MutableDog mutableDog = new MutableDog("Spot", Collections.singletonList("Chicken"));
ImmutableDog immutableDog = new ImmutableDog(mutableDog);
immutableDog.getAcceptedMeals().forEach(System.out::println);
mutableDog.acceptedMeals.add("Pasta");
immutableDog.getAcceptedMeals().forEach(System.out::println);
}
将会打印出以下内容:
Chicken
Chicken
Pasta
这是因为ImmutableDog
的构造函数复制了对acceptedMeals
集合的句柄,但新的句柄指向与原始集合相同的位置。因此,当你通过可变的狗进行修改调用时,由于ImmutableDog
指向内存中相同的位置,它的值也可能被修改。
在这种特定情况下,你可以通过执行集合的深拷贝来避免这种副作用,而不仅仅是复制引用句柄:
public ImmutableDog(MutableDog doggy) {
this.name = doggy.name;
this.acceptedMeals = new ArrayList<>(doggy.acceptedMeals);
}
通过这样做,上面呈现的相同main
方法只会打印出以下内容:
Chicken
Chicken
英文:
Is it truly immutable in all senses and situations? No. Is it immutable to a certain degree? Yes.
As long as you're doing only variable assignment, the immutable dog is indeed always going to remain unchanged. This is generally due to the pass-by-value semantics, which is greatly explained here.
When you copy the reference of the color
in the ImmutableDog
dog, you essentially copy the handle. When you then modify the color through the mutable dog by assigning the Pink/Black
value to it, the handle of the mutable dog changes, but immutable dog still holds the original handle, pointing to original color.
With the String
type, this goes a step further, since strings are immutable. Therefore calling any method on the string is guaranteed not to modify the original value. So in terms of string, yes, the immutable dog is truly immutable and can be trusted.
Collections DO make a difference. If we change the design of your classes slightly:
public class MutableDog {
public String name;
public List<String> acceptedMeals;
public MutableDog(String name, List<String> acceptedMeals) {
this.name = name;
this.acceptedMeals = new ArrayList<>(acceptedMeals);
}
}
public class ImmutableDog {
private final String name;
private final Iterable<String> acceptedMeals;
public ImmutableDog(MutableDog doggy) {
this.name = doggy.name;
this.acceptedMeals = doggy.acceptedMeals;
}
public Iterable<String> getAcceptedMeals() {
return this.acceptedMeals;
}
}
and execute the following code:
public static void main(String[] args) throws IOException {
MutableDog mutableDog = new MutableDog("Spot", Collections.singletonList("Chicken"));
ImmutableDog immutableDog = new ImmutableDog(mutableDog);
immutableDog.getAcceptedMeals().forEach(System.out::println);
mutableDog.acceptedMeals.add("Pasta");
immutableDog.getAcceptedMeals().forEach(System.out::println);
}
the following is printed out:
Chicken
Chicken
Pasta
This is due to the ImmutableDog
's constructor copying handle to the acceptedMeals
collection, but the new handle points to the same location as the original collection. So when you call modification through the mutable dog, since the ImmutableDog
points to the same place in memory, its values are also presumably modified.
In this specific case, you could circumvent this side effect by performing a deep copy of the collection, rather than simply copying the reference handle:
public ImmutableDog(MutableDog doggy) {
this.name = doggy.name;
this.acceptedMeals = new ArrayList<>(doggy.acceptedMeals);
}
By doing this, the same main
method presented above prints only the following:
Chicken
Chicken
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论