这是一个不可改变的类吗?

时间:2010-09-29 20:12:02

标签: java arrays immutability

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

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;
    }
}

14 个答案:

答案 0 :(得分:17)

不,您可以更改Points数组中的内容。如果你想让它不可变,让getter分发Points数组的副本,而不是原来的。

试试这个:

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

Point也需要不变(正如Nikita Rybak指出的那样)。有关如何复制数组的信息,请参阅how to copy an array in Java

答案 1 :(得分:10)

不,不是。您公开Point []并且调用者可以修改其内容。此外,你的课程不是最终的,所以有人可以通过继承它来颠覆它。

答案 2 :(得分:8)

不,这绝对是可变的。

不仅暴露了实际的Point []数组,在通过构造函数获取Point对象时,不会防御复制(Bloch 2nd ed.,第39项)。

  1. Point []数组可以包含项目 删除或添加到它,所以它 可变的。
  2. 你可以通过积分a,     b和c,然后调用setX()或setY()     在他们之后改变他们的数据     构造

答案 3 :(得分:6)

关闭。首先,不可变类使其字段为final,但这不是必需的。

但是,您通过getter公开数组,这不是不可变的。使用Arrays.copyOf(array, length)制作防御性副本:

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

答案 4 :(得分:4)

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

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.
}

答案 5 :(得分:3)

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

在本文中可以找到一个简短而优秀的处理方法:

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

答案 6 :(得分:1)

您不仅需要提供内部化数组的不可变副本,需要确保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()));

答案 7 :(得分:0)

这不是一成不变的,因为......

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

因此,类不是不可变的,因为您可以更改实例的 state (内部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]); }

或返回您在编辑中建议的points副本。

答案 8 :(得分:0)

除了其他人已经注意到的内容之外,你应该:

  • 制作三角类final以防止子类创建可变三角形。
  • 声明所有字段final,以捕获类本身对字段的意外修改。

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

答案 9 :(得分:0)

1)让会员成为私人和最终的 - 所以

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

2)让课程最终成为不可归类的

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

关于该主题的最佳治疗,请参阅Joshua Bloch,Effective Java- item 15

答案 10 :(得分:0)

这可能是更好的Point实施。     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;
    }
}

答案 11 :(得分:0)

除了暴露数组(因为getter不会这样做)而不是final之外,可序列化是“有问题的”。

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

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

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

注意:如果您实施equals,您还应该实施hashCode,可能toStringComparable

答案 12 :(得分:0)

Point本身不一定是不可变的,因为Triangle是不可变的。你只需要做很多防御性拷贝,这样就没有人能够引用存储在三角形中的Point对象。

此外,不应该是三角形a-b-c等于triange b-c-a(以及其他4种排列)

答案 13 :(得分:0)

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

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;
    }
}