Является ли это непреложным классом?
Я понятия не имею, какой должен выглядеть неизменный класс, но я уверен, что это так. Я прав? Если я не укажу, что нужно добавить/удалить.
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;
}
}
Ответы
Ответ 1
Нет, вы можете изменить то, что в массиве Points. Если вы хотите сделать его неизменным, попросите геттера передать копию массива Points, а не оригинал.
попробуйте следующее:
Triangle triangle = new Triangle(a, b, c);
triangle.getPoints()[1] = null;
System.out.println(Arrays.toString(triangle.getPoints()));
Точка должна быть неизменной (как указывает Никита Рыбак). Как копировать массивы см. как скопировать массив в Java.
Ответ 2
Нет, нет. Вы показываете Point [], и вызывающий может изменить его содержимое. Кроме того, ваш класс не является окончательным, поэтому кто-то может подорвать его, подклассифицируя его.
Ответ 3
Нет, он определенно изменен.
Вы не только показываете фактический массив Point [], вы не защищаете-копируете (Bloch 2nd ed., Item 39) Объект Point сам берет их через конструктор.
- В массиве Point [] могут быть элементы
удалены или добавлены к нему, так что это
изменяемые.
- Вы можете переходить в пункты a, b и c, затем вызовите setX() или setY() на них изменить свои данные после строительство.
Ответ 4
Закрыть. Во-первых, неизменяемый класс должен сделать его окончательным, но это не является обязательным требованием.
Однако вы выставляете массив через getter, и это не является неизменным. Создайте защитную копию, используя Arrays.copyOf(массив, длина):
@Override
public Point[] getPoints() {
return Arrays.copyOf(this.points,this.points.length);
}
Ответ 5
Вот что я сделал бы, чтобы сделать этот класс неизменным, с помощью 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.
}
Ответ 6
Чтобы быть неизменным классом, недостаточно, чтобы ваши методы обещают не изменять объект. Помимо того, что все поля являются частными, а методы не позволяют изменять, вы также должны гарантировать, что подклассы имеют одинаковое обещание неизменности. Это включает в себя создание самого финального класса и обеспечение того, чтобы ссылки на поля не возвращались.
Короткий, но отличный подход к этому можно найти в этой статье:
http://www.javaranch.com/journal/2003/04/immutable.htm
Ответ 7
Вам нужно не только предоставить неизменяемую копию интернализованного массива, но также нужно убедиться, что объект Point является неизменным.
Рассмотрим следующее использование класса Point в стандартном 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()));
Ответ 8
Это не является неизменным, потому что...
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]); }
или верните копию points
, как вы предложили в своем редактировании.
Ответ 9
В дополнение к тому, что уже отмечалось другими, вам необходимо:
- Создайте свой треугольный класс
final
, чтобы предотвратить создание измененных треугольников по подклассам.
- Объявить все поля final, чтобы случайно вызвать модификацию полей самим классом.
В "Эффективной Java" Джошуа Блох содержит список правил для неизменяемых классов в целом, в пункте 15: "Минимизировать взаимность".
Ответ 10
1) Сделать членов закрытыми и окончательными - так
private Point[] points; //should be
private final Point[] points;
2) Сделать класс окончательным, чтобы он не подклассифицировался
3) Исключительный доступ к изменяемым элементам (массиву) - означает возвратную копию, а не ссылку на изменяемые элементы
Для лучшего лечения этой темы см. Джошуа Блох, Эффективный Java-элемент 15
Ответ 11
Это может быть лучше 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;
}
}
Ответ 12
Помимо раскрытия массива (поскольку геттеры не хотят делать), а не 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
.
Ответ 13
Сама точка не обязательно должна быть неизменной, чтобы Треугольник был неизменным. Вам просто нужно сделать много защитных копий, чтобы никто не ссылался на объекты Point, хранящиеся в треугольнике.
Кроме того, не должен быть треугольник a-b-c равным треугольником b-c-a (и 4 других перестановок)
Ответ 14
Необязательный пример класса с изменяемым полем:
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;
}
}