Introduction
Here are some email between me and Joshua Bloch. I hope these email will do some help for you. These mail had been saved for some time. I think I can share them with you.
About Joshua Bloch, here is some information:
Joshua Bloch, a senior staff engineer at Sun Microsystems, Inc. Bloch, an architect in the Core Java Platform Group, designed and implemented the award-winning Java Collections Framework, the java.math package, and has contributed to many other parts of the platform. The author of numerous articles and papers, Bloch has also written a book, Effective Java Programming Language Guide, which won the prestigious Jolt Award from Software Development Magazine. Bloch holds a Ph.D. in computer science from Carnegie-Mellon University.
These email discussed about the general contract when overriding equals and java interface version ploblem. these questions were mentioned in the book Effective Java Programming Language Guide.
--------------------------------------------------------------------------------------------------------------------------------------
Suggestion:
After I read Effective Java (by Joshua Bloch mailto:joshua.bloch@sun.com), I find some questions. Here are my suggestion:
1. In Item 7: Obey the general contract when overriding equals
"It turns out that this is a fundamental problem of
equivalence relations in object-oriented languages. There is simply no way to
extend an instantiable class and add an aspect while preserving the equals
contract."
I think we can modify function Point.equals to do this, make it return true only when two object have the same class type.
public class Point extends Point2D implements java.io.Serializable {
//...
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass().equals(obj.getClass()) == false) {
return false;
}
Point pt = (Point)obj;
return (x == pt.x) && (y == pt.y);
}
}
public class ColorPoint extends Point {
private Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
public boolean equals(Object o) {
if (obj == null) {
return false;
}
if (getClass().equals(obj.getClass()) == false) {
return false;
}
ColorPoint cp = (ColorPoint)o;
return super.equals(o) && cp.color == color;
}
}
There is no problem about symmetry or transitivity.
I suggest this should be a general rule for overwrite function equals().
2.In item 16: use interfaces insteadof abstract classes.
I think Sun should remove interfaces from Java. And the rule should be "replace interfaces with abstract classes".
Interfaces result a problem, that is version not compatitable. Once you release a java interface, others use it, you can't modfiy it any more. Cause if you add a funtion to that java interface, others code can't compile. When you change to abstract classes, everything is ok, you can add new functions later freely.
For example, if Sun add a new function in java.lang.Runnable, many java multi-thread writen with Runnable interface can't compile. This also happen in java JDBC, Sun use interfaces such as Connection. When Sun release a new jdk version, for example , from jdk1.4.1 to jdk1.4.2, Sun add some new functions to Connection interface. And programmers who upgrade their jdk to jdk1.4.2 find they can't compile their code since the JDBC driver they use didn't implement those new functions in Connection interface. This is not acceptable. Why new jdk can't comile code that compile ok in early version? No reason.
Another example is LayoutManager, Sun need to add more function for this interface, they have to add a new LayoutManager2 interface.That is not a good way,but they have no choice.
In java swing module, there are a lot of interfaces for event. But Sun keep on add more functions in old interface in new jdk. This will cause old code compile error. They have to add a new abstract class named as XXXAdapter for each event interface. And programmer should use these XXXAdapter class insteadof event interface.In this way, they can compile old code without error using new jdk.That is not a good way.
I think Sun should modify java class Object like this:
public class Object {
//... old code
private HashMap m_InterfacesMap = new HashMap();
public void addInterface(Object o) {
m_InterfacesMap.put(o.getClass(), o);
}
public Object getInterface(Class c) {
Object result = m_InterfacesMap.get(c);
if (result != null) {
return result;
}
Iterator iteratorInterfaceObj = m_InterfacesMap.values().iterator();
while (iteratorInterfaceObj.hasNext()) {
Object o = iteratorInterfaceObj.next();
if (c.isInstance(o)) {
return o;
}
}
return null;
}
public boolean supportInterface(Class c) {
if (m_InterfacesMap.get(c) != null) {
return true;
}
Iterator iteratorInterfaceObj = m_InterfacesMap.values().iterator();
while (iteratorInterfaceObj.hasNext()) {
Object o = iteratorInterfaceObj.next();
if (c.isInstance(o)) {
return true;
}
}
return false;
}
}
And we can now remove interface from java now.
Jacklondon Chen
Joshua Bloch replied email about general contract when overriding equals. As for interface version problem, he said few words.
>Suggestion:
>After I read Effective Java (by Joshua Bloch mailto:joshua.bloch@sun.com), I find some questions. Here are my suggestion:
>
>1. In Item 7: Obey the general contract when overriding equals
>
>"It turns out that this is a fundamental problem of
>equivalence relations in object-oriented languages. There is simply no way to
>extend an instantiable class and add an aspect while preserving the equals
>contract."
>
>I think we can modify function Point.equals to do this, make it return true only when two object have the same class type.
>
>public class Point extends Point2D implements java.io.Serializable {
>
>//...
>
> public boolean equals(Object obj) {
> if (obj == null) {
> return false;
> }
> if (getClass().equals(obj.getClass()) == false) {
> return false;
> }
>
> Point pt = (Point)obj;
> return (x == pt.x) && (y == pt.y);
>
> }
>
>}
>
>public class ColorPoint extends Point {
> private Color color;
>
> public ColorPoint(int x, int y, Color color) {
> super(x, y);
> this.color = color;
> }
>
> public boolean equals(Object o) {
> if (obj == null) {
> return false;
> }
> if (getClass().equals(obj.getClass()) == false) {
> return false;
> }
>
> ColorPoint cp = (ColorPoint)o;
> return super.equals(o) && cp.color == color;
> }
>}
>
>There is no problem about symmetry or transitivity.
>I suggest this should be a general rule for overwrite function equals()
>
>
Technically speaking you are correct. A getClass-based equals method
allows you to add an "aspect" (a field that affects equals comparisons)
to a subclass without violating the letter of the equals contract, but
it has other, more serious problems. In particular, you sacrifice
substitutability (the Liskov Substitution Principle) and with it, the
Principle of Least Astonishment. This matter is somewhat
controversial. The bulk of Java experts (including Doug Lea) agree with
my position on this, but a few people (notably Angelika Langer)
disagree. I was planning on writing up the controversy, but never
finished it.. Here's a rough, incomplete draft:
Many people have mailed me to say that it *is* possible to extend an
instantiable class and add an aspect while preserving the equals contract,
contrary to my claim in Item 7. Other books have recommended these
"getClass-based equals methods," and my book (Item 7) didn't address
them. I
considered discussing this topic in Item 7, but decided against it because
Item 7 was already so long and complex. On balance, this may have been a
mistake. Had I known how controversial this topic was, and how well-know
the getClass approach, I would have discussed it. I plan on posting an
essay
on this topic on the books web site, but I haven't had time to finish
writing
it. Here it is in rough form:
This technique ("getClass-based equals methods") does satisfy the equals
contract, but at great cost. The disadvantage of the getClass approach
is that it violates the "Liskov Substitution Principle," which states
(roughly speaking) that a method expecting a superclass instance must
behave properly when presented with a subclass instance. If a subclass
adds a few new methods, or trivially modifies behavior (e.g., by
emitting a trace upon each method invocation), programmers will be
surprised when subclass and superclass instances don't interact
properly. Objects that "ought to be equal" won't be, causing programs
to fail or behave erratically. The problem is exacerbated by the fact
that Java's collections are based on the equals method.
Here's a simple example that doesn't involve collections. Suppose you
have a complex number class with a getClass-based equals method:
public class Complex {
private double re;
private double im;
public static final Complex ORIGIN = new Complex(0.0, 0.0);
public Complex(double re, double im) {
this.re = re;
this.im = im;
}
final public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) // Questionable
return false;
Complex c = (Complex)o;
return c.re == re && c.im == im;
}
final public double norm() { return Math.sqrt(re*re + im*im); }
// The standard definition of signum on a complex number
final public Complex signum() {
if (this.equals(ORIGIN))
return ORIGIN;
double norm = norm();
return new Complex(re/norm, im/norm);
}
...
}
All of this may seem fine, but look what happens if you extend Complex
in some innocuous way, create an instance of the subclass whose re and
im values are both 0.0, and invoke the signum method. The initial
equals test returns false, and signum divides by zero (twice) and
silently return the wrong answer, a complex number whose real and
imaginary parts are both NaN! (The correct answer is ORIGIN.) This not
an isolated example. It takes more care to write a non-final class with
a getClass-based equals method.
Equals methods based on getClass provide very different semantics from
those based on instanceof. Many Java programmers expect the latter
semantics, in part because the great majority of classes in the Java
platform libraries use instanceof-based equals methods. If interclass
comparisons all return false (as they do with getClass-based equals
methods), subclasses may behave erratically and programmers may be at a
loss to understand why. Note also that mixing the two approaches in a
single hierarchy produces meaningless results.
A minor disadvantage of the getClass approach is that it requires a
separate null-test to achieve the correct behavior. The null-test is
performed automatically by the instanceof operator, as described on page
32.
I should reiterate that one can argue for both approaches. With the
instanceof approach, it's easy to write "trivial subclasses" and
impossible to add an aspect (a field used in equals comparisons). With
the getClass approach it's possible to add an aspect (assuming you're
willing to have the subclass instances not interact with the superclass
instances) but it is impossible to create a subclass that it is usable
anywhere the superclass is usable, even a "trivial subclass." While the
instanceof approach predominates, some respected authors do consider the
getClass approach to be acceptable.
One last thing worth mentioning is that this controversy is far less
important than it might appear. The vast majority of classes should not
override Object.equals at all. Only value classes (or other classes
with value semantics) should do so. Furthermore, most value classes
should be immutable, hence final. If a class is final, it doesn't
matter which of the two kinds of equals methods it has.
From a theoretical perspective, it is often suspect to add an aspect in
a subclass, as it often violates the "is-a" test. For example, some
books have a 3-dimensional point (Point3) subclass a 2-dimensional point
(Point2). However, it is *not* the case that a 3-dimensional point is a
2-dimensional point! A 3-dimensional point may be *viewed* as a
2-dimensional point in any of three ways (projection onto X-Y plane,
projection onto Y-Z plane, projection onto X-Z plane). Note that my
suggested solution (p. 31) meshes well with this situation: one can add
three separate 2-d-point-returning view methods to the 3-d point class.
Prof. Mads Torgersen summed it up this way:
"It's amazing how many interesting problems of object-oriented
abstraction you can
fix by doing away with object-oriented abstraction. It's like holding your
breath to get rid of hiccups: If you do it long enough its guaranteed to
work."
>2.In item 16: use interfaces insteadof abstract classes.
>
>I think Sun should remove interfaces from Java
>
I assume you know that this would be impossible even if it were
advisable. It would represent a gross incompatibility, breaking
millions of existing programs. That said, I think it would be a very
bad idea. Interfaces are the heart and soul of the Java programming
language.
>And the rule should be "replace interfaces with abstract classes".
>
>
Nope. You correctly point out that it's easier to evolve an abstract
class than an interface. In fact Item 16 says this and repeats it in
its closing paragraph. Where evolution is of paramount importance,
abstract classes may be preferable to interfaces. Generally speaking,
however, the increased flexibility of implementation afforded by
interfaces make them preferable. This topic is less controversial than
the previous one. (To the best of my knowledge, you're the first person
ever to take issue with Item 16.)
Happy new year,
Josh
I found that these code about "the general contract when overriding equals" still had some bugs in it. So I changed the code and resend a mail:
Dear Mr Bloch,
It's my great honor to receive your email. Thank you very much.
as for item 7:Obey the general contract when overriding equals,
I find a good way to solve this question . That's to check class which define equals function.
public class Point {
public boolean equals(Object other) {
if(CheckEqualsTool.hasSameDeclaringEqualsClass(this,other)){
Point otherPoint = (Point) other;
return x == otherPoint.x && y == otherPoint.y;
}
else {
return false;
}
}
}
public class ColorPoint
extends Point {
private Color color;
public ColorPoint() {
super(0, 0);
this.color = Color.black;
}
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
public boolean equals(Object other) {
if(CheckEqualsTool.hasSameDeclaringEqualsClass(this,other)){
ColorPoint otherColorPoint = (ColorPoint) other;
return super.equals(other) && color == otherColorPoint.color;
}
else {
return false;
}
}
}
public class CheckEqualsTool {
public static boolean hasSameDeclaringEqualsClass(Object obj, Object other) {
if (obj == null || other == null) {
return false;
}
Class thisDeclaringEqualsClass = getDeclaringEqualsClass(
obj);
Class otherDeclaringEqualsClass = getDeclaringEqualsClass(
other);
return thisDeclaringEqualsClass.equals(otherDeclaringEqualsClass);
}
private static Class getDeclaringEqualsClass(Object obj) {
Class declaringEqualsClass = null;
try {
Method method = obj.getClass().getMethod("equals",
new Class[] {Object.class});
declaringEqualsClass = method.getDeclaringClass();
}
catch (Exception e) {
declaringEqualsClass = null;
}
return declaringEqualsClass;
}
}
as for item 16: use interfaces insteadof abstract classes:
I just finish leading a java application project. No one is allowed to create java interface. We get very good version compatibility.
I don't why Sun don't about java source version compatibility.
As a BASIC general rule, programmer update software develope tools, all old code is supposed to be compiled OK with new version tools.
Anyway, we can't do this when we use java interface. We feel good without java interface.
If you write a java interface and others use it, you cannot add code to it. This may cause others code compile error.
I have check there are so many java awt/swing event use java interface for event listener and abstract class for event interface adapter,
for example:
public interface KeyListener extends EventListener {
public void keyTyped(KeyEvent e);
public void keyPressed(KeyEvent e);
public void keyReleased(KeyEvent e);
}
public abstract class KeyAdapter implements KeyListener {
public void keyTyped(KeyEvent e) {}
public void keyPressed(KeyEvent e) {}
public void keyReleased(KeyEvent e) {}
}
I think there is no reason for existence of KeyListener, both JDK and programmer use KeyAdapter is good.
Sorry to bother you.
Jacklondon Chen
--------------------------------------------------------------------------------------------------------------------------------------
Jacklondon Chen (陈平)
Software Engineer
HP China Software Solutions Center
25 Yunqiao Road, T22 Jinqiao Export Processing Zone, Shanghai China
Tel: 86-21-28982061
Fax: 86-21-28982112
Email: jacklondon.chen@hp.com