[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: the benefits of immutability



Pascal Costanza wrote:
 > What's the problem with this?
 >
 > public boolean equals(Object obj) {
 >   if (!(obj instanceof Subclass)) return false;
 >
 >   Subclass that = (Subclass)obj;
 >
 >   return super.equals(that) && (this.m_num == that.m_num);
 > }

With the Subclass defining its equals method the way suggested above,
the Superclass's equals method is no longer symmetric, thus violating
the method's documented contract.  See the example at the bottom of
this message.

 > I think the main design problem in Java is that immutability is just
 > a convention, but inappropriate language constructs are used to
 > enforce that convention. Either you completely forget about
 > enforcing immutability and rely on intelligent programmers to do it
 > right, or invent a language construct that explicitly says what you
 > want. In Java, one could have added a class modifier "immutable"
 > and, for example, require that all fields in an immutable class are
 > final.

Doesn't really help here.  Immutability doesn't mean that the
underlying representation does not change, once the instance has been
constructed.  Rather, it means that none of the visible state ever
changes it if is accessible via the exported API.  For instance, if
you call str.substring(10), you always get the same result back.  If
you allow subclassing, then the compiler cannot enforce this
requirement in all but the simplest cases.  Sure if the class is
declared "immutable" (where "immutable" is a new hypothetical Java
keyword), then the compiler can enforce the requirement that all
instance fields be "final".  So far, so good.  However, the compiler
cannot enforce the requirement that the substring method of the String
type always return the same value, if subclassing is allowed.

Vadim

P.S. Here's why the suggested implementation is faulty:


$ cat Test.java
public class Test {

    public static void main(String[] args) {
        Superclass xx = new Superclass("foo");
        Superclass yy = new Subclass("foo", 2);
        log(xx.equals(yy) ?
            "xx equals yy" : "xx doesn't equal yy");
        log(yy.equals(xx) ?
            "yy equals xx" : "yy doesn't equal xx");
    }

    private static void log(String str) {
        System.out.println(str);
    }
}

class Superclass {

    private String m_str;

    public Superclass(String str) {
        m_str = str;
    }

    public boolean equals(Object obj) {
        if ( !(obj instanceof Superclass) ) { return false; }

        return ((Superclass) obj).m_str.equals(m_str);
    }

    public int hashCode() {
        return m_str == null ? 0 : m_str.hashCode();
    }
}

class Subclass extends Superclass {
    private int m_num;

    public Subclass(String str, int num) {
        super(str);
        m_num = num;
    }

    public boolean equals(Object obj) {
        if (!(obj instanceof Subclass)) return false;

        Subclass that = (Subclass)obj;

        return super.equals(that) && (this.m_num == that.m_num);
    }
}

$ javac Test.java

$ java -cp . Test
xx equals yy
yy doesn't equal xx