我不知道不可变类应该是什么样的,但我很确定这个是。我对吗?如果我不是,请说明应该添加/删除的内容。
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;
}
}
答案 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项)。
答案 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)
为了成为一个不可变的类,你的方法承诺不改变对象是不够的。除了使所有字段都是私有的并且方法不允许更改之外,还必须保证子类具有相同的不可变性承诺。这包括使类本身最终,并确保不返回对字段的引用。
在本文中可以找到一个简短而优秀的处理方法:
答案 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
以防止子类创建可变三角形。在“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
,可能toString
和Comparable
。
答案 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;
}
}