12

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;
    }
}
Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
Denys S.
  • 6,358
  • 12
  • 43
  • 65
  • 16
    +1 for "I have no idea what immutable class should look like but am pretty sure this one is.", that made my day :) – Karel Petranek Sep 29 '10 at 20:13
  • Quck tip: Immutable classes should return a new copy of its arrays or collections rather than passing them back directly. – Powerlord Sep 29 '10 at 20:16
  • 3
    Or return an unmodifiable copy of a collection, or (even better) use an immutable collection such as Guava's `ImmutableList` to begin with. – ColinD Sep 29 '10 at 20:18
  • Shouldn't it be `final class Triangle` anyway? – Ishtar Sep 29 '10 at 20:21
  • Assuming that this constructor works great) – Denys S. Sep 29 '10 at 20:21
  • *Will this do the trick?* Only if `Point` is immutable itself :) – OscarRyz Sep 29 '10 at 20:24
  • 5
    Ew, `Point` has public, non-final fields... a `Point` is a textbook example of something that should be immutable too. =( – ColinD Sep 29 '10 at 20:32
  • Unfortunately I'm not allowed to put my hands on Point (it's a third party class). So whether Point is immutable or not - it's non of my concern) Though, as far as I can judge now, it should be! – Denys S. Sep 29 '10 at 23:38
  • @den-javamaniac: `Point` isn't immutable because of the public, non-final fields like I said. Anything can change the x, y and z values of a `Point` any time they want. – ColinD Sep 30 '10 at 03:21
  • @den-javamaniac: lots of good answers. I'll just point out that you can take another OO approach: instead of exposing Point[] (even by providing a defensive copy), you can think in term of the OO messages that have to be exchanged with other objects. You probably don't need to expose points. You want to "draw" something: don't query the points to do the drawing: have your object know how to draw itself. Want to know if the object can intersect with another shape? Don't query it's points but provide and *doesIntersectWith(Shape ...)* method, etc. – SyntaxT3rr0r Sep 30 '10 at 05:28

14 Answers14

18

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.

Community
  • 1
  • 1
Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
  • 6
    Also, _Point_ class should be immutable. Doing _triangle.getPoints()[0].setX(3)_ would brake immutability too. – Nikita Rybak Sep 29 '10 at 20:18
  • @den-javamaniac: Then the array should be deep copied, i.e. every Point must be copied as well. – Bart van Heukelom Sep 29 '10 at 20:28
  • If you can't modify it and you can't trust the client code (or you're really paranoid) then you're going to have to create copies of the Point objects as well. At that point I'd rethink the API design. – Sean Patrick Floyd Sep 29 '10 at 20:28
  • @seanizer: that's not a real-world app, it's about my knowledge of coding immutable classes. :) So Point copy will do the trick. – Denys S. Sep 29 '10 at 20:32
  • 1
    I'd go with ColinD's suggestion then, using an immutable collection. Collections are always nicer to use than arrays, and that way you can skip the defensive copying (at least on the top level) – Sean Patrick Floyd Sep 29 '10 at 20:36
10

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.

dty
  • 18,795
  • 6
  • 56
  • 82
  • 2
    They can't subvert it by subclassing. The only data, "points", is private. A child class can't see its parent's private data. (That would be incest or child abuse or something.) – Jay Sep 29 '10 at 20:38
  • 3
    But if Point weren't immutable, the child could call `super(pointA, pointB, pointC)`, keep local references to the points and change them. Who is abusing whom now? :-) – Sean Patrick Floyd Sep 29 '10 at 20:45
  • 1
    Thank you, @seanizer, you beat me to it. – dty Sep 29 '10 at 20:50
  • 1
    The purpose of making the class final is not to prevent the internal data from being manipulated by subclasses. This is easily accomplished by making the fields private. The purpose of making the class final is to ensure that the behaviour of the object itself is immutable. If I can add a new point field to the class, and override getPoints() in such a way as to return a reference to the new point field, I have used subclassing to make the object mutable where it wasn't before. – Zoe Sep 29 '10 at 20:53
  • So do you not consider java.lang.String to be immutable since it's not final? – Mike Deck Sep 29 '10 at 21:17
  • 2
    I don't know which String you're looking at, but the one I'm looking at most definitely IS final. – dty Sep 29 '10 at 21:34
  • java.lang.String has been final since Java 1.0. – Zoe Sep 29 '10 at 22:41
8

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.
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
RonU
  • 5,525
  • 3
  • 16
  • 13
6

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);
}
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
4

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.
}
ColinD
  • 108,630
  • 30
  • 201
  • 202
  • 1
    +1 Very nice solution (both classes). I'd go further and say that it's nonsense to let IShape return an array of points. Each geometrical shape has specific requirements and handling them all through the same method is a bad abstraction imho. – Sean Patrick Floyd Sep 29 '10 at 21:18
  • Good point about the array. Introducing a new dependency to Guava would not be required, however -- java.util.Collections.unmodifiableList() returns a reference to an unmodifiable list. @seanizer - returning an array of vertices would be useful for some algorithms; it's not nonsense. A simple example is computing perimeter. – Andy Thomas Sep 30 '10 at 02:55
  • @Andy: Yes, an unmodifiable wrapper is sufficient to prevent users of the class from modifying the list. I find the Guava version preferable for the simplicity of creating it, its guarantee of immutability, and the fact that the type itself makes its immutability clear. Plus I'm a big fan of Guava and like to promote its use when I see something that can be done a little better with it. – ColinD Sep 30 '10 at 03:19
3

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

Zoe
  • 1,833
  • 1
  • 16
  • 18
1

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()));
Synesso
  • 37,610
  • 35
  • 136
  • 207
0

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;
    }
}
Vadim
  • 8,701
  • 4
  • 43
  • 50
0

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.

Yanick Rochon
  • 51,409
  • 25
  • 133
  • 214
  • That would work, but you're not using OO to its best extent. Clients of this class would not be able to work with Point objects, and if they wanted to they'd have to recompose one from the x,y coords. – Synesso Sep 29 '10 at 20:28
0

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.

Andy Thomas
  • 84,978
  • 11
  • 107
  • 151
0

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

Adi Pandit
  • 689
  • 1
  • 6
  • 10
0

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;
    }
}
OscarRyz
  • 196,001
  • 113
  • 385
  • 569
  • 1
    Point may not necessarily be fixed (I mean from general point of view). But that's not of current question's concern. – Denys S. Sep 29 '10 at 21:46
  • Well, yes, but if one of the points of the triangle changes, you can say the triangle it self changed, right? – OscarRyz Sep 29 '10 at 21:53
0

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.

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
0

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)

irreputable
  • 44,725
  • 9
  • 65
  • 93