Files and Intro. to Inheritance

advertisement
Session 5
More on Java Strings and Files
& Intro. to Inheritance
Java File I/O
• Allows us to write and read “permanent”
information to and from disk
• How would file I/O help improve the
capabilities of the MemoPadApp?
Java File I/O Example: Echo.java
• echoes all the words in one file to an output
file, one per line.
$ java Echo hamlet.txt hamlet.out
$ less hamlet.out
1604
the
tragedy
of
hamlet
prince
of
denmark
by
william
shakespeare ...
Study Echo.java’s File I/O
• have constructors that allow convenient and
flexible processing
• send input message: readLine()
• send output messages: print() and println()
• use a stereotypical loop to process a file of
lines
• use of the stereotypical StringTokenizer
loop as inner loop
import java.io.*;
import java.util.StringTokenizer;
public class Echo {
public static void main( String[] args ) throws IOException {
String delimiters = " .?!()[]{}|?/&\\,;:-\'\"\t\n\r";
BufferedReader inputFile = new BufferedReader(new FileReader(args[0]) );
PrintWriter
outputFile = new PrintWriter( new FileWriter( args[1] ) );
String buffer = null;
while( true ) {
buffer = inputFile.readLine();
if ( buffer == null ) break;
buffer = buffer.toLowerCase();
StringTokenizer tokens = new StringTokenizer( buffer, delimiters );
while( tokens.hasMoreElements() ) {
String word = tokens.nextToken();
outputFile.println( word );
} // end while
} // end while(true)...
} // end main
} // end class Echo
wc - UNIX/Linux utility
• wc prints the number of lines, words, and
characters in a file to standard output.
• For example:
$ wc hamlet.txt
4792
31957 196505 hamlet.txt
Exercise
• Using Echo.java as your starting point, create a
WordCount.java program that does the same
thing as wc, i.e., prints the number of lines,
words, and characters in a file to standard output.
For example:
$ java WordCount hamlet.txt
lines = 4792 words = 32889 chars = 130156
import java.io.*;
import java.util.StringTokenizer;
public class WordCount {
public static void main( String[] args ) throws IOException {
String delimiters = " .?!()[]{}|?/&\\,;:-\'\"\t\n\r";
BufferedReader inputFile = new BufferedReader( new FileReader( args[0] ) );
String
int
int
int
buffer
chars
words
lines
=
=
=
=
null;
0;
0;
0;
while( true ) {
buffer = inputFile.readLine();
if ( buffer == null ) break;
lines++;
buffer = buffer.toLowerCase();
StringTokenizer tokens = new StringTokenizer( buffer, delimiters );
while( tokens.hasMoreElements() ) {
String word = tokens.nextToken();
words++;
chars += word.length();
} // end while
} // end while( true )...
System.out.println( "" + lines + " " + words + " " + chars );
} // end main
} // end class WordCount
Why the difference in the number
of words and number of characters?
$ wc hamlet.txt
4792
31957
196505 hamlet.txt
$ java WordCount hamlet.txt
lines=4792 words=32889 chars=130156
Java File I/O Example: Echo.java
• echoes all the words in one file to an output
file, one per line.
$ java Echo hamlet.txt hamlet.out
$ less hamlet.out
1604
the
tragedy
of
hamlet
prince
of
denmark
by
william
shakespeare ...
import java.io.*;
import java.util.StringTokenizer;
public class Echo {
public static void main( String[] args ) throws IOException {
String delimiters = " .?!()[]{}|?/&\\,;:-\'\"\t\n\r";
BufferedReader inputFile = new BufferedReader(new FileReader(args[0]) );
PrintWriter
outputFile = new PrintWriter( new FileWriter( args[1] ) );
String buffer = null;
while( true ) {
buffer = inputFile.readLine();
if ( buffer == null ) break;
buffer = buffer.toLowerCase();
StringTokenizer tokens = new StringTokenizer( buffer, delimiters );
while( tokens.hasMoreElements() ) {
String word = tokens.nextToken();
outputFile.println( word );
} // end while
} // end while(true)...
} // end main
} // end class Echo
Working with Standard Input and
Output as Files
• Sometimes, we'd like to give the user an
option of providing a file name or using
standard I/O.
• We can call sort with its own file
argument, or we can pipe the standard
output of one program (cat hamlet.out) as
the standard input to sort.
• How can we make our Java programs do the
same thing?
Streams vs. Readers and Writers
• a stream is a device for transmitting or
receiving a sequence of byte (8-bit) values
– emphasis on reading/writing -- not on data itself
– network and file systems are based on byte unit
• Readers and Writers use 16-bit Unicode
– useful for I/O of textual values as opposed to
binary data such as images, colors, etc.
– for example, BufferedRead has readLine method
Working with Standard Input and
Output as Files
• Standard input is an instance of the
InputStream class and does not respond to
readLine(), which is how we would like to
grab lines of text as Strings.
• Standard output does respond to println()
messages, but it is a PrintStream, which
cannot be stored in a PrintWriter variable.
What can we do?
• We could write duplicate code for the four
different cases. (file-file, file-stdout, stdin-file,
stdin-stdout)
• Every case would look the same except for one
or two lines.
• That doesn't seem to be the correct solution.
• Maybe we can find a way to have them talk to
objects that talk to standard input and output...
A Solution
• Let's take advantage of an object-oriented idea: We ought to
be able to substitute an object with a common interface,
even if somewhat different behavior, in place of one
another, and let the new object fulfill the responsibilities of
the replaced one.
• While BufferedReaders and PrintWriters don't know how to
talk to standard input and output, respectively, we can use a
translator to serve as a go-between.
• Java give us the classes we need: InputStreamReader and
OutputStreamWriter.
import java.io.*;
import java.util.StringTokenizer;
public class EchoStandard {
public static void main( String[] args ) throws IOException {
String delimiters = " .?!()[]{}|?/&\\,;:-\'\"\t\n\r";
BufferedReader inputFile = new BufferedReader(
new InputStreamReader( System.in ) );
PrintWriter outputFile = new PrintWriter(
new OutputStreamWriter( System.out ) );
String buffer = null;
while( true ) {
buffer = inputFile.readLine();
if ( buffer == null ) break;
buffer = buffer.toLowerCase();
StringTokenizer tokens = new StringTokenizer(buffer,delimiters);
while( tokens.hasMoreElements() ) {
String word = tokens.nextToken();
outputFile.println( word );
} // end while
} // end while( true )...
} // end main
} // end class EchoStandard
Echo
BufferedReader inputFile = new BufferedReader(
new FileReader( args[0]) );
PrintWriter
outputFile = new PrintWriter(
new FileWriter( args[1]) );
vs. EchoStandard
BufferedReader inputFile = new BufferedReader(
new InputStreamReader( System.in ) );
PrintWriter outputFile = new PrintWriter(
new OutputStreamWriter( System.out ) );
Exercise
•
Turn Echo.java into EchoV2.java, which behaves just like Echo,
except that it takes two optional command-line arguments: the names
of the input file and output file, respectively.
• If the user omits the second argument, the program writes to standard
output.
• If the user omits both arguments, the program reads from standard
output and writes to standard output. For example:
$ java EchoV2 hamlet.txt hamlet.out
$ less hamlet.out
1604
the
tragedy
of
...
Exercise - More Examples
$ java EchoV2 EchoV2.java
...
$ java EchoV2 hamlet.txt | less
1604
the
tragedy
of
...
(interesting that the pipe “|” is not args[1])
$ java EchoV2
...
$ cat hamlet.txt | java EchoV2 | less
1604
the
tragedy
of
...
Introduction to Inheritance
Accumulator Example
• a simple calculator app
• classes needed:
– AdderApp - contains main
– AddingFrame - GUI
– CloseableFrame - allows X
button
– Accumulator - internal
representation and
implementation of the
accumulator
AdderApp
• contains the main() method that serves as
the "Big Bang" for this part of the world
public class AdderApp {
public static void main( String[] args )
{
AddingFrame f = new AddingFrame();
f.show();
} // end main
} // end class AdderApp
AddingFrame
• Provides the graphical interaction between the
user and the actual calculator methods
• AddingFrame extends CloseableFrame extends
JFrame.
• AddingFrame depends on the Accumulator class
do the mathematics for the program.
Accumulator Class
• Recall from CS I that a class contains three
things.
–
–
–
Data / Instance Variables
Method(s)
Constructor(s)
Accumulator Class
• What Data / Instance Variables are needed?
Accumulator Class
• Data / Instance Variables needed:
– currentSum – the current value “accumulated”
by the accumulator.
– currentNumber – the number that has been
entered by the user. The value that will be
added or subtracted.
– displayValue – the value visible on the
graphical calculator. (Needed because sometimes
we display the number the user is entering
(currentNumber) and sometimes it is the current
accumulated value (currentSum), so we will maintain a
new value which holds whatever is on display.)
Accumulator Class
• What methods would the accumulator class
need (hint, there are five of them)?
Accumulator Class
• Needed methods:
– plus – adds the last number entered to the currentSum.
– minus – subtracts the last number entered from the
currentSum.
– clear – sets everything back to zero
– addDigit – adjusts the currentNumber upon input of an
additional integer
– getDisplay – returns the current displayValue. (This is
necessary because our graphical class will want to
know what value to display any time some action
occurs.)
Accumulator Class
• What would the constructor do?
AccumulatorV1
public class AccumulatorV1 {
private int currentSum;
private int currentNumber;
private int displayNumber;
public Accumulator() {
currentSum=0;
currentNumber=0;
displayNumber=0;
}
public void clear() {
currentSum=0;
currentNumber=0;
displayNumber=0;
}
public void addDigit( int digit ) {
currentNumber=currentNumber*10+digit;
displayNumber=currentNumber;
}
public void plus() {
currentSum+=currentNumber;
currentNumber=0;
displayNumber=currentSum;
}
public void minus() {
currentSum-=currentNumber;
currentNumber=0;
displayNumber=currentSum;
}
public int getDisplay() {
return displayNumber;
}
} // end class AccumulatorV1
Refactoring Accumulator
• What is refactoring?
– Changing a program in a way that does not
change its functionality.
• Why do it?
– To improve the structure of your code based on
what you have learned since writing it.
• What common code can we refactor?
Refactoring Accumulator
• Using the clear() method in the constructor
• Refactoring the plus() and minus() methods to call
a private helper method.
Refactoring Accumulator
public class AccumulatorV2 {
private int currentSum;
private int currentNumber;
private int displayNumber;
public Accumulator() {
clear();
}
public void clear() {
currentSum=0;
currentNumber=0;
displayNumber=0;
}
public void addDigit( int digit ) {
currentNumber=currentNumber*10+digit;
displayNumber=currentNumber;
}
public void plus() {
currentSum+=currentNumber;
prepareForNextNumber();
}
public void minus() {
currentSum-=currentNumber;
prepareForNextNumber();
}
private void prepareForNextNumber() {
currentNumber=0;
displayNumber=currentSum;
}
public int getDisplay() {
return displayNumber;
}
} // end class AccumulatorV2
Reinforcing the refactoring
• There is an old programmers adage that
states
"There are only two numbers: 1 and
many"
• Once you start to repeat code, it is time to
start to think about refactoring and adding
in a helper method.
Alternative structure of the program
• The complete “calculator” consists of four
classes.
–
–
–
–
AdderApp
AddingFrame
CloseableFrame
Accumulator
Alternative structure of the program
• We can think of the relationships between
these four classes as being “narrow and
deep”
– AdderApp creates an instance of AddingFrame
which creates an instance of Accumulator.
– This is a good example of data hiding since
AdderApp doesn’t know/care that there is an
instance of the Accumulator class.
public class AdderApp {
public static void main( String[] args ) {
AddingFrame f = new AddingFrame();
f.show();
} // end main
} // end class AdderApp
public class AddingFrame extends CloseableFrame {
private Accumulator myAccumulator;
...
public AddingFrame( ) {
// create frame and accumulator
myAccumulator = new Accumulator();
...
Alternative structure of the program
• But another way to structure this program would be to
create a relationship which is “wide and shallow”
– AdderApp creates an an instance of Accumulator which it
passes to an instance of AddingFrame.
public class AdderApp
public static void
Accumulator a =
AddingFrame f =
f.show();
{
main( String[] args ) {
new Accumulator();
new AddingFrame(a);
} // end main
} // end class AdderApp
– This is a good example of composition.
• We emphasize that AddingFrame is composed of an Accumulator
– This is a good example of writing code that is modular.
• Now that we know the composition relation, we can compose
solutions using variations of Accumulator.
CountedAccumulator Extension
• Suppose we need a new kind of object, an
Accumulator that counts how many operations it
executes. Let’s call this class
CountedAccumulator.
• It responds to all the same messages as a regular
Accumulator and also responds to an
operationsExecuted() message, by returning its
count.
• What changes would you need to make to
Accumulator?
Adding Behavior to a Class
• Any time that we need to add behavior to a
class we have at least three options:
– Add code to the class itself, keeping the
original class.
– Copy all the old code into a new class and add
code to this new class.
– Create a subclass that extends the original
class' behavior.
Pros and Cons
“Add code to the class itself, keeping the
original class. “
– Pros: Quick. Convenient. Simple.
– Cons: May change the behavior of the class.
Thus, it isn’t always an option.
Pros and Cons
“Add code to the class itself, keeping the
original class. “
– Pros: Quick. Convenient. Simple.
– Cons: May change the behavior of the class.
Thus, it isn’t always an option.
Pros and Cons
“Add code to the class itself, keeping the
original class. “
– Pros: Quick. Convenient. Simple.
– Cons: May change the behavior of the class.
Thus, it isn’t always an option.
Pros and Cons
“Copy all the old code into a new class and
add code to this new class. “
– Pros: Quick. Convenient. Simple.
– Cons: Duplicated code. Error trap! Error trap!
Pros and Cons
“Copy all the old code into a new class and
add code to this new class. “
– Pros: Quick. Convenient. Simple.
– Cons: Duplicated code. Error trap! Error trap!
Pros and Cons
“Copy all the old code into a new class and
add code to this new class. “
– Pros: Quick. Convenient. Simple.
– Cons: Duplicated code. Error trap! Error trap!
Pros and Cons
“Create a subclass that extends the original
class' behavior.“
– Pros: Doesn’t break existing code. Virtually
eliminates duplicate code. Provides the most
flexibility.
– Cons: Slightly more time consuming.
Pros and Cons
“Create a subclass that extends the original
class' behavior.“
– Pros: Doesn’t break existing code. Virtually
eliminates duplicate code. Provides the most
flexibility.
– Cons: Slightly more time consuming.
Pros and Cons
“Create a subclass that extends the original
class' behavior.“
– Pros: Doesn’t break existing code. Virtually
eliminates duplicate code. Provides the most
flexibility.
– Cons: Slightly more time consuming.
Developing an Extended Class
• There are typically four steps in developing
an extended class.
–
–
–
–
declare the class
declare the new data
create the constructors
adjust the methods
Developing an Extended Class
• declare the class
public class CountedAccumulator
extends Accumulator {
Developing an Extended Class
• declare the new data
private int numberOfOperations;
Developing an Extended Class
• create the constructor
public CountedAccumulator () {
super();
numberOfOperations=0;
}
Developing an Extended Class
• Leave inherited methods alone
– clear() and prepareForNextNumber() are both
inherited from Accumulator and there is no
need to change them.
Developing an Extended Class
• Modify/Override inherited methods
– plus() and minus() are inherited, but they don't do what
we want them to.
– We can make them do more without completely
replacing the code however.
public void plus() {
super.plus();
numberOfOperations++;
}
Developing an Extended Class
• Add completely new methods
– We need an accessor method for numberOfOperations
public void operationsExecuted() {
return numberOfOperations;
}
CountedAccumulator Solution
public class CountedAccumulator extends Accumulator {
private int numberOfOperations;
public CountedAccumulator() {
super();
// calls the superclass’ constructor
numberOfOperations=0;
}
public void plus() {
super.plus();
numberOfOperations++;
}
public void minus() {
super.minus();
numberOfOperations++;
}
public int getOperations() {
return numberOfOperations;
}
} // end class CountedAccumulator
CountedAccumulator Solution
• Now, before we can really work with this
we need to modify other files in our
application.
• We need to set up the AddingFrame so that
it works with a CountedAccumulator rather
than a regular Accumulator. We do this in
the AdderApp class for simplicity.
Accumulator a = new CountedAccumulator();
AddingFrame f = new AddingFrame(a);
A solution
• Why do we do this in the AdderApp rather than
leave it alone and modify the AddingFrame?
– Because in the end this makes our AddingFrame
slightly more versatile.
– Think about it...AddingFrame works with an
Accumulator (or CountedAccumulator). If one is
provided, it uses it. If one is not provided, it creates it.
– THAT, is more versatile than telling an AddingFrame to
now always create a CountedAccumulator.
A solution
• Now we can run this...
– Notice that we have basically returned to
having a Accumulator. Why?
– Notice that even though I have private data and
methods in Accumulator, I didn't have to
change this here. Why?
A solution that USES the counting
functionality
• If we want to actually use the functionality of this new
class, then something needs to call the new method in
CountedAccumulator.
• Without discussing the details of exception handling, we
could do this by writing:
try {
Thread.sleep(10000);
} catch(Exception e) {
}
System.out.println("Performed“
+a.getOperations()+"operations");
Another Exercise
Create a class named EvenOddAccumulator that
subclasses Accumulator to implement this
behavior.
EvenOddAccumulators respond to all the same
messages as regular Accumulators. But, in
response to plus() and minus() messages, an
EvenOddAccumulator both computes the new
sum and writes a congratulatory message if the
sum is even.
Toward a Solution
Here is the critical new piece of the
EvenOddAccumulator class:
if ( currentSum % 2 == 0 ) {
System.out.println( "Hurray! You made
an even number." );
}
The big question is, what else is a part of the class?
Toward a Solution
• Let’s look at one version of this…
A Problem Accessing Inherited Data
$ javac EvenOddAccumulator.java
EvenOddAccumulator.java:17: currentSum
has private access in Accumulator
if ( currentSum % 2 == 0 )
^
EvenOddAccumulator.java:24: currentSum
has private access in Accumulator
if ( currentSum % 2 == 0 )
^
2 errors
Oops!
currentSum is declared as a private instance variable in class Accumulator.
private means private: no code outside the Accumulator class can access
that variable.
A Possible Solution for Accessing
Inherited Data
•
Change currentSum to be public or
protected.
public class Accumulator {
protected int currentSum;
...
}
A Better Solution
for Accessing Inherited Data
(2) Add a protected “accessor” method to the
Accumulator class. Use that method to access the
currentSum instance variable in the subclass.
public class Accumulator {
...
protected int currentSum() {
return currentSum;
}
}
Then use currentSum() in EvenOddAccumulator.
Programming with Inheritance
Inheritance is an object-oriented programming
construct that enables us to add behavior to
an existing system without modifying the
existing classes.
Programming with Inheritance
Our new EvenOddAccumulator class adds behavior to a program
that uses Accumulators without modifying:
• the behavior of the existing Accumulator class or
• the existing AddingFrame class!
That means...
• No chance of introducing an unnecessary, unexpected errors
into the working Accumulator class.
• No need to modify programs that use instances of
Accumulator but which don’t need instances of
EvenOddAccumulator.
• The ability to use EvenOddAccumulators in programs that
expect to use Accumulators.
Programming with Inheritance
We could have achieved some of these results without using
inheritance by creating a new class named
EvenOddAccumulator that simply duplicated the behavior
of existing Accumulator class.
Using inheritance means that...
• No need to reimplement existing methods.
• No need to duplicate code.
One of the most important features of object-oriented
programming is that it encourages us to create new classes
that reuse existing code as much as possible. Without
inheritance, you have only one tool for doing that,
composition. With inheritance, you have two tools.
Download