Download Inheritance in Java

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
Inheritance in Java: The keyword extends
is used to signify inheritance in Java.
Outline for Lecture 13
For example, suppose we want to
subclass Point to create a class Circle.
Then we might write—
A. Abstract classes
B. this
public class Circle extends Point {
protected double radius;
public Circle() {
super(0, 0); // Call the
// superclass constructor
setRadius(0);
}
I.
Inheritance
II. Predicates &
control flow
III. File streams
IV. Exception handling
V. Modularization.
VI. Interfaces
So far, we have seen three new features
of Java:
• The keyword extends is used to signify
subclassing.
• The radius instance variable is
declared not as public or private, but
as protected.
This means that it is accessible to the
Circle class and any of its subclasses,
or other classes in its package, but not
to other classes in the system.
How does this compare with instance
variables in Smalltalk?
In declaring variables and methods to
be public, protected, or private, it is
good to follow the need-to-know
principle: Only those classes that
need to be able to access a variable or
method should be able to.
This prevents other classes from coming
to depend upon them, and allows the
programmer to change their
implementation later, if needed.
Lecture 13
Object-Oriented Languages and Systems
207
• In the process of creating a Point
object, the superclass constructor is
called. This is denoted by the call
super(…).
Continuing with the class definition …
//Three-parameter constructor
public Circle(double r, double x, double y) {
super(x, y);
setRadius(r);
}
public void setRadius(double r) {
radius = r;
}
public double getRadius(double r) {
return radius;
}
//Calculate area of circle
public double area() {
return Constants.¹ *
radius * radius;
}
//Return a representation of circle
public String printString() {
return "Center = " + "(" + x +
"," + y + "); Radius = " + radius;
}
}
Circle is a subclass of Point. Any
subclass of Circle is also a subclass of
Point.
Because Circle inherits directly from
Point, it is called a direct subclass of
Point. Point is called a direct superclass
of Circle.
© 2000 Edward F. Gehringer
CSC 517/ECE 517 Lecture Notes, Fall 2000
208
In the last lecture, our definition of Point
did not specify that it was extending a
class. Since it did not, Object was its
direct superclass.
A class can extend only one class; that is,
it can have only one direct superclass.
Because of this, Java is said to have
single inheritance.
The “opposite” of single inheritance is
multiple inheritance, as in C++.
Since Java has single inheritance, all
classes form a tree with Object at the root.
For safety’s sake, Java always makes
sure that a constructor calls its
superclass’s constructor. (Why did I say,
“For safety’s sake”?)
Thus, if the first statement in a constructor
is not a call to super, then Java implicitly
inserts the call super().
As in Smalltalk, a subclass can override
some of its superclasses’ methods.
To decide which method to use, Java
follows the same rule as Smalltalk: If an
invoked method is not defined in a class,
then the direct superclass is checked, then
its direct superclass, etc.
That is, the compiler searches from the
object’s class up the tree toward Object
until it finds a definition of the method.
Unlike in Smalltalk, the compiler can
determine whether the call will succeed.
Why?
Lecture 13
Object-Oriented Languages and Systems
209
The Winston & Narasimhan text suggests
a rule for deciding where to site instance
methods (and instance variables. Two
criteria should be satisfied:
• There should be no needless
duplication of an instance method or
variable.
• Each instance method and variable
should be useful in all the subclasses
of the class in which it is defined.
Abstract classes: In Smalltalk, our abstract
classes were identified by having some of
their methods implemented as
subclassResponsibility.
In Java, it is possible to declare a class to
be abstract, and then any attempt to
create an instance of it will be flagged by
the compiler.
Consider an abstract class Shape, with
subclasses of Circle and Rectangle:
public abstract class Shape {
public abstract double area();
public abstract double circumf();
}
Notice that not only the class, but also its
methods are declared to be abstract.
A method declared as abstract is
required to be implemented in every
subclass of the class declaring the
abstract method.
© 2000 Edward F. Gehringer
CSC 517/ECE 517 Lecture Notes, Fall 2000
210
Note that an abstract method has no body; a
semicolon immediately follows the signature.
If a class extends Shape but does not
implement both area and circumf, the
compiler will complain.
In essence, defining abstract classes
allows us to make aspects of our design
explicit in our code.
Here is the code for the Circle class:
public class Circle extends Shape {
protected double radius;
public Circle() {radius = 1.0;}
public Circle(double r) {radius = r;}
public double area() {
return Constants.¹ * radius * radius;
}
public double circumf() {
return 2 * Constants.¹ * radius;
}
public double getRadius()
{return radius;}
}
What would happen here if we didn’t have
the getRadius() access method?
Here is the code for Rectangle:
public class Rectangle extends Shape {
protected double w, h;
public Rectangle() {};
public Rectangle(double wd, double ht) {
w = wd; h = ht;}
public double area() {return w*h;}
public double circumf() {return 2*(w+h);}
public double getWidth() {return w;}
Lecture 13
Object-Oriented Languages and Systems
211
public double getHeight() {return h;}
}
If a class is not supposed to be
subclassed, you can declare it as final.
Then any attempt to subclass it produces
a compilation error.
this: We have seen how a constructor can
call a constructor of a superclass. It is
also possible for a constructor to call a
constructor of the same class.
For example, suppose our Rectangle
class above included a one-parameter
constructor for specifying a zero-height
rectangle (a horizontal line). It could be
implemented simply by calling the
two-parameter constructor:
public Rectangle(double wd) {
this(wd, 0.0);
}
In this call, we need to use the this
keyword. What would happen if we tried
to use Rectangle(wd, 0.0)?
In general, this in Java is like self in
Smalltalk; Smalltalk messages that would
be sent to self are sent to this in Java.
© 2000 Edward F. Gehringer
CSC 517/ECE 517 Lecture Notes, Fall 2000
212
For example, suppose we redefined the
area method of Circle in terms of
circumf:
public double area() {
return this.circumf() * radius / 2;
}
Since the receiver of the message is the
same instance as the sender, we send
this.circumf().
Instead of explictly naming this, however,
Java allows us to omit it. That is, if the
receiver is omitted, then this is
understood:
public double area() {
return circumf() * radius / 2;
}
Predicates and control flow: In general, the
expressions and statements that control
the flow of a Java program are very similar
to those in C or C++. They are covered in
¤20–¤25 of Winston and Narasimhan.
We will touch only on the highlights.
=, ==, and equals:
In Java, = means
assignment, while == means a test for
equality. As in C or C++, a common error
is to write = when you mean to test for
equality.
In Java, == compares
• values, if the objects being compared
are primitive types, or
• references, if the objects being
compared are class types.
Lecture 13
Object-Oriented Languages and Systems
213
Thus, it does not test objects for equality.
However, several classes define an
equals method to test objects for equality.
For example, suppose we define
String s1 = new String("Hello, world!");
String s2 = new String("Hello, world!");
Then s1 == s2 returns
while s1.equals(s2) returns
If we then define
String s3 = s1;
then s1 == s3 returns
and s1.equals(s3) returns
One caveat is that if we define
String s4 = "Hello, world!";
String s5 = "Hello, world!";
then s4 == s5 and s4.equals(s5) are both
true.
This is because Java treats all anonymous
Strings with the same contents as one
anonymous String that has many
references.
&& and || vs. & and |: The double
operators && and || may not evaluate both
of their operands.
The left-hand side operand is evaluated.
If the truth or falsity of the expression can
be determined from the lh. operand, then
the rh. operand is not evaluated.
© 2000 Edward F. Gehringer
CSC 517/ECE 517 Lecture Notes, Fall 2000
214
However, & and | always evaluate both of
their operands.
Ordinarily, it is more efficient to use &&
and || on Boolean expressions. Use the
others only when you are depending on
side-effects of evaluating the right-hand
expression.
The operators | and & can also be used on
integral expressions; in this case, they
perform bitwise and and or.
Augmented assignment: The form
operator = means that an operation is
performed on the lhs. and rhs. and then
assigned to the lhs. That is,
result *= 2
means the same as
result = result * 2
This is a useful shorthand, but sometimes
obscures the fact that an assignment is
being performed.
The augmented assignment may be
quicker when using arrays, because the
index expression needs to be calculated
only once. So, a[i*5] *= 2 may be
faster than a[i*5] = a[i*5] * 2.
Order of evaluation: Unlike C and C++,
Java prescribes that expressions are
evaluated left-to-right.
Thus, expressions like
++x + x
have well defined results in Java.
Lecture 13
Object-Oriented Languages and Systems
215
Switch statement: Java has a switch
statement like C or C++:
public class Demo {
public static int fibonacci(int n) {
switch (n) {
case 0: return 0;
case 1: return 1;
default: return fibonacci(n - 1)
+ fibonacci(n - 2);
}
}
}
Note that if the first two cases did not
return, we’d have to insert break
statements to prevent control from falling
through to the next case.
File streams: A stream is a sequence of
data.
• The stream that flows from your
keyboard to your program is the
standard input stream.
• The stream that flows from your
program to the display is the
standard output stream.
To read data, you create an input stream
that connects an input file to your
program.
In order to use input (or output) streams,
you need to inform Java that you want to
use classes from the input-output
package:
import java.io.FileInputStream;
import java.io.FileOutputStream;
or simply
import java.io.*;
© 2000 Edward F. Gehringer
CSC 517/ECE 517 Lecture Notes, Fall 2000
216
Then, you create an instance of
FileInputStream, and assign it to a
particular file.
FileInputStream inputFile =
new FileInputStream("/ncsu/efg/input.data");
We can use inputFile.read() to read a
single byte. However, it is usually more
convenient to read complete numbers and
strings.
To do this, one converts a FileInputStream
into an instance of StreamTokenizer.
StreamTokenizer tokens =
new StreamTokenizer(inputFile);
Tokenizers treat whitespace as delimiters.
Thus, a file containing the following
characters is viewed as a stream of seven
numbers:
1.0 2 3.1415926536 -42
5.15e6 678 0
The nextToken method moves the
tokenizer from one token to the next.
Each time it moves to a token, it assigns
the token it moves past to its instance
variable nval.
What are the values that nval takes on on
successive calls to nextToken?
The nval variable is of type double, so if
you want to change it to any other value,
you need to cast it:
(int) tokens.nval
Lecture 13
Object-Oriented Languages and Systems
217
When the tokenizer reaches the end of the
stream, nextToken returns a special value,
which is the same as the value in the
tokenizer’s TT_EOF instance variable.
Thus, to test for end-of-file, you can use
while (tokens.nextToken() != TT_EOF) {
…
i = (int) tokens.nval …
…
}
Before the end of the file is reached,
nextToken returns
• TT_NUMBER whenever it reads a number
(and it assigns the value of the number
to nval), and
• TT_WORD whenever it reads a string
(and it assigns the value of the string to
sval).
Output streams are created in a similar
fashion:
FileOutputStream outputFile =
new FileOutputStream("output.data");
You can write a byte at a time to the
output stream with the write(byte[] b)
method. However, it is usually more
convenient to use an instance of
PrintStream.
PrintStream ouput =
new PrintStream(outputFile);
You can write to PrintStreams using the
print and println methods.
After you have finished reading from or
writing to a file, you should close it:
© 2000 Edward F. Gehringer
CSC 517/ECE 517 Lecture Notes, Fall 2000
218
inputFile.close();
outputFile.close();
Exception handling: Normally, a
programmer writes code to deal with an
exceptional condition where that condition
is detected.
For example, in Smalltalk’s lazy
initialization, the programmer codes a
check for a null instance variable, and if it
is found to be null, then initializes it.
This is a good way to handle exceptions in
code that doesn’t have to deal with a lot of
them.
However, production code tends to contain
a lot of error handling, which has the effect
of “polluting” the source code.
Therefore, code is more readable if the
language provides a mechanism to deal
with exceptions elsewhere.
These exceptions include events like—
•
•
•
•
array out of bounds,
arithmetic overflow and zero-divide,
memory exhaustion,
nonexistent files or insufficient
permissions.
It is possible for a programmer to define
additional exceptions, but it is best not to
overuse exceptions.
They are expected to be unusual, so the
compiler may not concentrate on
optimizing code for dealing with them.
When an error is detected by a method,
the method will throw an exception.
Lecture 13
Object-Oriented Languages and Systems
219
The programmer can choose whether to
write code that deals with the exception.
• If (s)he chooses not to deal with the
exception, (s)he writes in the
procedure heading, e.g.,
throws IOException
to indicate that the method will not deal
with any exceptions involving I/O.
If this is done in the main method, its
header might read—
public static void main(string argv[])
throws IOException {
…
}
In this case, handling the exception is
the responsibility of the code that
invoked this method.
• If (s)he chooses to deal with the
exception, (s)he encloses the code that
may generate the exception in a try
block.
The try block is immediately followed
by one or more catch blocks.
Java stops executing statements in the
try block as soon as an exception is
thrown.
Suppose we are trying to read from a file.
If an exception occurs, exception-handling
code prints out the name of the exception.
try {
…
FileInputStream inputFile =
new FileInputStream("input.data");
…
}
catch (IOException e) {
© 2000 Edward F. Gehringer
CSC 517/ECE 517 Lecture Notes, Fall 2000
220
System.out.println(e);
}
Note that the I/O exception is actually
an instance of an exception class.
The exception may be an instance of
FileNotFoundException, which is a
subclass of IOException.
IOException
FileNotFoundException
Multiple catch blocks permit different code
to be used to handle different exceptions.
For example,
• one piece of code can be used to
handle a FileNotFoundException
(perhaps a retry, in case the file is
being created “as we speak”), and
• another piece of code to handle any
other kind of I/O exception (perhaps
just reporting the exception that
occurred).
public static String
readInputFile(String fileName) {
boolean tryAgain = true;
while (tryAgain) {
try {
tryAgain = false;
FileInputStream inputFile =
new FileInputStream(fileName);
…
}
catch (FileNotFoundException)
tryAgain = true;
}
catch (IOException e {
System.out.println(e);
}
}
…
}
Lecture 13
Object-Oriented Languages and Systems
221
Occasionally, you may want to have a
piece of code executed after a try
statement, regardless of whether an
exception occurred.
In this case, Java provides a finally
block:
try {
…
}
catch (exception-class name e){
…
}
finally {
//clean-up statements
}
Modularization of Java programs: In
Visualworks\Smalltalk, related classes are
combined into the same
This is the only structuring feature
available in Smalltalk.
Java has two different mechanisms for
grouping classes.
• Multiple classes may be defined in the
same file.
In this case, however, only one of
these classes may be public—the one
whose name matches the name of the
file.
• Classes from multiple files may be
grouped into a package.
In this case, an identical package
statement is placed at the beginning of
each file.
package csc517.project.gui;
public class Window {
…}
© 2000 Edward F. Gehringer
CSC 517/ECE 517 Lecture Notes, Fall 2000
222
package csc517.project.gui;
public class DialogBox extends Window {
…}
Package names.
• The components of package names
always correspond to the components
at the end of the pathname where they
are stored.
• The beginning of the pathname is
taken from the CLASSPATH environment
variable.
So, the full pathname could be—
/ncsu/efg/csc517/project/gui/Window.java
Protection of variables and methods: So far,
we have seen three of Java’s visibility
levels, public,
Java has two more visibility levels,
• default, i.e., declared without a visibility
specifier
int i;
Here is a table that summarizes the visibility:
Situation
Inherited by
subclass in same
package?
Inherited by
subclass in
different package?
Accessible to other
classes in same
package?
Accessible to
classes in different
packages?
Lecture 13
public
protected
default
private
Yes
Yes
Yes
No
Yes
Yes
No
No
Yes
Yes
Yes
No
No
No
No
Object-Oriented Languages and Systems
223
Note that it is possible to have public
access methods, but private instance
variables.
This allows the implementor to change the
implementation of the class without
affecting clients of the class, as long as
the signature of the methods doesn’t
change.
Also note the difference between “inherited”
and “accessible.” Subclasses inherit
private protected and protected
fields, but they can’t access them in
instances of the superclass.
The same applies to protected fields
when the subclas is not in the same
package as the superclass.
Interfaces: C++ is among those languages
offering multiple inheritance: a class may
inherit from more than one direct superclass.
This adds flexibility, but it also brings
problems.
• A class may inherit two or more
methods of a given name along
different paths. Which method should
take precedence?
Many strategies have been developed
for this, but they often produce
surprising results.
• Multiple inheritance imposes
considerable implementation difficulties
on a language, and may make it take
longer to call a method.
In view of these difficulties, Java adopted
a different approach:
© 2000 Edward F. Gehringer
CSC 517/ECE 517 Lecture Notes, Fall 2000
224
• A class is allowed to inherit methods from
more than one direct superclass, but
• all the methods inherited must be
abstract, with the exception of the
methods inherited from a single class.
This solves the problems with multiple
inheritance:
• Since only a single method is inherited,
there can’t be any conflict about which
method predominates.
• Since the inheritance graph is a tree,
implementation is comparatively simple
and fast.
The entirely-abstract classes inherited
from are called interfaces.
Let’s take a look at an interface.
interface Drivable {
boolean startEngine();
void stopEngine();
float accelerate(float acc);
boolean turn(Direction dir);
}
The keyword interface replaces the word
class in the class definition, and all
methods must be abstract.
Note that these methods, being abstract,
have no body defined here. Instead, they
end with a semicolon.
They could be defined to be abstract, but
that’s not necessary.
Since interfaces name capabilities, it’s
often a good idea to name them after
those abilities.
Lecture 13
Object-Oriented Languages and Systems
225
Any class that implements all the methods
can then declare that it implements the
interface.
class Automobile implements Drivable {
…
boolean startEngine() {
if (notTooCold) engineRunning = true;
…
}
void stopEngine() {
engineRunning = false;
}
etc.
}
Other classes, like lawnmower, could also
implement Drivable.
An interface can also serve as a type
specifier. You can write procedures that
take objects of the interface type as
parameters:
Drivable myVehicle;
But the main purpose of an interface is to
impose requirements on the class(es) that
want to implement it.
Example: The java.util.Enumeration
interface.
This interface can be used by any kind of set
to provide serial access to its elements.
An object that implements the Enumeration
interface must provide two methods,
• nextElement(), which returns an
Object type so it can be used with any
kind of collection, and
• hasMoreElements().
© 2000 Edward F. Gehringer
CSC 517/ECE 517 Lecture Notes, Fall 2000
226
Any kind of object can implement the
Enumeration interface and then use it to
provide access to its elements.
This means we can write general looping
code, to which we later plug in the
appropriate object types.
Enumeration e = …
while (e.hasMoreElement()) {
String s = (String) e.nextElement();
System.out.println(s);
}
Summary: • Java is a single-inheritance
language.
• Java provides linguistic mechanisms for
declaring abstract superclasses, and
enforcing the requirement that abstract
methods be implemented in subclasses.
• Control flow is similar to C and C++,
except for the fact that expressions are
guaranteed to be evaluated left-to-right.
• File streams are used for input and output.
Utility classes such as tokenizers and print
streams facilitate their use.
• The exception-handling mechanism of
Java relies upon “throwing” exceptions
from one place in the code to be dealt with
by an exception-handler elsewhere.
• Packages and multiple compilation units
make it possible to organize a large
program.
• Interfaces provide many of the benefits of
multiple inheritance, while avoiding most of
the costs.
Lecture 13
Object-Oriented Languages and Systems
227