Download Abstract Classes and Interfaces in Java: CS61B Lecture #9 and more Slides Data Structures and Algorithms in PDF only on Docsity!
CS61B Lecture #9: Abstract Classes and All That
Public Service Announcement:
⢠Upsilon Pi Epsilon (UPE), the CS honor society, holds tutoring and
advising services for students. Friendly, courteous students who
understand what youāre going through can help. For further infor-
mation, go to http://upe.cs.berkeley.edu/services/tutoring
Abstract Methods and Classes
⢠Instance method can be abstract: No body given; must be supplied
in subtypes.
⢠One good use is in specifying a pure interface to a family of types:
/** A drawable object. / public abstract class Drawable { // "abstract" = "canāt say new Drawable" /* Expand THIS by a factor of SIZE / public abstract void scale (double size); /* Draw THIS on the standard output. */ public abstract void draw (); }
Now a Drawable is something that has at least the operations scale
and draw on it. Canāt create a Drawable because itās abstractāin
particular, it has two methods without any implementation.
⢠BUT, we can write methods that operate on Drawables:
void drawAll (Drawable[] thingsToDraw) { for (int i = 0; i < thingsToDraw.length; i += 1) thingsToDraw[i].draw (); }
⢠But draw has no implementation! How can this work?
Interfaces
⢠In generic use, an interface is a āpoint where interaction occurs
between two systems, processes, subjects, etc.ā ( Concise Oxford
Dictionary).
⢠In programming, often use the term to mean a description of this
generic interaction, specifically, a description of the functions or
variables by which two things interact.
⢠Java uses the term to refer to a slight variant of an abstract class
that contains only abstract methods (and static constants).
⢠Idea is to treat Java interfaces as the public specifications of data
types, and classes as their implementations:
public interface Drawable { void scale (double size); // Automatically public abstract. void draw (); }
public class Rectangle implements Drawable { ... }
⢠Interfaces are automatically abstract: canāt say new Drawable();
can say new Rectangle(...).
Multiple Inheritance
⢠Can extend one class, but implement any number of interfaces.
⢠Contrived Example:
interface Readable { | void copy (Readable r, Object get (); | Writable w) } | { | w.put (r.get ()); interface Writable { | } void put (Object x); | } | | class Source implements Readable { | class Sink implements Writable { public Object get () { ... } | public void put (Object x) { ... } } | }
class Variable implements Readable, Writable { public Object get () { ... } public void put (Object x) { ... } }
⢠The first argument of copy can be a Source or a Variable. The
second can be a Sink or a Variable.
Map in Java
/** Function with one integer argument */ | IntList map (IntUnaryFunction proc, | IntList items) { public interface IntUnaryFunction { | if (items == null) int apply (int x); | return null; } | else return new IntList ( | proc.apply (items.head), | map (proc, items.tail) | ); | }
⢠Itās the use of this function thatās clumsy. First, define class for
absolute value function; then create an instance:
class Abs implements IntUnaryFunction { public int apply (int x) { return Math.abs (x); } }
map (new Abs (), some list);
⢠Or, we can write a lambda expression (sort of):
map (new IntUnaryFunction () { public int apply (int x) { return x*x; } }, some list);
A Puzzle
class A { void f () { System.out.println ("A.f"); } void g () { f (); /* or this.f() */ } //static void g (A y) { y.f(); } }
| class B extends A { | void f () { | System.out.println ("B.f"); | } | } class C { static void main (String[] args) { B aB = new B (); h (aB); }
static void h (A x) { x.g() } //static void h (A x) { A.g(x); } x.g(x) also legal here }
1. What is printed?
2. What if we made g static?
3. What if we made f static?
4. What if f were not defined in A?
Choices:
a. A.f
b. B.f
c. Some kind of error
A Puzzle
class A { void f () { System.out.println ("A.f"); } //void g () { f (); /* or this.f() */ } static void g (A y) { y.f(); } }
| class B extends A { | void f () { | System.out.println ("B.f"); | } | } class C { static void main (String[] args) { B aB = new B (); h (aB); }
//static void h (A x) { x.g() } static void h (A x) { A.g(x); } x.g(x) also legal here }
1. What is printed?
2. What if we made g static?
3. What if we made f static?
4. What if f were not defined in A?
Choices:
a. A.f
b. B.f
c. Some kind of error
A Puzzle
class A { void f () { System.out.println ("A.f"); } //void g () { f (); /* or this.f() */ } static void g (A y) { y.f(); } }
| class B extends A { | void f () { | System.out.println ("B.f"); | } | } class C { static void main (String[] args) { B aB = new B (); h (aB); }
//static void h (A x) { x.g() } static void h (A x) { A.g(x); } x.g(x) also legal here }
1. What is printed?
2. What if we made g static?
3. What if we made f static?
4. What if f were not defined in A?
Choices:
a. A.f
b. B.f
c. Some kind of error
A Puzzle
class A { static void f () { System.out.println ("A.f"); } void g () { f (); /* or this.f() */ } //static void g (A y) { y.f(); } }
| class B extends A { | static void f () { | System.out.println ("B.f"); | } | } class C { static void main (String[] args) { B aB = new B (); h (aB); }
static void h (A x) { x.g() } //static void h (A x) { A.g(x); } x.g(x) also legal here }
1. What is printed?
2. What if we made g static?
3. What if we made f static?
4. What if f were not defined in A?
Choices:
a. A.f
b. B.f
c. Some kind of error
A Puzzle
class A {
void g () { f (); /* or this.f() */ } //static void g (A y) { y.f(); } }
| class B extends A { | void f () { | System.out.println ("B.f"); | } | } class C { static void main (String[] args) { B aB = new B (); h (aB); }
static void h (A x) { x.g() } //static void h (A x) { A.g(x); } x.g(x) also legal here }
1. What is printed?
2. What if we made g static?
3. What if we made f static?
4. What if f were not defined in A?
Choices:
a. A.f
b. B.f
c. Some kind of error
Answer to Puzzle
1. Executing java C prints , because
1. C.main calls h and passes it aB, whose dynamic type is B.
2. h calls x.g(). Since g is inherited by B, we execute the code for
g in class A.
3. g calls this.f (). Now this contains the value of hās argument,
whose dynamic type is B. Therefore, we execute the definition of
f that is in B.
4. In calls to f, in other words, static type is ignored in figuring out
what method to call.
2. If g were static, we see ; selection of f still depends on dynamic
type of this.
3. If f were static, would print because then selection of f would
depend on static type of this, which is A.
4. If f were not defined in A, weād get.
Answer to Puzzle
1. Executing java C prints B.f, because
1. C.main calls h and passes it aB, whose dynamic type is B.
2. h calls x.g(). Since g is inherited by B, we execute the code for
g in class A.
3. g calls this.f (). Now this contains the value of hās argument,
whose dynamic type is B. Therefore, we execute the definition of
f that is in B.
4. In calls to f, in other words, static type is ignored in figuring out
what method to call.
2. If g were static, we see B.f; selection of f still depends on dynamic
type of this.
3. If f were static, would print A.f because then selection of f would
depend on static type of this, which is A.
4. If f were not defined in A, weād get a compile-time error.
Specification Seen by Clients
⢠The clients of a module (class, program, etc.) are the programs or
methods that use that moduleās exported definitions.
⢠In Java, intention is that exported definitions are designated public.
⢠Clients are intended to rely on specifications, not code.
⢠Syntactic specification: method and constructor headersāsyntax
needed to use.
⢠Semantic specification: what they do. No formal notation, so use
comments.
ā Semantic specification is a contract.
ā Conditions client must satisfy ( preconditions, marked āPre:ā in
examples below).
ā Promised results ( postconditions).
ā Design these to be all the client needs!
ā Exceptions communicate errors, specifically failure to meet pre-
conditions.
Histogram Specification and Use
/** A histogram of floating-point values / public interface Histogram { /* The number of buckets in THIS. */ int size ();
/** Lower bound of bucket #K. Pre: 0<=K<size(). */ double low (int k);
/** # of values in bucket #K. Pre: 0<=K<size(). */ int count (int k);
/** Add VAL to the histogram. */ void add (double val); }
Sample output:
void fillHistogram (Histogram H, Scanner in) { while (in.hasNextDouble ()) H.add (in.nextDouble ()); }
void printHistogram (Histogram H) { for (int i = 0; i < H.size (); i += 1) System.out.printf (">=%5.2f | %4d%n", H.low (i), H.count (i)); }