0% found this document useful (0 votes)
59 views29 pages

3 Polymorphism 1

This document discusses various topics related to polymorphism in object-oriented programming, including: 1) Upcasting and how it allows subclasses to be used where the parent class is expected. 2) Method binding at runtime based on the actual object type, allowing polymorphic method calls (late binding). 3) Examples of polymorphism in action, where calling the same method on different object types causes the appropriate subclass method to be invoked. 4) Abstract classes and how they can be used to prevent direct instantiation of base classes.

Uploaded by

Kami Khan
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
59 views29 pages

3 Polymorphism 1

This document discusses various topics related to polymorphism in object-oriented programming, including: 1) Upcasting and how it allows subclasses to be used where the parent class is expected. 2) Method binding at runtime based on the actual object type, allowing polymorphic method calls (late binding). 3) Examples of polymorphism in action, where calling the same method on different object types causes the appropriate subclass method to be invoked. 4) Abstract classes and how they can be used to prevent direct instantiation of base classes.

Uploaded by

Kami Khan
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
You are on page 1/ 29

Today’s Topics

 Upcasting again
 Method-call binding
 Why polymorphism is good
 Constructors and polymorphism
 Downcasting
 Several digressions: the Object class, object
wrappers, the Class class, reflection
Upcasting Again
class CellPhone {
cellPhone() { //…}
public void ring(Tune t) { t.play(); }
}
class Tune {
public void play() {
System.out.println("Tune.play()");
}
}
class ObnoxiousTune extends Tune{
ObnoxiousTune() { // …}
// …
}
An ObnoxiousTune “is-a” Tune
public class DisruptLecture {
public static void main(String[] args) {
CellPhone noiseMaker = new CellPhone();
ObnoxiousTune ot = new ObnoxiousTune();
noiseMaker.ring(ot); // ot works though Tune called for
}
}
Tune A “UML” diagram

ObnoxiousTune
Aspects of Upcasting
 Upcasting is a cast
 The exact type of the object is lost
 What gets printed in the CellPhone example?
 Tune.play() (what were you expecting?)
 Is this “the right thing to do”?
 The alternative: write separate ring() methods
for each subclass of Tune?
Another Example
class CellPhone {
CellPhone() { //…}
public void ring(Tune t) { t.play(); }
}
class Tune {
Tune() { //…}
public void play() {
System.out.println("Tune.play()");
}
}
class ObnoxiousTune extends Tune{
ObnoxiousTune() { // …}
public void play() {
System.out.println("ObnoxiousTune.play()");
}
}
Polymorphism
 The second example prints
ObnoxiousTune.play()
 Since an ObnoxiousTune object was sent to
ring(), the ObnoxiousTune’s play() method
is called.
 This is called “polymorphism”
 Most people think it’s “the right thing to do”
Method-Call Binding
 The correct method is attached (“bound”) to
the call at runtime.
 The runtime system discovers the type of
object sent to ring(), and then selects the
appropriate play() method:
 if it’s a Tune object, Tune’s play() is called
 if it’s an ObnoxiousTune object,
ObnoxiousTune’s play() is called
Is Java Always Late-Binding?
 Yes, always, except for final, static and private
methods.
 final methods can’t be overridden, so the compiler
knows to bind it.
 The compiler may be able to do some speed
optimizations for a final method, but we
programmers are usually lousy at estimating where
speed bottlenecks are…
 In C++, binding is always at compile-time, unless
the programmer says otherwise.
Another Polymorphism Example
public static void main(String[] args) {
CellPhone noiseMaker = new CellPhone();
Tune t;
double d = Math.random();
if (d > 0.5)
t = new Tune();
else
t = new ObnoxiousTune();
noiseMaker.ring(t);
}
References and Subclasses
 We’ve seen that an ObnoxiousTune object can be
supplied where a Tune object is expected.
 Similarly, a Tune reference can refer to an
ObnoxiousTune object.
 In both cases, the basic question is: “Is an
ObnoxiousTune really a proper Tune?” Yes!
 This doesn’t work the other way around: any old
Tune may not be an ObnoxiousTune.
 So, an ObnoxiousTune reference cannot refer to a
Tune object.
Let’s Fool the Compiler!
public static void main(String[] args) {
CellPhone noiseMaker = new CellPhone();
Tune t1 = new Tune();
Tune t2 = new ObnoxiousTune();
noiseMaker.ring(t1);
noiseMaker.ring((Tune)t2);
}

Nothing changes… ObnoxiousTune.play()


is still printed.
Let’s Fool the Compiler!
public static void main(String[] args) {
CellPhone noiseMaker = new CellPhone();
Tune t1 = new Tune();
Tune t2 = new ObnoxiousTune();
noiseMaker.ring(t1);
noiseMaker.ring((ObnoxiousTune) t2);
}

Nothing changes…
Let’s Fool the Compiler!
public static void main(String[] args) {
CellPhone noiseMaker = new CellPhone();
Tune t1 = new Tune();
Tune t2 = new ObnoxiousTune();
noiseMaker.ring((ObnoxiousTune) t1);
noiseMaker.ring(t2);
}

This compiles, but gets a CastClassException


at runtime (even though Tune has a play() method).

“Downcasting” can be dangerous!


Extensibility I
public static void main(String[] args) {
CellPhone noiseMaker = new CellPhone();
SimpleInput keyboard = new SimpleInput();
System.out.println("Enter number of tunes:");
int numTunes = keyboard.nextInt();
Tune[] tunes = new Tune[numTunes];
for (int i = 0; i < numTunes; i++) {
System.out.println("Enter tune type");
System.out.println("(Tune=1, ObnoxiousTune=2)");
int tuneType = keyboard.nextInt();
switch(tuneType) {
case 1: tunes[i] = new Tune(); break;
case 2: tunes[i] = new ObnoxiousTune(); break;
default: tunes[i] = new Tune(); break;
}
}
for (int i = 0; i < tunes.length; i++)
noiseMaker.ring(tunes[i]);
Extensibility II
public class NiceTune extends Tune {
NiceTune() {}
public void play() {
System.out.println("NiceTune.play()");
}
}
Extensibilty III
public static void main(String[] args) {
CellPhone noiseMaker = new CellPhone();
SimpleInput keyboard = new SimpleInput();
System.out.println("Enter number of tunes:");
int numTunes = keyboard.nextInt();
Tune[] tunes = new Tune[numTunes];
for (int i = 0; i < numTunes; i++) {
System.out.println("Enter tune type");
System.out.println("(Tune=1, ObnoxiousTune=2, NiceTune = 3)");
int tuneType = keyboard.nextInt();
switch(tuneType) {
case 1: tunes[i] = new Tune(); break;
case 2: tunes[i] = new ObnoxiousTune(); break;
case 3: tunes[i] = new NiceTune(); break;
default: tunes[i] = new Tune(); break;
}
}
for (int i = 0; i < tunes.length; i++)
noiseMaker.ring(tunes[i]);
}
Extensibility
 The Tunes program allows additional
subclasses to be constructed and easily
integrated.
 Polymorphism is the key in letting this
happen.
 It was OK to make Tune objects.
 How to prevent creating base class objects?
Abstract Classes
 As always, get the compiler involved in enforcing our
decisions.
abstract class Glyph {
abstract void draw();
Glyph() {
System.out.println(“Glyph() before draw”);
draw();
System.out.println(“Glyph() after draw”);
 Now} it’s a compiler error if we try to
make a Glyph
}
object (but Glyph references are OK).
 Subclasses are abstract (and we must so state) until all
abstract methods have been defined.
Glyph Example (cont.)
class RoundGlyph extends Glyph {
int radius = 1;
RoundGlyph(int r) {
radius = r;
System.out.println(“RoundGlyph(), radius=“ + radius);
}
void draw() {
System.out.println(“RoundGlyph.draw(), radius=“ + radius);
}

public class GlyphTest {


public static void main(String[] args) {
new RoundGlyph(5);
}
}
Glyph Example (cont.)
 This produces
Glyph() before draw()
RoundGlyph.draw(), radius=0
Glyph() after draw()
RoundGlyph(), radius= 5

 Guideline for constructors:


 “Do as little as possible to set the object into a
good state, and if you can possibly avoid it, don’t
call any methods.”
But What If Draw() Isn’t Abstract?
abstract class Glyph {
void draw() { System.out.println(“Glyph.draw()”); }
abstract void doNothing(); // added to keep Glyph abstract
Glyph() {
System.out.println(“Glyph() before draw”);
draw();
System.out.println(“Glyph() after draw”);
}
}
Glyph Example (cont.)
 The Glyph constructor is at work creating the
Glyph “sub-object” within a RoundGlyph
object.
 draw() is overridden in RoundGlyph.
 A RoundGlyph object is being created.
 RoundGlyph’s draw() method is called
 Polymorphism rules!
Inheritance is Cool, But…
Composition + Inheritance is Cooler
abstract class Actor { public class Transmogrify {
abstract void act(); public static void
} main(String[] args){
class HappyActor extends Actor { Stage s = new Stage();
public void act() { //…} s.go(); //happy actor
} s.change();
class SadActor extends Actor { s.go() // sad actor
public void act() { //…} }
} }
class Stage {
Actor a = new HappyActor();
void change() { a = new SadActor(); }
void go() { a.act(); }
}
H&C’s Tips for Inheritance
 Place common operations and fields in the
superclass.
 Don’t use protected fields very often.
 Use inheritance to model the “is-a” relationship.
 Don’t use inheritance unless all inherited methods
make sense.
 Use polymorphism, not type information.
 Don’t overuse reflection.
Digression: The Object Class
 Object is the ancestor of every class.
 We don’t need to say, e.g.,
class Employee extends Object

 We can use an Object reference to refer to


any object.
Object obj = new Employee(“Harry Hacker”, 35000);

 But to use any of the methods of Employee,


we must cast to the correct type.
int salary = ((Employee) obj).getSalary();
Some Object Methods
 clone(): Creates and returns a copy of this
object. Returns an Object.
 equals(): Indicates whether some other object
is "equal to" this one. Returns a Boolean.
 getClass(): Returns the runtime class of an
object. Returns a Class.
 toString(): Returns a string representation of
the object. Returns a String.
The equals() Method
 As implemented in the Object class, this just
tests whether two references point to the same
memory.
 This isn’t usually what we want.
 Override to get correct behavior.
 Pay attention to the Java language spec. to do
it right.
The Rules For equals()
 Reflexive: x.equals(x) is true
 Symmetric: if x.equals(y) then y.equals(x)
 Transitive: if x.equals(y) and y.equals(z) then
x.equals(z)
 Consistent: if x.equals(y) now, and x and y
don’t change, then x.equals(y) later
 If x is non-null, x.equals(null) is false
equals() Example
class Employee {
private String name;
private double salary;
private Date hireDay;
public boolean equals(Object otherObject) {
if (this == otherObject) return true;
if (otherObject == null) return false;
if (getClass() != otherObject.getClass())
return false;
Employee other = (Employee)otherObject;
return name.equals(other.name)
&& salary == other.salary
&& hireDay.equals(other.hireDay);
}
}

You might also like