Alexei B Khorev
March 2013
In this part we shall consider The Java language galore
Java’s I/O — Streams
StringTokenizer
Scanner
Exceptions
Assertions
Packages
Enumerations
Generic (parameterised) types
Reflections (time permits)
Two previously discussed ways to provide input to a program:
JOptionPane.showInputDialog dialog boxare very limited in what input can be provided, and how (un)flexibly it can be used. They are too primitive if we need:
input from a user during the run time
input from one or several files, or pipes, or network (socket), or database etc
to process the input data differently, depending on the type of input data
to filter input, or to format and filter the output
The Java APIs which allow you to do this are the so-called I/O packages:
java.io — the main I/O package (Big One! 78 interfaces, regular classes and exceptions)java.nio with sub-packages and java.net (will not be discussed)From Java Tutorial
An I/O Stream represents an input source or an output destination. A stream can represent many different kinds of sources and destinations, including disk files, devices, other programs, and memory arrays.
Streams support many different kinds of data, including simple bytes, primitive data types, localised characters, and objects. Some streams simply pass on data; others manipulate and transform the data in useful ways. No matter how they work internally, all streams present the same simple model to programs that use them. A stream is a sequence of data.
| Input | Output |
|---|---|
| A program uses an input stream to to read data from a source, one item at a time | A program uses an output stream to to write data to a destination, one item at a time |
![]() |
![]() |
java.io packageThe java.io defines I/O in terms of streams. Streams are ordered sequences of data that have a source (input streams) or destination (output streams). The I/O classes act as front ends to the specific details of OS, providing access to the system resources through files, peripheral devices (keyboard, display screen) and other means. Operations which can be carried out over the streams are provided by in interfaces and abstract classes. Concrete classes (eg Filters) may have additional methods.
Character Streams (16 bit, for text) and
Byte Streams (8 bit, for data, eg images)
Stream are objects of corresponding I/O classes. Depending on the processing task, methods of the stream class are called, or the object is used as a parameter for a constructor of another stream, eg, for filtering or buffering the original stream (see two examples, ByteCounter.java and UppercaseConvertor.java):
// wrapping std input as a character stream
InputStreamReader cin = new InputStreamReader(System.in);
java ByteCounter; // run demo
java UppercaseConvertor // run demo
Learning how to read() and write()
read()/write() (below we shall omit mentioning the byte stream classes).BufferedReader/BufferedWriter — adds the ability to buffer the input (eg, readLine() instead of a single character) and flush the output. A stream with additional capabilities (br) can be created (Decorator design pattern) from a more basic one (fr) as follows
FileReader fr = new FileReader(args[0]); // args[0] is a file name
BufferedReader br = new BufferedReader(fr);
//a shorter alternative
BufferedReader br1 = new BufferedReader(new FileReader("foo.in")); print() and printf() methods that make it easy to write the values of primitive types and objects to a stream, in a human-readable text format.Zip/Jar input/output streams (in packages java.util.zip and java.util.jar) — stream filters for reading/writing files in the ZIP/JAR file formats. Includes support for both compressed and uncompressed data (no need to use System to extract or archive, neat!).
|
printf() (in PrintWriter) — writes a formatted string to this writer using the specified format string and arguments; returns reference to this instance of PrintWriterskip(long n) (in BufferedReader) — skips n charactersreset() — resets the stream, eg, by repositioning it to its starting point; used alongside with mark()close() (in BufferedWriter) — flushes the buffer and closes the stream (when writing to a file, it is safer to close() and save the content)Problem: Process a formatted input — read an input text stream, organised line-by-line as a series of fields by breaking the lines and using each field accordingly.
The StringTokenizer class from
are read, parsed and then star objects created and added to a catalog. |
This is how the program may look (hint: relevant to one of Assignment 1 tasks):
ArrayList<Star> stars; // a star catalog
BufferedReader input = new BufferedReader(new FileReader(catalog.txt));
StringTokenizer tokens;
String starName;
int catalogNumber;
char starClass;
Coordinates coor;
double lum;
String line = input.readLine();
while (line != null) {
tokens = new StringTokenizer(line);
starName = tokens.nextToken();
catalogNumber = Integer.parseInt(tokens.nextToken());
starClass = tokens.nextToken().charAt(0);
coor = new Coordinates(tokens.nextToken());
lum = Double.parseDouble(tokens.nextTokens());
star = new Star(<all the collected values>);
stars.add(star);
line = input.readLine();
}
StringTokenizer is a legacy class that is kept for backward compatibility, but its use is discouraged in new code. A better way to exercise the same functionality by using the split() method of String.
// "\\s" is a regex for any number of WS characters
String[] result = "this is a test".split("\\s");
for (String str: result)
System.out.println(str + " has " + str.length());
output:
this has 4
is has 2
a has 1
test has 4
You are free to choose which method to use — StringTokenizer, or String.split(). But the trend is to weed out the former one. Apart from StringTokenizer, java.io package has StreamTokenizer, which can recognise identifiers, numbers, quoted strings, and various comment styles. It is specially designed to process the text code for languages like Java and C.
Scanner class combines the facilities of InputStreams, StringTokenizer and Regex classes to break down formatted input into tokens and translating individual tokens according to their data type. Scanner is not a stream (more like a Tokenizer), but it must be closed to indicate that it’s no more to be done with the underlying stream. The stream breaking is done in accordance with delimiter pattern (default is whitespaces, see Character.isWhitespace(char c)). The following example is taken directly from API documentation:
String input = "1 fish 2 fish red fish blue fish";
Scanner s = new Scanner(input).useDelimiter("\\s*fish\\s*");
System.out.print(s.nextInt() + " ");
System.out.print(s.nextInt() + " ");
System.out.print(s.next() + " ");
System.out.println(s.next());
s.close();
with the output (as you might expect)
1 2 red blue
Scanner allows to process input in accordance with specified Locale. The Scanner interface is quite big, but even the use of a small part of it can simplify your code substantially. Read API, Java Tutorial, the text books.
Doing the OS work from within a Java program
An application can interact with the underlying operation system. Often such interaction involves elements of the file system — files and directories. An application may require reading, writing and executing files, finding files, establishing and changing file properties, deleting files etc. As usual, Java must do this in the OS-neutral way, without using OS-specific commands and file-system properties. This type of problems are dealt with with help of File class and a set of global configuration values called properties (which include system properties).
A File object is virtual proxy for an underlying OS file; it allows to find out everything about a file and perform a number of operations on it:
getName()) and path (getPath())canRead(), canWrite(), canExecute())exists(), isDirectory(), isHidden() etc)length())createNewFile(), renameTo(), delete())list(), listFiles())and some others. As an example, this is how we can list a directory content for files which end with the chosen suffix (from examples DirectoryLister.java).
import java.io.*;
import java.util.Properties;
class DirectoryLister {
public static void main(String[] args) {
final String suffix = args.length < 1 ? "" : args[0];
//run-time directory name (different on different OS platforms)
String cwdName = System.getProperty("user.dir");
File cwd = new File(cwdName); // cwd is File object standing for directory
// (tricky) anonymous class instantiation
FilenameFilter ffilter = new FilenameFilter() {
public boolean accept(File f, String n) {
return (n.endsWith(suffix)) ? true : false;
}
};
if (cwd.isDirectory()) {
System.out.println("The directory contains:");
for (File file: cwd.listFiles(ffilter))
System.out.printf("%s: %d\n", file.getName(), file.length());
}
}
}
ls -l *.pattern | awk '{ print $9": " $5 }' # this is the Unix CL solution :)
For historical reasons, Java I/O is rather complicated and harder to use. Java 7 comes up with substantial improvements in dealing with I/O issues (so called NIO 2.0):
java.nio.file), most notably the Path classThe class File from java.io allows in principle to fully manage the file system — navigate the file hierarchy, establish file properties, delete and rename files, link and unlink and so on. But it is not easy to get right (esp. symbolic links) and has problem with synchronisation (the file tree could change in the course of program execution, which would cause problems with navigating it via the absolute path). Most of this problem have the root in how a file on the disk is represented in the program as an object of the class File.
Instead of java.io.File all the file system manipulations (more reliably) and many new ones, can be performed with the new java.nio.file.Path class. The key novelty here is that JVM binds a Path object to the actual physical file (programmer’s jargon for a file on the disk) at the run-time. Foundation classes of the java.nio.file package include:
Path — includes methods to obtain information about the path, to access elements of the path, to convert the path to other forms, or to extract portions of a path. There are also methods for matching the path string and others.Paths — utility class with methods to return a pathFileSystem — class that interfaces with the filesystemFileSystems — utility with methods to access local or remote filesystemsWatchService — utility class to detect file system changes through event notification; this allows event-driven programming style (which results in significant simplification)Files — utility class to create, rename, copy and delete files, changing their attributes etc (provides better support for atomic operations then java.io.File).All in all — java.nio.file is a great new API asset; it’ll pay to learn how to use it, but not in this course (not yet)!
Exceptions are natural in OO programming
Advanced flow control
| How to handle errors (including severe) which occur at run time? When a method is invoked on an object, but the is object in a wrong state, or data passed as parameters are wrong, or the method contract is otherwise violated — an erroneous event occurs which must be dealt with during the program execution. To test for all such possibilities (correctness) would be to hamper the code clarity. Exceptions is the way to achieve both correctness and clarity. | Method call stack |
Exception goes back |
catch clause in the client code, which may in turn throw another exception so that the exception will propagate (“sideways”)As everything in Java, exceptions are objects. Their classes extend either Exception or Error:
Checked exceptions (anticipated by the application writer). Methods deals with these exceptions explicitly by try-catch clause (eg, wrong file name supplied to open a FileReader stream). Methods declare such exceptions by throws part in their signature and appropriate implementation. Most common situation. Checked exceptions are subclasses of Exception on the non-RuntimeException branch (NoSuchMethodException, IOException etc.). Flagged by compiler.
Errors (irregular events from outside of the program) cannot be anticipated and recovered from (eg, reading from a stream is impossible due to network problem or smth). They can be caught, or allowed to terminate the program. No catch or throws for them. These are children of Error class.
Runtime Exceptions, which originate from within the application, but cannot be foreseen or recovered from. They are bugs (logic errors, misused resources — NumberFormatException, IndexOutOfBoundsException and the like). One can catch them, but better allow them to runtime crush the application to discover and fix them. catch or throws are not required. They are children of RuntimeException class.
try-catch)This is how an exception handler is written:
try {
.... do smth which can throw an exception type caught below ....
} catch (ExceptionOne e1) {
.... dealing with ExceptionOne ....
} catch (ExceptionTwo e2) {
.... dealing with ExceptionTow ....
} .... <possibly more catch statements> ....
finally { // this part is optional, include it to avoid resource leaks and
// to secure handling unchecked exceptions --- it's never bypassed
.... <executes always when try block exits> ....
} ...
// anything else is executed only if no exceptions are thrown in try
An example from Java Tutorial for the method writeList() (in the class ListOfNumbers and ListOfNumbers2), which demonstrates the use of the clauses in an exception handler + two examples, WithoutFinally.java and WithFinally.java.
The order in which the caught exceptions are listed is important: if their types overlap (ie, one is a subtype of another, IOException → FileNotFoundException), the narrower (here, second) type must precede the wider (first) to avoid skipping a specialised response.
throw)Exception objects are created when methods (or constructors) throw them:
modifiers return-type doSmth() throws ExceptionOne, ExceptionTwo, ... {
...........
throw new ExceptionOne(); // instance of ExceptionOne class or its superclass
...........
throw new ExceptionTwo(); // instance of ExceptionTwo class or its superclass
...........
}
The throw statement can occur either in a standard if-else construct (which may test for a specific state of an object), or inside the catch block (which will result in a chain of exceptions thrown up to the next higher level exception handler).
The exception classes listed after throws represent a part of the full method signature. Unchecked exceptions, if not caught in “catch”-requirement, are not flagged by compiler. If your code invokes a method which was defined with throws clause for a checked exception, it must take care of catching it (using try-catch construct), or the compiler will flag an error. The unchecked exceptions do not require catching, though it’s often prudent to do so (example ListOfNumbers.java).
A method may choose to handle an exception by throwing an exception of its own, in which case the method caller will have to deal with it, which, if it handles it, will create an exception chain.
This is an example of an exception chain which takes place during the execution of ExceptionTest.java. Here, exceptions throw their own exceptions (propagate sideways), some exceptions are uncaught are returned back to their caller (forwarded exceptions).
|
When an exception throwing method is overridden (in a subclass), exceptions which the superclass method has thrown can be “specialised”: ie, the throws clause of an overridden method can have fewer types listed than the superclass method, or more specific types, or both. It can even dispense with throwing exceptions altogether (no checked exceptions). Contrast this with “overriding” access modifiers: they can either extend method’s visibility, or leave it unchanged (protected or "package" → public, but not other way around).
Some programmers question (not unreasonably) the usefulness of separation checked and unchecked exceptions (see, for example, articles cited in Tim McCune’s “Exception-Handling Anti-patterns”; this paper doesn’t directly discuss this, but instead gives a good advice on when to catch an exception and when to ignore it, when and how best to define your own exception class, how exceptions are abused, and how to avoid such a practice).
As to the checked-vs-unchecked issue (eg, C# doesn’t have such distinction), my opinion is that it’s not the distinction itself (it is useful since catching certain exceptions must be enforced at compilation), but why some exceptions are defined as checked (NoSuchMethodException…), or unchecked (EnumConstantNotPresentException…). Some exceptions which should be often (always?) caught (NumberFormatException, IndexOutOfBoundsException, etc) are unchecked, because otherwise “they would get too much in a way” (I’m simply guessing what API’s people thought -:).
Exceptions are classes, and you can define your own exception to deal with a particular situation as you see fit. By defining a checked Exception class, every client which invokes a method or constructor which throws your exception, shall also try-catch it.
catch clauses from more specific first to more general last.@throws tag (required for determining which catch clauses to include). Documenting unchecked exceptions is also desirable since it describes the method preconditions.Advantages of exceptions — they allow simpler, more maintainable programs by
return valuesCorrect use of exceptions usually takes longer then other aspects of OO programming. When is it better to use exceptions instead of “ordinary” procedural approach?
Use them in a really exceptional situation, not for ordinary control flow (think first before including try/catch inside loops, yet see ScannerExample.java) — it worsens performance, bad for clarity. For API, it’s a balancing act — to make a method return a special value to mark atypical event or to throw an exception (Integer.parseInt() vs InputStream.read()) — depends on concurrent access to object, synchronisation, performance etc.
| End-of-input-stream is a regular event | End-of-input-stream treated as exception |
|
|
The flow of control in the first case is clear and simple. In the second case (which at first sight loops forever), even if you know about StreamEndException, the construction is confusing since the loop termination condition is moved outside.
Remark: when all values read from a stream are valid (non-exceptional), eg when reading from a stream of doubles, it’s a good idea to add an explicit eof() test method that should be called before reading next token.
Here some rules:
Unchecked run-time exceptions are for programming errors (precondition violations etc)
Checked exceptions for recoverable conditions; they provide enough functionality (accessor methods) to gain adequate information to recover
Errors are signs of resource deficiencies (memory etc), corruption of object state and other conditions which make continuation of a program execution impossible or dangerous. The rule of thumb: do not subclass Error.
Checked exceptions must be handled in one or many catch statements — therefore, declared them sparingly to avoid putting excessive burden on their user.
Unless an exceptional condition can be avoided through proper use of API, or API itself allows to deal with an exceptional situation, use of unchecked exceptions is more appropriate.
The rule of thumb: never quit (throw new AssertError(), or System.exit(1)) when catching a checked exception.
When a code can throw multiple exceptions, and one has to handle them all, the old Java way was to use multi-catch statements, like in the try-catch Slide 21. The catch statements must be properly ordered to allow exception-type specific handling (as discussed before). It can get cluttered (therefore, inefficient and error-prone).
When the exceptions can be sub-grouped, so each one within the same subgroup can be given a similar treatment, these sub-grouped exceptions can be handles by a single catch-statement. The following code (taken from Evans and Verburg’s book) discriminates between exceptions caused by input error (system unable to supply the resource) or data error (missing or bad file):
public Configuration getConfig(String fileName) {
Configuration cfg = null;
try {
String fileText = getFile(fileName);
cfg = verifyConfig(parseConfig(fileText));
} catch (FileNotFoundException|ParseException|ConfigurationException e) {
System.err.println("Config file '" + fileName +
"' is missing or malformed");
} catch (IOException iox) {
System.err.println("Error while processing file '" + fileName + "'");
}
return cfg;
}
Another JDK 7 extension involving handling exception is resource management. Program resources are memory, I/O streams — opened files, sockets (network connections), pipes (program-to-program connections) and others. Memory (allocated for objects) is managed automatically by the garbage collector (great feature of Java), but other resources must be cared for manually by the programmer. Often, to guarantee the release of a stream resource (file opened for reading or writing etc) is difficult if between opening (acquiring resource) and closing (relinquishing resource) an exception can disrupt the normal flow of control, so that the resource management remains unfinished. That’s where finally statement is very useful, but… it may include its own try-catch-finally because, for example, closing a stream (the method OutputStream.close() and similar) can throw an IOException of its own.
Moreover, a code with multiple external streams may include nested try-statements. In the example MultipleFinally.java, data is read from an InputStream (a URL connection) and written to a OutputStream connected to a File. Opening and closing each of the three can throw an exception:
InputStream can fail to open from the URL, to read from it, or to close properly.File corresponding to the OutputStream can fail to open, to write to it, or to close properly.According to a JRS to Project Coin (the JDK 7 name), 2/3 of uses of close() in Java API (prior to JDK 7) were buggy.
try(...)To clean up all this (mainly artificial) mess, Java 7 has introduced try-with-resources (TWR) syntax extension for the use of try. It allows to localise all opened streams (and acquired resources, in general) to the try-catch-finally block, such that when the block is exited (in whatever way — regular or exceptional) — the resources are relinquished (just like any local variable is destroyed and memory consumed by it released). The syntax:
try (OutputStream out = new FileOutputStream(file);
InputStream is = url.openStream()) {
byte[] buf = new byte[4096];
int len;
while ((len = is.read(buf)) > 0) {
out.write(buf, 0, len);
}
}
The resource acquisition is performed according to statements inside the parentheses and that’s it! No more care is necessary. (Subtlety: chaining the stream object creation via nested constructors isn’t recommended in TWRs, since some streams may not be closed due to their anonymity; better to assign every opened stream to a variable, like it’s done in the example).
TWR shamelessly borrowed the idea of automatic resource management through localisation inside a block from other languages. Python’s with-statement has almost identical semantics to TWR (apparently, C# does the same thing using using-clause, but I wouldn’t know -:).
The state of computational environment can be characterised by an invariant, which is a boolean value expression always evaluating to true. Assertions is the Java mechanism to check the invariant. It can be used at every step of computation as a test that everything goes “according to plan”. If the test fails (the invariant is false), the AssertError is thrown.
assert expr [:"Error message string"]; // the [...] (second expression) is optional
assert val > 0 && val < 20 : "the input value must be within the range";
Assertions are disabled by default. To enable them, run your application with -ea option
java -ea RootClass // enabling assertions across all classes
java -ea:package-name RootClass // enabling assertions in a package
java -ea:class-name RootClass // enabling assertions in a class
Assertions are turned on during development, and normally are turned off after the application is made available to customers (however, it’s advisable to keep them on since they greatly simplify the errors analysis).
A useful “hack” — to put an assertion in a place where the execution must never ever get into:
assert false; // the code will abort if this is reached!
Types in Java cab be defined in two ways: abstract classes (partial implementation) and interfaces (pure contract). The main purpose of an interface is to define behaviour. The type defined in an interface can be used to refer to instances of the class, which implement that interface. But, Java also allows to define an interface without any behaviour, typeless. Before Java 1.5, such interfaces were used for types with a finite number of instances:
public interface PhysicalConstants {
static final double PLANCK_CONSTANT = ...;
static final double ELECTRON_MASS = ...;
static final double ELECTRON_CHARGE = ...;
... ... ...
}
Bad practice: use of constants is an implementation detail which leaks into the implementing classes; its use by clients has nothing to do with the type behaviour, and is confusing. Worse, constant interface represents a commitment: future class modifications which do not use the constants still require implementation for binary compatibility (design becomes ugly and illogical).
One solution for constants problem is to put them into a utility class with no public constructors:
public final class PhysicalConstants {
private PhysicalConstants() { ... } //instantiation impossible
public static final double PLANCK_CONSTANT = ...;
.............
}
Better solution is achieved through the use of type safe enumeration pattern for class definition. For example, the type which holds four suits of cards can be declared as such class as follows:
public class Suit {
private final String name;
private Suit(String name) { this.name = name; }
public String toString() { return name; }
public static final Suit CLUBS = new Suit("clubs");
public static final Suit DIAMONDS = new Suit("diamonds");
public static final Suit HEARTS = new Suit("hearts");
public static final Suit SPADES = new Suit("spades");
}
private static final Suit[] PRIVATE_VALUES = { CLUBS, DIAMONDS, HEARTS, SPADES };
When defined like this, there is no way for clients to create objects of the class or to extend it, there will never be any objects of the type besides those exported via the public static final fields (type safety not available through the use of typeless interfaces). Even though the class is not declared final, there is no way to extend it: Subclass constructors must invoke a superclass constructor, and no such constructor is accessible.
This very elegant solution (due to Joshua Bloch) is the classic example when a successful solution to a recurring problem (this is what software designers call a design pattern), can be promoted and made a language feature in its own right (not every design pattern can be utilised like this). The feature which implements the type safe enumerations is the enum.
enums are the types, all instances of which are known when the type is defined:
enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES, } // named instances
This looks like a class declaration, and this is indeed a special kind of class disguised as a new language feature. In such primitive form, the usage of this definition of Suit is similar to the constant interface variant (without the negative implications). But enums can be used with much greater power (this is a class, after all). You can declare constructor and methods inside enum:
public enum Chess {
PAWN(1), BISHOP(10), KNIGHT(15), ROOK(20), QUEEN(100); // named constructors
Chess(int value) { this.value = value; } // implicit private constructor
private final int value; // constant value tied to enum instance
public int value() { return value; }
public String printValue() {return value + ((value > 1) ? " points" : " point");}
}
enum types guarantee type safety (they are uniquely defined types), and can be put into Collections (they are objects)enum values are used as switch keys (in JDK1.7, also String) — demo ChessTest.java.enums cannot extend another class (because they implicitly extend Enum abstract class)enums have a static values() method that returns an array of all the values (in the declaration order)enumerated chessThe Chess example (the source code: Chess.java and ChessTest.java):
public class ChessTest {
public static void main(String[] args) {
System.out.println("You don't play a chess game to count points, but:");
for (Chess c : Chess.values())
System.out.println(c + " is worth "
+ c.printValue() + ", and it's " + appearance(c));
}
private enum ChessStature { SHORT, MEDIUM, TALL } // this is inner enum type
private static ChessStature appearance(Chess c) {
switch(c) { // c can be only Number or enum
case PAWN: return ChessStature.SHORT;
case BISHOP:
case KNIGHT:
case ROOK: return ChessStature.MEDIUM;
case QUEEN: return ChessStature.TALL;
default: throw new AssertionError("Unknown figure: " + c);
}
}
}
Demo run to output. (Another example is Planets.java from Java Tutorial.)
This is the Java mechanism for finding and using types (classes, interfaces, enumes and annotations), for avoiding name conflicts, and for controlling the access to class members. Projects have a package structure which defines how related classes are bundled together. The Java APIs are organised as follows: fundamental classes are in java.lang, classes for reading and writing (input/output) are in java.io, useful (utility) classes are in java.util, (some) GUI classes are in javax.swing etc. JavaSE comes with more than 200 packages, and there are numerous specialised packages from third parties.
A class is defined as a package member via the package declaration in the beginning of every class (before the class declaration):
package comp6700.labs.lab1; //in every class you wrote in the lab 1
package comp6700.ass.ass1; //in all assignment 1 classes
Remember that the type/member visibility increases in this gradation:
private → <default> → protected → public
To make a type available to the program, use import declaration at the beginning of your source code (before the class declaration):
import comp6700.ass.assOne;//makes all classes from assOne available
// wildcard (*) does not mean *all*, compiler only imports those which are used
import javax.swing.*;
// (since Java 1.5) one can also import static members from a class
import static packagename.Classname.staticMemberName;
Why to package?
To create the byte code for your application which has the correct mapping of the package structure to the file structure of compiled class files, use the -d option with javac command (DrJava’s casus). To make classes available during the compiling or execution, use -classpath option, or set CLASSPATH environment variable.
javac -d . comp6700/ass/ass1/MainClass.java
This will compile the ass1 classes to their package structure.
From the java man page (run man java):
For example, if you specify "-d /home/myclasses" and the class is
called com.mypackage.MyClass, then the class file is called
/home/myclasses/com/mypackage/MyClass.class. If -d is not specified,
javac puts the class file in the same directory as the source file.
Generics is the language technique for treating a computational problem which involves one or several class types without specifying the actual classes involved. A class or a method, which involves manipulation of other classes or instances, can be defined in a way which only requires the validity of operations used for those classes while the classes themselves should be defined only at the time of the class instantiation or method invocation.
Prior to the Java 1.5 version, the only generic type was Array, “[ ]”, where the type of its elements could regarded as the parameter-type: E[] "=" []E. With all the quirkiness of such type definition, the structure of this type was very specific. Few other types (Vector, Collection, List, Hashtable) also had rudimentary parameterisation, which was achieved by treating every element of these structures as Object. By this, there was no mechanism to ensure that a data structure contained type-“homogeneous” elements. Even if all your elements in a Vector object were, say, Strings, when you extracted them, the explicit cast was required, since the return type of the extracted element was Object:
String str = (String) v.elementAt(i); // v is an instance of Vector class;
This was awkward (code clutter, annoyance), this was error prone (the programmer might have inserted an incompatible type into v etc). The generics introduce two major benefits:
Since Java 1.5 you can define parameterised types, or generic classes — classes which involves other classes as parameters, not actual types. The actual type values are used during instantiation (similar to method’s invocation with actual parameters).
class A<T> { // more than one type parameter can be used, class A<E,K>
private T field;
public A (T param) {
this.field = param;
}
public T getField() {
return this.field;
}
}
Instances of a generic class are defined with the type parameter given a value — an existing type:
A<String> aS = new A<String>("I get it!");
A<Integer> aI = new A<Integer>(100); // "100" means A<Integer>(100) --- autoboxing!
Type parameters are not types themselves! There is no T.java class defined anywhere in the API. This is why T (or any other symbol used) is not a part of the class A name (compiler removes generic information from the class definition — so called type erasure — and A.java → A.class). Examples: Cell.java, ClassTest.java.
Naming guidelines: type parameter names are single, uppercase letters, which is quite different from the variable naming convention — the difference between type variable and an ordinary class or interface name should be very clear. The most commonly used type parameter names:
E — Element (in the collection type DS)
K — Key (in Hashtable and the like)
V — Value (in Hashtable and the like, Hashtable<K,V>)
N — Number (of Number type)
T,S,U,V — Type (anything, really)
Syntax enhancement in JDK 7
Instantiating a generic container is now easier. Instead of
Map<String, List<Trade>> trades = new TreeMap<String, List<Trade>> ();
use the diamond operator <>:
Map<String, List<Trade>> trades = new TreeMap <> ();
and compiler will select the right type values from the left side.
Type parameters can also be used within method and constructor signatures to create generic methods and generic constructors. Similar to declaring a generic type, but the type parameter’s scope is limited to the method or constructor in which it’s declared.
public class BoxG<T> { //"borrowed" from Java Tutorial
private T t;
public void add(T t) { this.t = t; }
public T get() { return t; }
public <U> void inspect(U u){ // a method with a parameterised parameter type
System.out.println("T: " + t.getClass().getName());
System.out.println("U: " + u.getClass().getName());
}
public static void main(String[] args) {
BoxG<Integer> integerBox = new BoxG<Integer>();
integerBox.add(new Integer(10));
integerBox.inspect("some text");
}
}
The output (BoxG.java):
T: java.lang.Integer
U: java.lang.String
A more interesting use of generic static methods:
public static <U> void fillBoxes(U u, ArrayList<Box<U>> boxes) {
for (Box<U> box : boxes) {
box.add(u);
} // assuming that this is a part of a non-generic class Box definition
}
The method usage may go like this:
Fruit pineapple = new Fruit("pineapple", 3.0); // creating a 3kg heavy pineapple
ArrayList<Box<Fruit>> fruitBoxes = new ArrayList<Box<Fruit>>();
Box.<Fruit>fillBoxes(pineapple, fruitBoxes);
Compiler can infer the type value of the parameter itself, so the explicit declaration isn’t often necessary:
Box.fillBoxes(pineapple, fruitBoxes); // compiler infers that U is Fruit.
(this is called type inference, it allows to invoke a generic method as if it was an ordinary one.)
A generic class definition may involve manipulations which do not make sense for every class, eg, addition of two instance of the parameter class. In such case, one would like to put a constraint on the class parameter values used in instantiation. In the case of the mentioned addition operation, which makes sense for Number types (subclasses of Number) and few others (like String). Such constraint is declared via bounded type parameters. The syntax (using the Box example above — demo in BoxBT.java):
public <U extends Number> void inspect(U u){ ... } // U is (sub-)type of Number
class CMP<T extends Number & Comparable<T>> { ... } // T is also comparable
After such bounding, the compilation will fail because the invocation integerBox.inspect("some text") is now illegal (the value of the type parameter U here is String which is not Number).
The generic class with bounded type parameters are also defined (which is more often used than bounded type methods).
Sub-typing of parameterised types: Integer is a subtype on Number, but Box<Integer> is not a subtype of Box<Number> (see diagram on the next slide). The method doSmth(Box<Number> n) will not accept Box<Integer> type actual parameter. The right way to declare such methods is to use wildcards ? (demo in BoxWC.java):
public void doSmth(Box<? extends Number> box) {... // upper bound wildcard
public void doSmth(Box<? super Integer> box) {... // lower bound wildcard
A generic method to sum the elements of a generic List
The wildcard “?” indicates that the method |
A bounded wildcard with a lower bound List<? super Integer> matches any super-type of List<Integer> (including itself):
List<Integer>List<Number>List<Serializable>List<Comparable<Integer>>List<Object>Back in 1995… “The Feel of Java”
Java is a blue collar language. It’s not PhD thesis material but a language for a job. Java feels very familiar to many different programmers because we preferred tried-and-tested things (James Gosling “Feel of Java”, Talk at OOPSLA 1996).
After Generics became part of the language
“I am completely and totally humbled. Laid low. I realise now that I am simply not smart at all. I made the mistake of thinking that I could understand generics. I simply cannot. I just can’t. This is really depressing. It is the first time that I’ve ever not been able to understand something related to computers, in any domain, anywhere, period.”
“I’m the lead architect here, have a PhD in physics, and have been working daily in Java for 10 years and know it pretty well. The other guy is a very senior enterprise developer (wrote an email system that sends 600 million emails/year with almost no maintenance). If we can’t get [generics], it’s highly unlikely that the ‘average’ developer will ever in our lifetimes be able to figure this stuff out.” (both citations by Josh Bloch at JavaPolis 2007.)
If you want to become a Java’s generics expert, read Angelika Langer’s 427-page (!) Java Generics FAQ (also there is a book “Java Generics and Collections” by Maurice Naftalin and Philip Wadler, O’Reilly 2006). But first ask yourself: “Can I go to C++ or Scala instead?”
Cay Hortsmann (interview Java Champion, Feb 2008)
When students first see the API with thousands of classes, they despair. I used to be able to tell them, “That’s OK, at least the language itself is very simple.” But that was before this:
static <T extends Object & Comparable<? super T>> T
Collections.max(Collection<? extends T> coll);
As a student, you need to stay within a safe subset of the Java language and the API so that you can use it as a tool to learn some good computer science.
Joshua Bloch (talk at JavaPolis2007): “We simply cannot afford another wildcards”.
“Typical” declarations and compiler errors and warnings involving generics:
Enum<E extends Enum<E>> { ... };
<T extends Object & Comparable<? super T>>
T Collections.max(Collection<? extends T> col) ;
public <V extends Wrapper<? extends Comparable<T>>>
Comparator<V> comparator() { ... };
error: equalTo(Box<capture of ?>) in Box<capture of ?> cannot
be applied to (Box<capture of ?>)
equal = unknownBox.equalTo(unknownBox)
Arrays.asList(String.class, Integer.class) // Warning!
Java’s doclets, annotation processing tools, the JUnit testing framework, class browsers (in IDEs) and many other Java related technologies — they all rely on the “ability of a running program to examine itself and its software environment, and to change what it does depending on what it finds” (I. & N. Formans, Java Reflections in Action).
This is a simplest example of this kind, when the program understands its own name. Without being overridden, the printName() method behaves differently for each subclass than it does for HelloWorld. The printName() method is flexible; it adapts to the class that inherits it, causing the change in behaviour.
|
What’s happening?
|
“To perform this self-examination, a program needs to have a representation of itself. This information is called metadata. In an object-oriented world, metadata are organized into objects, called metaobjects. The runtime self-examination of the metaobjects is called introspection.” The very style of writing programs which can understand and define their semantic structure is called metaprogarmming.
Metaobjects are instances of metaclasses, dwellers of yet higher (“Platonic”) world: Class, Array, Field, Method, Constructor, Modifierand others are metaclasses — the object-oriented elements of the Java metamodel. Java programs get their meaning through the meaning of metaobjects, and the metaobject semantics is provided through metaclasses. Things quickly get heady:
All your objects are instances of regular classes defined by you or elsewhere.
Those classes (and all metaclasses except Class) are instances of the metaclass Class, but they all inherit (direct or not) from the class Object.
The class Class inherits from the class Object, but Object is the Class’s instance.
The class Class is an instance of itself. (Fancy that!)
Using metaobjects, one can change the program structure and behaviour: Method metaobjects can be commanded to invoke the methods they represent (dynamic invocation), Field and Constructor objects can be used to reflectively access and reset the program state variable, and create new ones. In principle, the reflections allow you to achieve the effect impossible via standard means, eg to access and change a value of private fileds: It is not possible to read and change the value of an object private field without getter-setter methods. Or is it?
HelloWorld x = new HelloWorld("magic_word", 42);
x.printMembers();
...
name = magic_word
number = 42
...
Field[] fields = x.getClass().getDeclaredFields();
// some code to establish that f1 is a private String and it's called "name"
Field f1 = fields[0];
f1.setAccessible(true);
f1.set(x,"mutabor");
x.printMembers();
...
name = mutabor
number = 42
...
Complete code is in TestHelloWorld.java and HelloWorld.java.
Let x is a name (reference) of an object defined elsewhere (for example sent alongside with a client request) we can find out a lot about x during run-time and use this information to choose what to do as we go.
Class<?> cl = x.getClass();
Constructor<?>[] constructors = cl.getDeclaredConstructors();
Field[] fields = cl.getDeclaredFields();
Method[] methods = cl.getDeclaredMethods();
// we can invoke a constructor to create a new (identical) object to x
xClone = constructors[0].newInstance(Object... initargs);
// which constructor to use from the constructors array cab be
// meticulously computed as well as what parameters initargs to
// use for recreating the current field values of the object x
It’s all quite tricky and even messy (at first sight), but we can recreate an exact copy (“clone”) of the object x in its current state even if x is an instance of a class which does not support cloning (ie, does not implement CloneNotSupportedException interface). The code and its discussion can be found in an article by Vladimir Roubtsov, in JavaWorld, January 2003.
Reflections drawbacks:
10x slower)final fields, inner classes (the latter due to complexity reason, not in principle)Reflections warts:
getClass() method on any object (it’s defined in the Object class) to establish the class of an object at run-time. How does this compare with the use of instanceof operator? Are the two methods equivalent, and can one use either of them, eg in the equals() method? (Explore the examples A.java and B.java to draw the conclusion when not to use getClass() as a test of the object type. Again, ponder the difference between the type and the class of an object.)Every class, interface and primitive type are class objects (instances of the class Class) which can be obtained by reading their class type literals .class
boolean.class; // primitive type is a (Class) instance
Boolean.class; // different from the previous one!
java.lang.String.class // same instance is also called String.class
java.util.Iterator.class // types are too Class instances