250 likes | 469 Views
Puzzle 3. Write the class Enigma , which extends Object , so that the following program prints false: public class Conundrum { public static void main(String[] args ) { Enigma e = new Enigma(); System.out.println ( e.equals (e) ); } }
E N D
Puzzle 3 • Write the class Enigma, which extends Object, so that the following program prints false: public class Conundrum { public static void main(String[] args) { Enigma e = new Enigma(); System.out.println( e.equals(e) ); } } • You must not override Object.equals() [Java Puzzlers by Joshua Block and Neal Gaffer]
Solution • the puzzle does not allow you to override equals() but it says nothing about overloading public class Enigma { public boolean equals(Enigma other) { return false; } }
Equality of Instances of Different Types • our implementation of equals() is correct in that it obeys the equals() contract • however, it returns true if and only if two objects have the exact same type • because we used getClass()
Goals for Today • implication of getClass() in equals() • implement a simple mutable vector class • delegating to accessor and mutator methods • constructor chaining
Implication of Same Type Equality • suppose PhoneNumber was not declared final • we might consider extending PhoneNumber to keep track of the total number of phone numbers created public class CountedPhoneNumber extends PhoneNumber { private static final AtomicInteger counter = new AtomicInteger(); // constructor and equals not shown public int numberCreated() { return counter.get(); } } // adapted from Effective Java (Item 8)
our implementation of equals() says that a PhoneNumber is never equal to a CountedPhoneNumber because they have different types // client code somewhere PhoneNumber x = new PhoneNumber(416, 736, 2100); CountedPhoneNumber y = new CountedPhoneNumber(416, 736, 2100); System.out.println( x.equals(y) ); // false System.out.println( y.equals(x) ); // false
classes that use equals() might not work the way you want them to • for example, HashSet uses equals() to organize the way it stores objects // client code somewhere PhoneNumber x = new PhoneNumber(416, 736, 2100); CountedPhoneNumber y = new CountedPhoneNumber(416, 736, 2100); HashSet<PhoneNumber> h = new HashSet<PhoneNumber>(); h.add(y); System.out.println( h.contains(x) ); // false
Substituting Types • it would be nice if a client could write methods that expect a PhoneNumber but work as expected when given a CountedPhoneNumber • after all, a CountedPhoneNumber is a kind of PhoneNumber • we can as long as we don't use equals() • the ability to substitute a subtype (CountedPhoneNumber) for a supertype (PhoneNumber) in client code without changing the behaviour of the client code is related to the Liskov Substitution Principle
Liskov Substitution Principle • often cited as a fundamental principle of object-oriented design (but this is fiercely debated) • Let (x) be a property provable about objects x of type T. Then (y) should be true for objects y of type S where S is a subtype of T. • observe that by using getClass() we never get equality (provable property) between PhoneNumber objects (objects of type T) and CountedPhoneNumber objects (objects of type S) [don't get hung up on this slide; it's beyond the scope of this course]
use getClass() when implementing equals() in this course • it will satisfy the equals() contract • but be aware of its limitation • if you need equality between different types: • http://www.angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/Equals.html • http://www.angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/Equals-2.html
Mutable Classes • a mutable class can change how its state appears to clients • recall that immutable classes are generally easier to implement and use • so why would we want a mutable class? • because you need a separate immutable object for every value you need to represent • we will implement a simple class that represents 2-dimensional mathematical vectors
What Can Mathematical Vectors Do? • add • subtract • multiply by scalar • set components • get components • construct • equals • toString
equals() @Override public boolean equals(Object obj) { boolean eq = false; if (obj == this) { eq = true; } return eq; }
@Override public boolean equals(Object obj) • { • boolean eq = false; • if (obj == this) { eq = true; } • else if (obj != null && this.getClass() == obj.getClass()) • { • } • return eq; • }
@Override public boolean equals(Object obj) • { • boolean eq = false; • if (obj == this) { eq = true; } • else if (obj != null && this.getClass() == obj.getClass()) • { • Vector2d v = (Vector2d) obj; • } • return eq; • }
@Override public boolean equals(Object obj) { booleaneq = false; if (obj == this) { eq = true; } else if (obj != null && this.getClass() == obj.getClass()) { Vector2d v = (Vector2d) obj; if (this.getName() == null && v.getName() != null) { eq = false; } } return eq; }
@Override public boolean equals(Object obj) { booleaneq = false; if (obj == this) { eq = true; } else if (obj != null && this.getClass() == obj.getClass()) { Vector2d v = (Vector2d) obj; if (this.getName() == null && v.getName() != null) { eq = false; } else { eq = this.getName().equals( v.getName() ) && Double.compare( this.getX(), v.getX() ) == 0 && Double.compare( this.getY(), v.getY() ) == 0; } } return eq; }
Observe That... • instead of directly using the attributes, we use accessor methods where possible • this reduces code duplication, especially if accessing an attribute requires a lot of code • this gives us the possibility to change the representation of the attributes in the future • as long as we update the accessor methods (but we would have to do that anyway to preserve the API) • for example, instead of two attributes x and y, we might want to use an array or some sort of Container • the notes [notes 2.3.1] call this delegating to accessors
setX(), setY(), and set() public void setX(double x) { this.x = x; } public void setY(double y) { this.y = y; } public void set(double x, double y) { this.x = x; this.y = y; this.setX(x); this.setY(y); } public void set(String name, double x, double y) { this.name = name; this.x = x; this.y = y; this.name = name; this.set(x, y); } delegate to mutator delegate to mutator
Observe That... • instead of directly modifying the attributes, we use mutator methods where possible • this reduces code duplication, especially if modifying an attribute requires a lot of code • this gives us the possibility to change the representation of the attributes in the future • as long as we update the mutator methods (but we would have to do that anyway to preserve the API) • for example, instead of two attributes x and y, we might want to use an array or some sort of Container • the notes [notes 2.3.1] call this delegating to mutators
Constructors public Vector2d(String name, double x, double y) { this.name = name; this.x = x; this.y = y; this.set(name, x, y); } public Vector2d(Vector2d v) { this.name = v.name; this.x = v.x; this.y = v.y; this(v.getName(), v.getX(), v.getY()); } delegate to mutator delegate to constructor and accessors
More Constructors public Vector2d(double x, double y) { this(null, x, y); } public Vector2d() { this(null, 0.0, 0.0); } good, delegate to constructor good, delegate to constructor
Observe That... • all of the simpler constructors do nothing except call the most general constructor • again, this reduces code duplication • notes [notes 2.3.1] call this constructor chaining • the most general constructor calls a mutator • again, this reduces code duplication
Things to Think About • how do you implement Vector2d using an array to store the coordinates? • how do you implement Vector2d using a Container to store the coordinates? • how do you implement VectorNd, an N-dimensional vector?