我与 Joshua Bloch 的 email 讨论(emails between Joshua Bloch and me)

2004-08-11 14:57

 

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

 

 

欢迎转载,转载请注明出处: https://www.zheguisoft.com/staff_blogs/jacklondon_chen/2004, 及 https://blog.csdn.net/jacklondon/article/details/71307?spm=1001.2014.3001.5501