通过引用传递—在java中,是将对象的非原语包含字段传递给作为对象句柄传递的方法,如果是这样的话,会如何影响其可变性?

xmq68pz9  于 2021-06-30  发布在  Java
关注(0)|答案(3)|浏览(380)

如果对象的非原语包含的字段作为引用该字段的对象的对象句柄传递,那么如果最初传递的字段被更新/更改,那么它是否容易在事后被更改?

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 ,有什么不同吗?
这个问题是对这个答案的回应。

juzqafwq

juzqafwq1#

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 // 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 班级。
所以不要将可变类存储在不可变类中。:)

vsaztqbk

vsaztqbk2#

它非常简单:java中的任何引用类型最终都指向某个对象。
如果该对象具有可变状态,那么对它的任何引用都可以用来更改该状态。
因此,你是对的:只是把 private final 在此之前,每个字段声明不一定使该类本身不可变。
在您的示例中,string类是不可变的(除了使用 Unsafe ). 之后 name 以及 color 被指定时,引用不能更改,它们指向的对象也不能更改。
当然,如果类型是 List 例如,底层对象很可能在其他地方更改。如果您想防止这种情况发生,您必须创建一个传入列表的副本,例如,并保留对该列表的引用。

qlckcl4x

qlckcl4x3#

它真的在所有感官和情境中都是不变的吗?不,它在一定程度上是不变的吗?对。
只要您只执行变量赋值,不变的dog实际上总是保持不变的。这通常是由于传递值语义造成的,本文对此进行了详细解释。
当您复制 colorImmutableDog 狗,你基本上是在模仿把手。然后通过指定 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

相关问题