在 Java 中,将对象的非原始包含字段传递给作为对象句柄传递的方法,如果是这样,这将如何影响其可变性?
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?
如果对象的非原始包含字段作为引用该字段对象的对象句柄传递,如果最初传递的字段是 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?
}
本质上,ImmutableDog
真的是不可变的吗?在示例中,使用了字符串。使用可变对象(例如 Collection
会有所不同吗?
此问题是对 this 答案的回应。
ImmutableDog
是真正不可变的,即使它可以从可变对象接收字符串。这是因为 String
s 是不可变的。这也证明了不可变性的一大好处 - 您可以传递不可变对象而不用担心它们会突然改变。
您可能认为 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 // 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);
}
现在不可变狗的颜色似乎会发生变化。您仍然可以通过在构造函数中复制字符串生成器来防御此问题:
public ImmutableDog(MutableDog doggy)
{
this.name = new StringBuilder(doggy.name);
this.color = new StringBuilder(doggy.color);
}
但是,这仍然允许您(意外地)修改 ImmutableDog
class.
中的字符串生成器
所以不要将可变 classes 存储在不可变 classes 中。 :)
很简单:java中的任意引用类型,最后指向某个对象。
如果该对象具有可变状态,那么 任何 对它的引用都可用于更改该状态。
因此,你是对的:在每个字段声明之前放置 private final
并不一定会使 class 本身不可变。
在您的示例中,字符串 class 是不可变的(除了使用 Unsafe
的非常晦涩的技巧)。 name
和color
赋值后,引用不能改变,指向的对象也不能改变。
但是当然:例如,如果类型是 List
,则底层对象很可能会在 其他地方 发生更改。例如,如果你想防止这种情况发生,你必须创建一个 copy 该传入列表,并保留对该列表的引用。
它真的在所有感官和情况下都是不变的吗? 没有。它在某种程度上是不可变的吗? 是的。
只要你只做变量赋值,不可变狗确实总是会保持不变。这一般是由于 pass-by-value 语义,对此有很大的解释 here.
当您在 ImmutableDog
狗中复制 color
的引用时,实际上是在复制句柄。然后,当您通过可变狗修改颜色并为其分配 Pink/Black
值时,可变狗的句柄发生变化,但不可变狗仍保持原始句柄,指向原始颜色。
对于 String
类型,这更进一步,因为字符串是不可变的。因此,保证在字符串上调用任何方法都不会修改原始值。所以就字符串而言,是的,不可变的狗是真正的不可变的,可以信任。
Collections 有所作为。 如果我们稍微更改一下您的 类 的设计:
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
collection,但新句柄指向与原始 collection 相同的位置.所以当你通过mutable dog调用modification时,由于ImmutableDog
在内存中指向同一个地方,所以它的值估计也被修改了
在这种特定情况下,您可以通过执行 collection 的深层复制来避免这种副作用,而不是简单地复制引用句柄:
public ImmutableDog(MutableDog doggy) {
this.name = doggy.name;
this.acceptedMeals = new ArrayList<>(doggy.acceptedMeals);
}
通过这样做,上述相同的 main
方法仅打印以下内容:
Chicken
Chicken
如果对象的非原始包含字段作为引用该字段对象的对象句柄传递,如果最初传递的字段是 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?
}
本质上,ImmutableDog
真的是不可变的吗?在示例中,使用了字符串。使用可变对象(例如 Collection
会有所不同吗?
此问题是对 this 答案的回应。
ImmutableDog
是真正不可变的,即使它可以从可变对象接收字符串。这是因为 String
s 是不可变的。这也证明了不可变性的一大好处 - 您可以传递不可变对象而不用担心它们会突然改变。
您可能认为 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 // 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);
}
现在不可变狗的颜色似乎会发生变化。您仍然可以通过在构造函数中复制字符串生成器来防御此问题:
public ImmutableDog(MutableDog doggy)
{
this.name = new StringBuilder(doggy.name);
this.color = new StringBuilder(doggy.color);
}
但是,这仍然允许您(意外地)修改 ImmutableDog
class.
所以不要将可变 classes 存储在不可变 classes 中。 :)
很简单:java中的任意引用类型,最后指向某个对象。
如果该对象具有可变状态,那么 任何 对它的引用都可用于更改该状态。
因此,你是对的:在每个字段声明之前放置 private final
并不一定会使 class 本身不可变。
在您的示例中,字符串 class 是不可变的(除了使用 Unsafe
的非常晦涩的技巧)。 name
和color
赋值后,引用不能改变,指向的对象也不能改变。
但是当然:例如,如果类型是 List
,则底层对象很可能会在 其他地方 发生更改。例如,如果你想防止这种情况发生,你必须创建一个 copy 该传入列表,并保留对该列表的引用。
它真的在所有感官和情况下都是不变的吗? 没有。它在某种程度上是不可变的吗? 是的。
只要你只做变量赋值,不可变狗确实总是会保持不变。这一般是由于 pass-by-value 语义,对此有很大的解释 here.
当您在 ImmutableDog
狗中复制 color
的引用时,实际上是在复制句柄。然后,当您通过可变狗修改颜色并为其分配 Pink/Black
值时,可变狗的句柄发生变化,但不可变狗仍保持原始句柄,指向原始颜色。
对于 String
类型,这更进一步,因为字符串是不可变的。因此,保证在字符串上调用任何方法都不会修改原始值。所以就字符串而言,是的,不可变的狗是真正的不可变的,可以信任。
Collections 有所作为。 如果我们稍微更改一下您的 类 的设计:
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
collection,但新句柄指向与原始 collection 相同的位置.所以当你通过mutable dog调用modification时,由于ImmutableDog
在内存中指向同一个地方,所以它的值估计也被修改了
在这种特定情况下,您可以通过执行 collection 的深层复制来避免这种副作用,而不是简单地复制引用句柄:
public ImmutableDog(MutableDog doggy) {
this.name = doggy.name;
this.acceptedMeals = new ArrayList<>(doggy.acceptedMeals);
}
通过这样做,上述相同的 main
方法仅打印以下内容:
Chicken
Chicken