Classes

advertisement
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]
1
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;
}
}
2
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

3
because we used getClass()
Goals for Today
implication of getClass() in equals()
implement a simple mutable vector class




4
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)
5
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) );
System.out.println( y.equals(x) );
6
// false
// 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
7
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

8
Liskov Substitution Principle
often cited as a fundamental principle of objectoriented 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]
9

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
10
Mutable Classes
11
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 2dimensional mathematical vectors
12
What Can Mathematical Vectors Do?








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
...
13
equals()
@Override public boolean equals(Object obj)
{
boolean eq = false;
if (obj == this)
{
eq = true;
}
return eq;
}
14
@Override public boolean equals(Object obj)
{
boolean eq = false;
if (obj == this) { eq = true; }
else if (obj != null && this.getClass() == obj.getClass())
{
}
return eq;
}
15
@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;
}
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;
if (this.getName() == null && v.getName() != null)
{
eq = false;
}
}
return eq;
}
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; }
else
{
eq = this.getName().equals( v.getName() ) &&
Double.compare( this.getX(), v.getX() ) == 0 &&
Double.compare( this.getY(), v.getY() ) == 0;
}
}
return eq;
}
18
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
19
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.setX(x);
this.y = y;
delegate to mutator
this.setY(y);
}
public void set(String name, double x, double y)
{ this.name = name;
this.name = name;
}
20
this.x = x;
this.y = y;
this.set(x, y);
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
21
Constructors
public Vector2d(String name, double x, double y) {
this.name = name;
this.x = x;
delegate to mutator
this.y = y;
this.set(name, x, y);
}
public Vector2d(Vector2d v) {
this.name = v.name;
this.x = v.x;
delegate to constructor and accessors
this.y = v.y;
this(v.getName(), v.getX(), v.getY());
}
22
More Constructors
public Vector2d(double x, double y)
{
this(null, x, y);
good, delegate to constructor
}
public Vector2d()
{
this(null, 0.0, 0.0); good, delegate to constructor
}
23
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

24
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?
25
Download