This issue presents tips, techniques, and sample code for the following topics:
Filter Streams
If you've read much about the Java programming language, you may have
encountered filter streams. A stream refers to an ordered sequence of
bytes or other data, with a source (for input streams) or a destination
(for output streams). Streams are used for file I/O, networking, pipes,
and so forth.
A filter stream is two or more streams hooked together to provide added
functionality. For example, one stream may provide a method to read in a
large block of characters efficiently, while another stream built on the
first one could offer a mechanism for buffering those characters so they
can be read one at a time in an efficient way. Another example of
filtering would be support for tracking line numbers.
To see how all this works, consider the following program that searches a
text file for a pattern, and displays each line containing the pattern
along with its line number:
import java.io.*;
public class filter {
public static void main(String args[])
{
if (args.length != 2) {
System.err.println("usage: pattern file");
System.exit(1);
}
String patt = args[0];
String file = args[1];
try {
FileReader fr =
new FileReader(file);
LineNumberReader lr =
new LineNumberReader(fr);
String str;
while ((str = lr.readLine()) != null) {
if (str.indexOf(patt) != -1) {
int ln = lr.getLineNumber();
System.out.println(file + "["
+ ln + "]: " + str);
}
}
lr.close();
}
catch (IOException e) {
System.err.println(e);
}
}
}
The example uses the LineNumberReader class in preference to the older
LineNumberInputStream that has been deprecated.
LineNumberReader is a buffered input reader that tracks line numbers as it
goes. In other words, successive large chunks of the file are read into a
buffer using FileReader. FileReader is a class that sets up a
FileInputStream based on an actual file, and then uses the mechanisms of
its superclass InputStreamReader to convert a stream of bytes into a
sequence of Java language characters. readLine is then called to read each
line in turn from the buffer. When a line terminator is detected, an
internal line number counter is incremented, and its current value is
retrieved using getLineNumber.
Note that you can also design your own filter streams. One example is to
extend java.io.BufferedReader so that readLine skips blank lines and
returns only the lines that contain non-whitespace characters.
Default Constructors
Suppose that you have a superclass and a subclass as follows:
class A {
A() {/* ... */}
}
class B extends A {/* ... */}
and you create a new instance of B by saying:
B bref = new B();
B defines no constructors, but B is extended from A, and A does have a
constructor that needs to be invoked for the instance of B. How does this
work?
What happens is that a default constructor is generated automatically, and
the constructor calls the superclass constructor with no arguments. So
"new B()" results in the generated default constructor for B
being called, and it in turn calls the no-argument constructor for A.
The generated constructor is given the access modifier "public"
if the class is public, or else the constructor is given the default access
implied by no modifier.
You can actually observe a default constructor in generated code. For
example, for the following source code:
class A {}
the result of "javap -c" (see Tech Tips:
Jan 20, 1998), is:
Method A()
0 aload_0
1 invokespecial #3 <Method java.lang.Object()>
4 return
In other words, a constructor for A is generated, and it simply invokes the
superclass (in this case java.lang.Object) constructor.
Relying on default constructor generation is not necessarily good coding
practice. In such a case it might be worth inserting an explanatory
comment. Note also that you can control class instantiation by means of
protected or private constructors. For example, a class used only as a
packaging vehicle for class methods and variables might define a private
constructor, making it uninstantiable.
|