Puzzle 3

25
Puzzle 3 1 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]

description

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) ); } } - PowerPoint PPT Presentation

Transcript of Puzzle 3

Page 1: Puzzle 3

Puzzle 3

1

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]

Page 2: Puzzle 3

Solution

2

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; } }

Page 3: Puzzle 3

Equality of Instances of Different Types

3

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()

Page 4: Puzzle 3

Goals for Today

4

implication of getClass() in equals() implement a simple mutable vector class

delegating to accessor and mutator methods constructor chaining

Page 5: Puzzle 3

Implication of Same Type Equality

5

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)

Page 6: Puzzle 3

6

our implementation of equals() says that a PhoneNumber is never equal to a CountedPhoneNumber because they have different types

// client code somewherePhoneNumber x = new PhoneNumber(416, 736, 2100);

CountedPhoneNumber y = new CountedPhoneNumber(416, 736, 2100);

System.out.println( x.equals(y) ); // falseSystem.out.println( y.equals(x) ); // false

Page 7: Puzzle 3

7

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 somewherePhoneNumber 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

Page 8: Puzzle 3

Substituting Types

8

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

Page 9: Puzzle 3

Liskov Substitution Principle

9

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]

Page 10: Puzzle 3

10

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

Page 11: Puzzle 3

Mutable Classes

11

Page 12: Puzzle 3

Mutable Classes

12

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

Page 13: Puzzle 3

What Can Mathematical Vectors Do?

13

add subtract multiply by scalar set components get components construct equals toString

Vector2d- x: double

- y: double

- name: String

+ Vector2d(): Vector2d

+ Vector2d(double, double): Vector2d

+ Vector2d(String, double, double): Vector2d

+ Vector2d(Vector2d): Vector2d

+ add(Vector2d): void

+ equals(Object): boolean

+ getX(): double

+ getY(): double

+ length(): double

+ multiply(double): void

...

Page 14: Puzzle 3

equals()

14

@Override public boolean equals(Object obj) { boolean eq = false; if (obj == this) { eq = true; }

return eq;}

Page 15: Puzzle 3

15

@Override public boolean equals(Object obj) { boolean eq = false; if (obj == this) { eq = true; } else if (obj != null && this.getClass() == obj.getClass()) {

} return eq;

}

Page 16: Puzzle 3

16

@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;}

Page 17: Puzzle 3

17

@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; if (this.getName() == null && v.getName() != null) { eq = false; }

} return eq;}

Page 18: Puzzle 3

18

@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; 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;}

Page 19: Puzzle 3

Observe That...

19

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

Page 20: Puzzle 3

setX(), setY(), and set()

20

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

Page 21: Puzzle 3

Observe That...

21

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

Page 22: Puzzle 3

Constructors

22

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

Page 23: Puzzle 3

More Constructors

23

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

Page 24: Puzzle 3

Observe That...

24

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

Page 25: Puzzle 3

Things to Think About

25

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?