这是一个不可变的类吗?

发布于 2024-09-25 14:54:06 字数 1786 浏览 8 评论 0原文

我不知道不可变类应该是什么样子,但我很确定这个是这样的。我说得对吗?如果我不这样做,请指定应添加/删除的内容。

import java.io.Serializable;

public class Triangle implements IShape, Serializable {
    private static final long serialVersionUID = 0x100;

    private Point[] points;

    public Triangle(Point a, Point b, Point c) {
        this.points = new Point[]{a, b, c};
    }

    @Override
    public Point[] getPoints() {
        return this.points;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) return false;
        if (this == obj) return true;
        if (getClass() != obj.getClass()) return false;
        Point[] trianglePoints = ((Triangle) obj).getPoints();
        for (int i = 0; i < points.length; i++){
            if (!points[i].equals(trianglePoints[i])) return false;
        }
        return true;
    }
}

这能起到作用吗?

@Override
    public Point[] getPoints() {
        Point[] copyPoint = {
                new Point(points[0]),
                new Point(points[1]),
                new Point(points[2]),};
        return copyPoint;
    }

点类:

import java.io.Serializable;

public class Point implements Serializable {
    private static final long serialVersionUID = 0x100;

    public int x;
    public int y;
    public int z;

    public Point(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public Point(Point that) {
        this.x = that.x;
        this.y = that.y;
        this.z = that.z;
    }

    public boolean equals(Object obj) { 
        // assume this is a typical, safe .equals implementation
        // that compares the coordinates in this instance to the
        // other instance
        return true;
    }
}

I have no idea what immutable class should look like but am pretty sure this one is. Am I right? If I'm not please specify what should be added/removed.

import java.io.Serializable;

public class Triangle implements IShape, Serializable {
    private static final long serialVersionUID = 0x100;

    private Point[] points;

    public Triangle(Point a, Point b, Point c) {
        this.points = new Point[]{a, b, c};
    }

    @Override
    public Point[] getPoints() {
        return this.points;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) return false;
        if (this == obj) return true;
        if (getClass() != obj.getClass()) return false;
        Point[] trianglePoints = ((Triangle) obj).getPoints();
        for (int i = 0; i < points.length; i++){
            if (!points[i].equals(trianglePoints[i])) return false;
        }
        return true;
    }
}

Will this do the trick?

@Override
    public Point[] getPoints() {
        Point[] copyPoint = {
                new Point(points[0]),
                new Point(points[1]),
                new Point(points[2]),};
        return copyPoint;
    }

Point class:

import java.io.Serializable;

public class Point implements Serializable {
    private static final long serialVersionUID = 0x100;

    public int x;
    public int y;
    public int z;

    public Point(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public Point(Point that) {
        this.x = that.x;
        this.y = that.y;
        this.z = that.z;
    }

    public boolean equals(Object obj) { 
        // assume this is a typical, safe .equals implementation
        // that compares the coordinates in this instance to the
        // other instance
        return true;
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

音盲 2024-10-02 14:54:06

不,您可以更改 Points 数组中的内容。如果要使其不可变,请让 getter 分发 Points 数组的副本,而不是原始数组。

试试这个:

Triangle triangle = new Triangle(a, b, c);
triangle.getPoints()[1] = null;
System.out.println(Arrays.toString(triangle.getPoints()));

Point 也需要是不可变的(正如 Nikita Rybak 指出的那样)。有关如何复制数组,请参阅如何在 Java 中复制数组

No, you can change what's in the Points array. If you want to make it immutable, have the getter hand out a copy of the Points array, not the original.

try this:

Triangle triangle = new Triangle(a, b, c);
triangle.getPoints()[1] = null;
System.out.println(Arrays.toString(triangle.getPoints()));

Also Point needs to be immutable (as Nikita Rybak points out). For how to copy arrays see how to copy an array in Java.

空城之時有危險 2024-10-02 14:54:06

不,不是。您公开 Point[],调用者可以修改其内容。另外,你的类不是最终的,所以有人可以通过子类化它来破坏它。

No, it's not. You expose the Point[] and a caller could modify its contents. Also, your class is not final, so someone could subvert it by subclassing it.

计㈡愣 2024-10-02 14:54:06

不,它绝对是可变的。

您不仅公开了实际的 Point[] 数组,而且不进行防御性复制(Bloch 2nd ed.,第 39 项)通过构造函数获取 Point 对象本身。

  1. Point[] 数组可以有项目
    删除或添加到它,所以它是
    可变的。
  2. 你可以传入a点,
    b 和 c,然后调用 setX() 或 setY()
    让他们在之后更改数据
    建造。

No, it's definitely mutable.

Not only do you expose the actual Point[] array, you don't defensive-copy (Bloch 2nd ed., Item 39) the Point objects themselves when taking them in via the constructor.

  1. The Point[] array could have items
    removed or added to it, so it's
    mutable.
  2. You could pass in Points a,
    b, and c, then call setX() or setY()
    on them to change their data after
    construction.
过期情话 2024-10-02 14:54:06

关闭。一方面,不可变类应该将其字段设为final,但这不是必需的。

但是,您通过 getter 公开了一个数组,并且它不是不可变的。使用 Arrays.copyOf(数组,长度)

@Override
public Point[] getPoints() {
    return Arrays.copyOf(this.points,this.points.length);
}

Close. For one thing, an immutable class should make it's fields final, but that's not a requirement.

However, you are exposing an array through the getter, and that is not immutable. Make a defensive copy using Arrays.copyOf(array, length):

@Override
public Point[] getPoints() {
    return Arrays.copyOf(this.points,this.points.length);
}
怀里藏娇 2024-10-02 14:54:06

Guava 的帮助下,我要做的就是使此类不可变。我从您发布的代码中的 @Override 中看到 IShape 似乎需要来自 getPoints()< 的 Point[] /code> 方法,但为了示例,我忽略了它,因为使用对象数组是一个相当糟糕的主意,特别是如果您想要不可变性(因为它们不能是不可变的)。

public final class Triangle implements IShape, Serializable {
  private final ImmutableList<Point> points;

  public Triangle(Point a, Point b, Point c) {
    this.points = ImmutableList.of(a, b, c);
  }

  public ImmutableList<Point> getPoints() {
    return this.points;
  }

  // ...
}

Point 也应该更像:

public final class Point implements Serializable {
  /*
   * Could use public final here really, but I prefer
   * consistent use of methods.
   */
  private final int x;
  private final int y;
  private final int z;

  public Point(int x, int y, int z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }

  // getters, etc.
}

Here's what I'd do to make this class immutable, with the help of Guava. I see from the @Override in the code you posted that IShape seems to require a Point[] from the getPoints() method, but I'm ignoring that for the sake of example since the use of object arrays is a rather poor idea, especially if you want immutability (since they cannot be immutable and all).

public final class Triangle implements IShape, Serializable {
  private final ImmutableList<Point> points;

  public Triangle(Point a, Point b, Point c) {
    this.points = ImmutableList.of(a, b, c);
  }

  public ImmutableList<Point> getPoints() {
    return this.points;
  }

  // ...
}

Point should also be more like:

public final class Point implements Serializable {
  /*
   * Could use public final here really, but I prefer
   * consistent use of methods.
   */
  private final int x;
  private final int y;
  private final int z;

  public Point(int x, int y, int z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }

  // getters, etc.
}
云柯 2024-10-02 14:54:06

为了成为一个不可变的类,仅仅保证你的方法不改变对象是不够的。除了让所有字段都是私有的并且方法不允许更改之外,您还必须保证子类具有相同的不变性承诺。这包括使类本身成为最终类,并确保不会返回对字段的引用。

这篇文章对此进行了简短但出色的处理:

http:// www.javaranch.com/journal/2003/04/immutable.htm

In order to be an immutable class, it is not enough that your methods promise not to change the object. In addition to having all fields be private and the methods not allow changing, you must also guarantee that the subclasses have the same promise of immutability. This includes making the class itself final, and ensuring that no references to the fields are ever returned.

A short, but excellent treatment of this can be found in this article:

http://www.javaranch.com/journal/2003/04/immutable.htm

苍暮颜 2024-10-02 14:54:06

您不仅需要提供内部化数组的不可变副本,还需要确保 Point 对象是不可变的。

考虑标准 Java API 中 Point 类的以下用法:

Point a = new Point(1,1);
Point b = new Point(1,1);
Point c = new Point(1,1);
Triangle triangle = new Triangle(a, b, c);
System.out.println(Arrays.toString(triangle.getPoints()));
c.setLocation(99,99);
System.out.println(Arrays.toString(triangle.getPoints()));

Not only do you need to provide an immutable copy of the internalised array, you also need to make sure that the Point object is immutable.

Consider the following use of the Point class in the standard Java API:

Point a = new Point(1,1);
Point b = new Point(1,1);
Point c = new Point(1,1);
Triangle triangle = new Triangle(a, b, c);
System.out.println(Arrays.toString(triangle.getPoints()));
c.setLocation(99,99);
System.out.println(Arrays.toString(triangle.getPoints()));
初相遇 2024-10-02 14:54:06

它不是不可变的,因为......

Triangle t1 = new Triangle(new Point(0,0), new Point(0, 10), new Point(10, 10));
Triangle t2 = t1;

System.out.println( t1.getPoints()[0] );  // -> 0

t2.getPoints()[0].x = 10;

System.out.println( t1.getPoints()[0] );  // -> 10

因此,该类不是不可变的,因为您可以更改实例的状态(公开的内部Point[]),这也会更改状态对同一实例的引用。

为了使其成为真正的不可变类,您需要方法来分别从每个点获取 X 和 Y,例如:

public int getPointX(int point) { return points[point].x; }
public int getPointY(int point) { return points[point].y; }

或者

public Point getPoint(int point) { return new Point(points[point]); }

返回 的副本,就像您在编辑中建议的那样。

It is not immutable because ...

Triangle t1 = new Triangle(new Point(0,0), new Point(0, 10), new Point(10, 10));
Triangle t2 = t1;

System.out.println( t1.getPoints()[0] );  // -> 0

t2.getPoints()[0].x = 10;

System.out.println( t1.getPoints()[0] );  // -> 10

Thus the class is not immutable because you can change the state of an instance (internal Point[] exposed) and this also changes the state of a reference to the same instance.

To make it a true immutable class, you would need methods to separately get X and Y from each point, for example:

public int getPointX(int point) { return points[point].x; }
public int getPointY(int point) { return points[point].y; }

or

public Point getPoint(int point) { return new Point(points[point]); }

or return a copy of the points like you suggested in your edit.

不疑不惑不回忆 2024-10-02 14:54:06

除了其他人已经指出的内容之外,您还应该:

  • 将您的 Triangle 类设为 final 以防止子类创建可变的 Triangle。
  • 将所有字段声明为final,以捕获类本身对字段的意外修改。

在《Effective Java》中,Joshua Bloch 在第 15 项:最小化可变性中提供了不可变类的一般规则列表。

In addition to what others have already noted, you should:

  • Make your Triangle class final to prevent the creation of mutable Triangles by subclasses.
  • Declare all the fields final, to catch accidental modification of fields by the class itself.

In "Effective Java," Joshua Bloch provides a list of rules for immutable classes in general, in Item 15: Minimize Mutability.

国粹 2024-10-02 14:54:06

1)使成员成为私有和最终的 - 所以

private Point[] points; //should be 
private final Point[] points;

2)使类成为最终的,这样它就不能被子类化

3)对可变成员(数组)的独占访问 - 意味着返回可变成员的副本而不是对可变成员的引用

为了最好地处理这个主题请参阅 Joshua Bloch,《Effective Java》- 第 15 条

1) Make members private and final - so

private Point[] points; //should be 
private final Point[] points;

2) Make class final so it cannot be sub-classed

3) Exclusive access to mutable members (array) - meaning return copy of and not the reference to mutable members

For the best treatment of this subject refer to Joshua Bloch, Effective Java- item 15

从来不烧饼 2024-10-02 14:54:06

这可能是一个更好的 Point 实现。
导入 java.io.Serialized;

public final class Point implements Serializable {
    private static final long serialVersionUID = 0x100;

    private final int x;
    private final int y;
    private final int z;

    public Point(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public Point(Point that) {
        this(that.x, that.y, that.z );
    }

    public boolean equals(Object obj) { 
        // assume this is a typical, safe .equals implementation
        // that compares the coordinates in this instance to the
        // other instance
        return true;
    }
}

This could be a better Point implementation.
import java.io.Serializable;

public final class Point implements Serializable {
    private static final long serialVersionUID = 0x100;

    private final int x;
    private final int y;
    private final int z;

    public Point(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public Point(Point that) {
        this(that.x, that.y, that.z );
    }

    public boolean equals(Object obj) { 
        // assume this is a typical, safe .equals implementation
        // that compares the coordinates in this instance to the
        // other instance
        return true;
    }
}
生寂 2024-10-02 14:54:06

除了暴露数组(正如 getter 习惯做的那样)并且不是最终的之外,可序列化是“有问题的”。

作为一个非常讨厌的人,当反序列化时,我可以获得对内部数组的另一个引用。对此的明显解决方法是:

private void readObject(
    ObjectInputStream in 
) throws ClassNotFoundException, IOException {
    ObjectInputStream.GetField fields = in.readFields();
    this.points = ((Point[])(fields.get("point", null)).clone();
}

仍然存在 points 不是 final 的问题,并且在未初始化 point 的情况下暴露对象(或更糟,但是有点理论上,部分初始化)。你真正想要的是一个“串行代理”,你可以在互联网上找到它......

注意:如果你实现equals,你也应该实现hashCode,可能toString 和可能的Comparable

Other than exposing the array (as getters are wont to do) and not being final, being serialisable is "problematic".

As a very nasty man, when deserialising, I can get another reference to the internal array. The obvious fix for this is:

private void readObject(
    ObjectInputStream in 
) throws ClassNotFoundException, IOException {
    ObjectInputStream.GetField fields = in.readFields();
    this.points = ((Point[])(fields.get("point", null)).clone();
}

That still leaves the problem of points not being final and exposing the object without points initialised (or worse, but a bit thoeretical, partially initialised). What you really want is a "serial proxy", which you can find out about on the internets...

Note: If you implement equals you should also implement hashCode, probably toString and possible Comparable.

沙与沫 2024-10-02 14:54:06

点本身不一定是不可变的,三角形也是不可变的。您只需要进行大量的防御性复制,这样就没有人可以引用存储在三角形中的点对象。

另外,三角形 abc 不应该等于三角形 bca (以及其他 4 个排列)

Point itself doesn't have to be immutable for Triangle to be immutable. You just have to do a lot of defensive copies so that nobody has a reference to the Point objects stored in the Triangle.

Also, shouldn't triangle a-b-c equal triange b-c-a (and 4 other permutations)

咿呀咿呀哟 2024-10-02 14:54:06

具有可变字段的不可变类示例:

public  final class  ImmutabilityTest {

    private final int i;
    private final C c1;

    ImmutabilityTest(int i, C c1){
        this.i = i;
        this.c1 = c1;
    }

    public int getI() {
        return i;
    }
    public C getC1() {
        return (C)c1.clone();//If return c1 simply without calling clone then contract of immutable object will break down 
    }

    @Override
    public String toString() {
        return "ImmutabilityTest [i=" + i + ", c1=" + c1 + "]";
    }


    public static void main(String[] args) {

        ImmutabilityTest i1 = new ImmutabilityTest(10, new C(new D("before")));
        System.out.println(i1);
        i1.getC1().getD1().name = "changed";

        System.out.println(i1);

    }

}

class C implements Cloneable{
    D d1;

    public C(D d1) {
        super();
        this.d1 = d1;
    }

    public D getD1() {
        return d1;
    }

    public void setD1(D d1) {
        this.d1 = d1;
    }


    @Override
    public String toString() {
        return "C [d1=" + d1 + "]";
    }

    public C clone(){
        C c = null;
        try {
            c = (C) super.clone();
            c.setD1(c.getD1().clone());// here deep cloning is handled if it is commented it will become shallow cloning
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return c;
    }

}

class D implements Cloneable{
    String name;

    public D(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }


    @Override
    public String toString() {
        return "D [name=" + name + "]";
    }

    public D clone(){
        D d = null;
        try {
            d = (D) super.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return d;
    }
}

A immutable class example with mutable field:

public  final class  ImmutabilityTest {

    private final int i;
    private final C c1;

    ImmutabilityTest(int i, C c1){
        this.i = i;
        this.c1 = c1;
    }

    public int getI() {
        return i;
    }
    public C getC1() {
        return (C)c1.clone();//If return c1 simply without calling clone then contract of immutable object will break down 
    }

    @Override
    public String toString() {
        return "ImmutabilityTest [i=" + i + ", c1=" + c1 + "]";
    }


    public static void main(String[] args) {

        ImmutabilityTest i1 = new ImmutabilityTest(10, new C(new D("before")));
        System.out.println(i1);
        i1.getC1().getD1().name = "changed";

        System.out.println(i1);

    }

}

class C implements Cloneable{
    D d1;

    public C(D d1) {
        super();
        this.d1 = d1;
    }

    public D getD1() {
        return d1;
    }

    public void setD1(D d1) {
        this.d1 = d1;
    }


    @Override
    public String toString() {
        return "C [d1=" + d1 + "]";
    }

    public C clone(){
        C c = null;
        try {
            c = (C) super.clone();
            c.setD1(c.getD1().clone());// here deep cloning is handled if it is commented it will become shallow cloning
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return c;
    }

}

class D implements Cloneable{
    String name;

    public D(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }


    @Override
    public String toString() {
        return "D [name=" + name + "]";
    }

    public D clone(){
        D d = null;
        try {
            d = (D) super.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return d;
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文