publicenum Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
// Attempting to add a value component to Point - Pages 37 - 38package org.effectivejava.examples.chapter03.item08;
publicclassColorPointextendsPoint {privatefinal Color color;
publicColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
// Broken - violates symmetry!@Overridepublicbooleanequals(Object o) {
if (!(o instanceof ColorPoint))
returnfalse;
returnsuper.equals(o) && ((ColorPoint) o).color == color;
}
publicstaticvoidmain(String[] args) {
// First equals function violates symmetry
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);
System.out.println(p.equals(cp) + " " + cp.equals(p));
}
}
你会发现这样违反了对称性,改成这样,又会违反传递性
// Broken - violates transitivity!
@Override public boolean equals(Object o) {
if (!(o instanceof Point))
returnfalse;
If o is a normal Point, do a color-blind comparison
if (!(o instanceof ColorPoint))
return o.equals(this);
o is a ColorPoint; do a full comparison
return super.equals(o) && ((ColorPoint)o).color == color;
}
// Second equals function violates transitivity
ColorPoint p1 =new ColorPoint(1, 2, Color.RED);
Point p2 =new Point(1, 2);
ColorPoint p3 =new ColorPoint(1, 2, Color.BLUE);
System.out.printf("%s %s %s%n", p1.equals(p2), p2.equals(p3),
p1.equals(p3));
你可能这样实现Point的equals方法:
// Broken - violates Liskov substitution principle - Pages 39-40// @Override public boolean equals(Object o) {// if (o == null || o.getClass() != getClass())// return false;// Point p = (Point) o;// return p.x == x && p.y == y;// }
这样看起来还可以,但结果是不可接受的,假设我们通过不添加值组件的方式扩展了Point
// Trivial subclass of Point - doesn't add a value component - Page 39package org.effectivejava.examples.chapter03.item08;
import java.util.concurrent.atomic.AtomicInteger;
publicclassCounterPointextendsPoint {privatestaticfinal AtomicInteger counter = new AtomicInteger();
publicCounterPoint(int x, int y) {
super(x, y);
counter.incrementAndGet();
}
publicintnumberCreated() {
return counter.get();
}
}
假设又有一个方法是用来判断某个Point是否是在单位圆中的整值点:
// Test program that uses CounterPoint as Pointpackage org.effectivejava.examples.chapter03.item08;
import java.util.HashSet;
import java.util.Set;
publicclassCounterPointTest {// Initialize UnitCircle to contain all Points on the unit circleprivatestaticfinal Set<Point> unitCircle;
static {
unitCircle = new HashSet<Point>();
unitCircle.add(new Point(1, 0));
unitCircle.add(new Point(0, 1));
unitCircle.add(new Point(-1, 0));
unitCircle.add(new Point(0, -1));
}
publicstaticbooleanonUnitCircle(Point p) {
return unitCircle.contains(p);
}
//里氏替换法则认为一个类型的任何重要属性也将适用于其子类型,因此为该方法定义的方法也应该在子类型上运行地很好,可是:publicstaticvoidmain(String[] args) {
Point p1 = new Point(1, 0);
Point p2 = new CounterPoint(1, 0);
// Prints true
System.out.println(onUnitCircle(p1));
// Should print true, but doesn't if Point uses getClass-based equals
System.out.println(onUnitCircle(p2));
}
}
// Adding a toString method to PhoneNumber - page 52package org.effectivejava.examples.chapter03.item10;
import java.util.HashMap;
import java.util.Map;
publicfinalclassPhoneNumber {privatefinalshort areaCode;
privatefinalshort prefix;
privatefinalshort lineNumber;
publicPhoneNumber(int areaCode, int prefix, int lineNumber) {
rangeCheck(areaCode, 999, "area code");
rangeCheck(prefix, 999, "prefix");
rangeCheck(lineNumber, 9999, "line number");
this.areaCode = (short) areaCode;
this.prefix = (short) prefix;
this.lineNumber = (short) lineNumber;
}
privatestaticvoidrangeCheck(int arg, int max, String name) {
if (arg < 0 || arg > max)
thrownew IllegalArgumentException(name + ": " + arg);
}
@Overridepublicbooleanequals(Object o) {
if (o == this)
returntrue;
if (!(o instanceof PhoneNumber))
returnfalse;
PhoneNumber pn = (PhoneNumber) o;
return pn.lineNumber == lineNumber && pn.prefix == prefix
&& pn.areaCode == areaCode;
}
@OverridepublicinthashCode() {
int result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumber;
return result;
}
/**
* Returns the string representation of this phone number. The string
* consists of fourteen characters whose format is "(XXX) YYY-ZZZZ", where
* XXX is the area code, YYY is the prefix, and ZZZZ is the line number.
* (Each of the capital letters represents a single decimal digit.)
*
* If any of the three parts of this phone number is too small to fill up
* its field, the field is padded with leading zeros. For example, if the
* value of the line number is 123, the last four characters of the string
* representation will be "0123".
*
* Note that there is a single space separating the closing parenthesis
* after the area code from the first digit of the prefix.
*/@Overridepublic String toString() {
return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber);
}
publicstaticvoidmain(String[] args) {
Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");
System.out.println(m);
}
}
// Adding a clone method to PhoneNumber - page 55package org.effectivejava.examples.chapter03.item11;
import java.util.HashMap;
import java.util.Map;
publicfinalclassPhoneNumberimplementsCloneable {privatefinalshort areaCode;
privatefinalshort prefix;
privatefinalshort lineNumber;
publicPhoneNumber(int areaCode, int prefix, int lineNumber) {
rangeCheck(areaCode, 999, "area code");
rangeCheck(prefix, 999, "prefix");
rangeCheck(lineNumber, 9999, "line number");
this.areaCode = (short) areaCode;
this.prefix = (short) prefix;
this.lineNumber = (short) lineNumber;
}
privatestaticvoidrangeCheck(int arg, int max, String name) {
if (arg < 0 || arg > max)
thrownew IllegalArgumentException(name + ": " + arg);
}
@Overridepublicbooleanequals(Object o) {
if (o == this)
returntrue;
if (!(o instanceof PhoneNumber))
returnfalse;
PhoneNumber pn = (PhoneNumber) o;
return pn.lineNumber == lineNumber && pn.prefix == prefix
&& pn.areaCode == areaCode;
}
@OverridepublicinthashCode() {
int result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumber;
return result;
}
/**
* Returns the string representation of this phone number. The string
* consists of fourteen characters whose format is "(XXX) YYY-ZZZZ", where
* XXX is the area code, YYY is the prefix, and ZZZZ is the line number.
* (Each of the capital letters represents a single decimal digit.)
*
* If any of the three parts of this phone number is too small to fill up
* its field, the field is padded with leading zeros. For example, if the
* value of the line number is 123, the last four characters of the string
* representation will be "0123".
*
* Note that there is a single space separating the closing parenthesis
* after the area code from the first digit of the prefix.
*/@Overridepublic String toString() {
return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber);
}
@Overridepublic PhoneNumber clone() {
try {
return (PhoneNumber) super.clone();
} catch (CloneNotSupportedException e) {
thrownew AssertionError(); // Can't happen
}
}
publicstaticvoidmain(String[] args) {
PhoneNumber pn = new PhoneNumber(707, 867, 5309);
Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
m.put(pn, "Jenny");
System.out.println(m.get(pn.clone()));
}
}
对象中有可变的对象时clone,例子:
// A cloneable version of Stack - Pages 56-57package org.effectivejava.examples.chapter03.item11;
import java.util.Arrays;
publicclassStackimplementsCloneable {private Object[] elements;
privateint size = 0;
privatestaticfinalint DEFAULT_INITIAL_CAPACITY = 16;
publicStack() {
this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
publicvoidpush(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0)
thrownew EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete referencereturn result;
}
publicbooleanisEmpty() {
return size == 0;
}
@Overridepublic Stack clone() {
try {
Stack result = (Stack) super.clone();
result.elements = elements.clone();
return result;
} catch (CloneNotSupportedException e) {
thrownew AssertionError();
}
}
// Ensure space for at least one more element.privatevoidensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
// To see that clone works, call with several command line argumentspublicstaticvoidmain(String[] args) {
Stack stack = new Stack();
for (String arg : args)
stack.push(arg);
Stack copy = stack.clone();
while (!stack.isEmpty())
System.out.print(stack.pop() + " ");
System.out.println();
while (!copy.isEmpty())
System.out.print(copy.pop() + " ");
}
}
// Making PhoneNumber comparable - Pages 65-66
package org.effectivejava.examples.chapter03.item12;
import java.util.NavigableSet;
import java.util.Random;
import java.util.TreeSet;
public final class PhoneNumber implements Cloneable, Comparable<PhoneNumber> {
private final short areaCode;
private final short prefix;
private final short lineNumber;
publicPhoneNumber(int areaCode, int prefix, int lineNumber) {
rangeCheck(areaCode, 999, "area code");
rangeCheck(prefix, 999, "prefix");
rangeCheck(lineNumber, 9999, "line number");
this.areaCode = (short) areaCode;
this.prefix = (short) prefix;
this.lineNumber = (short) lineNumber;
}
privatestaticvoidrangeCheck(int arg, int max, String name) {
if (arg < 0 || arg > max)
thrownew IllegalArgumentException(name + ": " + arg);
}
@Override
public boolean equals(Object o) {
if (o == this)
returntrue;
if (!(o instanceof PhoneNumber))
returnfalse;
PhoneNumber pn = (PhoneNumber) o;
return pn.lineNumber == lineNumber && pn.prefix == prefix
&& pn.areaCode == areaCode;
}
@Override
publicinthashCode() {
int result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumber;
return result;
}
/**
* Returns the string representation of this phone number. The string
* consists of fourteen characters whose format is "(XXX) YYY-ZZZZ", where
* XXX is the area code, YYY is the prefix, and ZZZZ is the line number.
* (Each of the capital letters represents a single decimal digit.)
*
* If any of the three parts of this phone number is too small to fill up
* its field, the field is padded with leading zeros. For example, if the
* value of the line number is 123, the last four characters of the string
* representation will be "0123".
*
* Note that there is a single space separating the closing parenthesis
* after the area code from the first digit of the prefix.
*/
@Override
public String toString() {
return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNumber);
}
@Override
public PhoneNumber clone() {
try {
return (PhoneNumber) super.clone();
} catch (CloneNotSupportedException e) {
thrownew AssertionError(); // Can't happen
}
}
// Works fine, but can be made faster// public int compareTo(PhoneNumber pn) {// // Compare area codes// if (areaCode < pn.areaCode)// return -1;// if (areaCode > pn.areaCode)// return 1;//// // Area codes are equal, compare prefixes// if (prefix < pn.prefix)// return -1;// if (prefix > pn.prefix)// return 1;//// // Area codes and prefixes are equal, compare line numbers// if (lineNumber < pn.lineNumber)// return -1;// if (lineNumber > pn.lineNumber)// return 1;//// return 0; // All fields are equal// }publicintcompareTo(PhoneNumber pn) {
// Compare area codesint areaCodeDiff = areaCode - pn.areaCode;
if (areaCodeDiff != 0)
return areaCodeDiff;
// Area codes are equal, compare prefixesint prefixDiff = prefix - pn.prefix;
if (prefixDiff != 0)
return prefixDiff;
// Area codes and prefixes are equal, compare line numbersreturn lineNumber - pn.lineNumber;
}
publicstaticvoidmain(String[] args) {
NavigableSet<PhoneNumber> s = new TreeSet<PhoneNumber>();
for (int i = 0; i < 10; i++)
s.add(randomPhoneNumber());
System.out.println(s);
}
privatestatic final Random rnd = new Random();
privatestatic PhoneNumber randomPhoneNumber() {
returnnew PhoneNumber((short) rnd.nextInt(1000),
(short) rnd.nextInt(1000), (short) rnd.nextInt(10000));
}
}