Counter
& UpperBoundedCounter
Counter
: current value (val
), increment (up
),
decrement (down
), retrieve current value (getVal
), toString
UpperBoundedCounter
: current value (val
), upper bound (limit
),
bounded increment (up
), decrement (down
), retrieve current value (getVal
),
retrieve limit (getLimit
), toString
UpperBoundedCounter
, there could also be a
LowerBoundedCounter
, and where there's UpperBoundedCounter
and LowerBoundedCounter
there could also be …. (By the same token, where there's a Counter
, there could be
an UpCounter
and/or a DownCounter
.)
App.java | App.out |
---|---|
public class App { public static void main(String [] args) { System.out.println("Playing with Counter"); Counter c = new Counter(); System.out.println("Initially: " + c); for (int i = 1; i <= 20; i++) c.up(); System.out.println("After 20 up's: " + c); for (int i = 1; i <= 3; i++) c.down(); System.out.println("After 3 down's: " + c); System.out.println(); //--------- System.out.println("Playing with UpperBoundedCounter"); UpperBoundedCounter ubc = new UpperBoundedCounter(10); System.out.println("Initially: " + ubc); for (int i = 1; i <= 20; i++) ubc.up(); System.out.println("After 20 up's: " + ubc); for (int i = 1; i <= 3; i++) ubc.down(); System.out.println("After 3 down's: " + ubc); } } |
Playing with Counter Initially: A counter with value 0 After 20 up's: A counter with value 20 After 3 down's: A counter with value 17 Playing with UpperBoundedCounter Initially: A upper-bounded counter with value 0 and limit 10 After 20 up's: A upper-bounded counter with value 10 and limit 10 After 3 down's: A upper-bounded counter with value 7 and limit 10 |
Counter.java | UpperBoundedCounter.java |
---|---|
public class Counter { Counter() {val = 0;} void up() {val++;} void down() {val--;} int getVal() {return val;} public String toString() { return "A counter with value " + val; } private int val; } |
public class UpperBoundedCounter { UpperBoundedCounter(int limit) { val = 0; this.limit = limit; } void up() {if (val < limit) val++;} void down() {val--;} int getVal() {return val;} int getLimit() {return limit;} public String toString() { return "A upper-bounded counter with value " + val + " and limit " + limit; } private int val; private int limit; } |
val
in both classes could have been done at the point of declaration:
private int val = 0;
Counter
class
new Counter()
UpperBoundedCounter
would still require a constructor be written (since a limit must be supplied).
Counter.java | UpperBoundedCounter.java |
---|---|
public class Counter { Counter() {val = 0;} void up() {val++;} void down() {val--;} int getVal() {return val;} public String toString() { return "A counter with value " + val; } private int val; } |
public class UpperBoundedCounter { UpperBoundedCounter(int limit) { counter = new Counter(); this.limit = limit; } // New methods void up() {if (counter.getVal() < limit) counter.up();} int getLimit() {return limit;} public String toString() { return "A upper-bounded counter with value " + getVal() + " and limit " + limit; } //Delegation methods void down() {counter.down();} int getVal() {return counter.getVal();} private Counter counter; private int limit; } |
Address
class being used as a member by a Person
class and a Company
class
Person
and Company
).
B
includes extends C
in its class header:
class B extends C { … }class
B
is said to inherit from class C
C
belong to B
as well.
B
can define its own instance variables and methods as well
B
defines methods identical to those of C
B
defines instance variables with the same name
as one in C
. While this variable shadowing is legal, it is not
all that useful, and in fact, is not recommended.
private
of the parent's instance variable (val
)Counter.java | UpperBoundedCounter.java |
---|---|
public class Counter {
Counter() {val = 0;}
void up() {val++;}
void down() {val--;}
int getVal() {return val;}
public String toString() {
return "A counter with value " + val;
}
/*private*/ int val;
}
|
public class UpperBoundedCounter extends Counter { UpperBoundedCounter(int limit) {this.limit = limit;} // Overridden methods void up() {if (val < limit) val++;} public String toString() { return "A upper-bounded counter with value " + val + " and limit " + limit; } // new method int getLimit() {return limit;} private int limit; } |
protected
Counter.java | UpperBoundedCounter.java |
---|---|
public class Counter {
Counter() {val = 0;}
void up() {val++;}
void down() {val--;}
int getVal() {return val;}
public String toString() {
return "A counter with value " + val;
}
protected int val;
}
|
public class UpperBoundedCounter extends Counter { UpperBoundedCounter(int limit) {this.limit = limit;} // Overridden methods void up() {if (getVal() < limit) super.up();} public String toString() {i return "A upper-bounded counter with value " + val + " and limit " + limit; } int getLimit() {return limit;} private int limit; } |
super
)
Counter.java | UpperBoundedCounter.java |
---|---|
public class Counter { Counter() {val = 0;} void up() {val++;} void down() {val--;} int getVal() {return val;} public String toString() { return "A counter with value " + val; } private int val = 0; } |
public class UpperBoundedCounter extends Counter {
UpperBoundedCounter(int limit) {this.limit = limit;}
// Overridden methods
void up() {if (getVal() < limit) super.up();}
public String toString() {
return "A upper-bounded counter with value " +
getVal() + " and limit " + limit;
}
int getLimit() {return limit;}
private int limit;
}
|
extends
clause, there's no sign of the
inherited class (superclass) in the definition of the inheriting class (subclass).
Counter
UpperBoundedCounter
Counter UpperBoundedCounter
UpperBoundedCounter
is-a Counter
,
an UpperBoundedCounter
object can appear wherever a Counter
object can appear
UpperBoundedCounter
is a subclass of
Counter
, it inherited ALL Counter
's state and behavior, and thus, any method or field access
that can be performed on a Counter
object can be performed on an UpperBoundedCounter
object as well.
UpperBoundedCounter
may contain state/behavior tht Counter
does
not (e.g. limit
and getLimit
), thus a Counter
object cannot appear in
a context expecting an UpperBoundedCounter
.
Counter c; UpperBoundedCounter ubc = new UpperBoundedCounter(10); c = ubc;or equivalently (but possibly more confusing):
Counter c= new UpperBoundedCounter(10);but not
UpperBoundedCounter ubc = new Counter(); // illegal
App2.java | App2.out |
---|---|
public class App2 { public static void main(String [] args) { Counter [] counters = {new Counter(), new UpperBoundedCounter(10)}; for (int i = 0; i < counters.length; i++) { System.out.println("Playing with counters[" + i + "]:"); doIt(counters[i]); } } static void doIt(Counter counter) { System.out.println("\tInitially: " + counter); for (int i = 1; i <= 20; i++) counter.up(); System.out.println("\tAfter 20 up's: " + counter); for (int i = 1; i <= 3; i++) counter.down(); System.out.println("\tAfter 3 down's: " + counter); } } |
Playing with counters[0]: Initially: A counter with value 0 After 20 up's: A counter with value 20 After 3 down's: A counter with value 17 Playing with counters[1]: Initially: An upper-bounded counter with value 0 and limit 10 After 20 up's: An upper-bounded counter with value 10 and limit 10 After 3 down's: An upper-bounded counter with value 7 and limit 10 |
Counter c; UpperBoundedCounter ubc;it is clear that the type of the variable
c
is the class Counter
(and similarly for the type
of ubc
.
Counter c = new Counter(); UpperBoundedCounter ubc = new UpperBoundedCounter(12);while we can tell the types of the objects reference by
c
(it's a Counter
object) and y ubc
(an UpperBoundedCounter
object), it's important to realize that the objects are not actually created until we execute
the program (and actually call the new
operator).
up
on Counter
and UpperBoundedCounter
objects manifested
completely different behavior
toString
Counter
objects can
operate — without any further modification — on any object belonging to a subclass of Counter
UpperBoundedCounter
into a project does not require any recompilation
or other deployment of any existing code that works with Counter
.
UpperBoundedCounter
despite the fact that
the method was written a month ago, and UpperBoundedCounter
just coded this morning:
void clear(Counter counter) { while (counter.getVal() > 0) counter.down();
void jumpUp(Counter counter, int howMany) { for (int i = 1; i <= howMany; i++) counter.up();
will invoke the proper up
method depending upon whether a Counter
or
UpperBoundedCounter
object is passed to jumpUp
(ensuring the integrity of the
limit for the latter).
is-a
relationship between subclass and superclass, and the consequential Substitution Principle, tells us that an object
of a subclass can always appear in any context that an object of the superclass can appear in.
Counter c = new UpperBoundedCounter(12);
Counter c; UpperBoundedCouner ubc; … c = ubc;
Counter
) reference to
that of a subclass (UpperBoundedCounter
)
UpperBoundedCounter ubc = new UpperBoundedCounter(10); Counter c = ubc; // perfectly legal — upcast ubc = c; // shouldn't this be legal? —c
contains a reference to anUpperBoundedCounter
object
Counter c = new Counter(); UpperBoundedCounter ubc = c; // should be illegal —c
contains a reference to aCounter
object
Counter c; Scanner keyboard = new Scanner(System.in); System.out.print("c or u? "); String choice = keyboard.next(); if (choice.equals("c") c = new Counter(); else c = new UpperBoundedCounter(12); // legal upcast UpperBoundedCounter ubc = c; // legal? illegal?
UpperBoundedCounter ubc = (UpperBoundedCounter)c; // legal? illegal? ... won't know until runtime
class Name { Name(String first, String last) { this.first first; this.last = last; } … private String first, last; }
Note that one must supply first and last names when creating a Name
object.
Name name = new Name("Gerald", "Weiss");
Now, consider a subclass FormalName
that inherits from Name
and adds a salutation:
class FormalName extends { … private String salutation; }
FormalName
is-a Name
as well, and thus when one creates a FormalName
you are also (implicitly) creating a Name
object and thus need to supply a first/last name as
well as the salutation, e.g.:
FormalName formalName = new FormalName("Mr.", "Gerald", "Weiss");
this gives us the constructor:
FormalName(String salutation, String first, String last) { this.salutation = salutation; … }
The question becomes, how do we initialize the instance variables of the Name
class?
FormalName
's constructor cannot/should-not do it:
first
andlast
may be declared private
(as they are
above).
first
andlast
are not the responsibility of FormalName
;
they belong to Name
super
:
FormalName(String salutation, String first, String last) { super(first, last); this.salutation = salutation; }in the context of constructors,
super
is used by a subclass to pass constructor arguments
to the superclass
this
in the constructor but that is to pass arguments between overloaded
constructors within the same class.
super
must be the first line of code in the constructor
FormalName
had been defined using composition instead of inheritance:
class FormalName { FormalName(String salutation, String first, String last) { name = new Name(first, last); this.salutation = salutation; } … private Name name; private String salutation; }
name
is an actual reference variable under FormalName
's control
and must have an object created and its reference assigned to name
Name
methods must be repeated as delegation methods in FormaName
UpCounter
, DownCounter
, Counter
,
UppperBoundedUpCounter
, UppperBoundedCounter
, LowerBoundedDownCounter
,
LowerBoundedCounter
, BoundedCounter
?