You are on page 1of 52

UNIT 4

GENERIC PROGRAMMING
Topics
• background and goals of generic programming
• basics of generic classes = parameterized types
• generic methods for general algorithms
• inheritance rules for generic types
• bounded type parameters
• generic code and the Java Virtual Machine
• restrictions and limitations
• wildcard types and wildcard type capture
Why generic programming

Background
• old version 1.4 Java collections were Object-based and
required the use of ugly casts
– cannot specify the exact type of elements
– must cast to specific classes when accessing

Java generics
• lets you write code that is safer and easier to read
• is especially useful for general data structures, such as
ArrayList
• generic programming = programming with classes and
methods parameterized with types
Why generic programming (cont.)

• generic types are a powerful tool to write reusable object-


oriented components and libraries
• however, the generic language features are not easy to
master and can be misused
– their full understanding requires the knowledge of the type
theory of programming languages
• especially covariant and contravariant typing

• the following introduces the main aspects of Java generics


and their use and limitations
• we mostly inspect illustrative samples of what is and what
is not allowed, with some short glimpses inside the JVM
implementation
Why generic programming (cont.)

Java generics
• in principle, supports statically-typed data structures
– early detection of type violations
• cannot insert a string into ArrayList <Number>
– also, hides automatically generated casts
• superficially resembles C++ templates
– C++ templates are factories for ordinary classes and
functions
• a new class is always instantiated for given distinct generic
parameters (type or other)
• in Java, generic types are factories for compile-time
entities related to types and methods
Definition of a simple generic class

class Pair <T> {


public T first;
public T second;
public Pair (T f, T s) { first = f; second = s; }
public Pair () { first = null; second = null; }
}
• you instantiate the generic class by substituting
actual types for type variables, as: Pair <String>
• you can think the result as a class with a constructor
public Pair (String f, String s), etc . .
• you can then use the instantiated generic class as it
were a normal class (almost):
Pair <String> pair = new Pair <String> ("1","2");
Multiple type parameters allowed

• you can have multiple type parameters


class Pair <T, U> {
public T first;
public U second;
public Pair (T x, U y) { first = x; second = y; }
public Pair () { first = null; second = null; }
}

• to instantiate: Pair <String, Number>


Generic static algorithms
• you can define generic methods both inside
ordinary classes and inside generic classes
class Algorithms { // some utility class
public static <T> T getMiddle (T [ ] a) {
return a [ a.length / 2 ];
}
...

}
• when calling a generic method, you can specify type
String s = Algorithms.<String>getMiddle (names);
• but in most cases, the compiler infers the type:
String s = Algorithms. getMiddle (names);
Inheritance rules for generic types
Comments on inheritance relations

• Pair<Manager> matches Pair<? extends Employee> =>


subtype relation (covariant typing)
• Pair<Object> matches Pair<? super Employee>
=> subtype relation (contravariant typing)
• Pair<Employee> can contain only Employees, but
Pair<Object> may be assigned anything (Numbers)
=> no subtype relation
• also: Pair<T> <= Pair<?> <= Pair (raw)
List <String> sl = new LinkedList <String> ();
List x = sl;                     // OK
x.add (new Integer (5));  // type safety warning
..
String str = sl.get (0);   // throws ClassCast.
Bounds for type variables

• consider the min algorithm: find the smallest item


in a given array of elements
• to compile this, must restrict T to implement the
Comparable interface that provides compareTo
public static <T extends Comparable>
T min (T [ ] a) { // this is almost correct
if (a.length == 0) throw new InvalidArg.. (..);
T smallest = a [0];
for (int i = 1; i < a.length; i++)
if (smallest.compareTo (a [i]) > 0) // T constraint
smallest = a [i];
return smallest;
}
Bounds for type variables (cont.)

• however, Comparable is itself a generic interface


• moreover, any supertype of T may have extended it
public static <T extends Object & // bounding class
Comparable <? super T>>
T min (T [ ] a) { . . . // the more general form
T smallest = a [0];
for (int i = 1; i < a.length; i++)
if (smallest.compareTo (a [i]) > 0) // T constraint
smallest = a [i];
return smallest;
}
• cannot inherit multiple different instantiations of the
same generic type (class or interface)
• an inherited generic type is fixed for subtypes, too
Generic code and the JVM
• the JVM has no instantiations of generic types
• a generic type definition is compiled once only, and
a corresponding raw type is produced
– the name of the raw type is the same name but type
variables removed
• type variables are erased and replaced by their
bounding types (or Object if no bounds); e.g.:
class Pair { // the raw type for Pair <T>
public Object first;
public Object second;
public Pair (Object f, Object s) { . . }
}
• byte code has some generic info, but objects don't
Generic code and the JVM (cont.)

• Pair <String> and Pair <Employee> use the same


bytecode generated as the raw class Pair
• when translating generic expressions, such as
Pair <Employee> buddies = new Pair < . .;
Employee buddy = buddies.first;
• the compiler uses the raw class and automatically
inserts a cast from Object to Employee:
Employee buddy = (Employee)buddies.first;

– in C++, no such casts are required since class instantiations


already use specific types
• if multiple constraints (Object & Comparable. .) then
the type parameter is replaced by the first one
Overriding of methods of generic type

• consider a generic class with a non-final method:


class Pair <T> { // parameter T is erased from code
public void setSecond (T s) { second = s; } . .
• to override such type-erased methods, the compiler
must generate extra bridge methods:
class DateInterval extends Pair <Date> {
public void setSecond (Date high) { // override
if (high.compareTo (first) < 0) throw new . .
second = high; // otherwise OK
}
public void setSecond (Object s) { // bridge method
setSecond ((Date)s); // generated by compiler
}..
Restrictions and limitations
• in Java, generic types are compile-time entities
– in C++, instantiations of a class template are compiled
separately as source code, and tailored code is
produced for each one

• primitive type parameters (Pair <int>) not allowed


– in C++, both classes and primitive types allowed

• objects in JVM have non-generic classes:


Pair<String> strPair = new Pair<String> . .;
Pair<Number> numPair = new Pair<Number> . .;
b = strPair.getClass () == numPair.getClass ();
assert b == true; // both of the raw class Pair
– but byte-code has reflective info about generics
Restrictions and limitations (cont.)
• instantiations of generic parameter T are not allowed
new T () // ERROR: whatever T to produce?
new T [10]
• arrays of parameterized types are not allowed
new Pair <String> [10]; // ERROR
– since type erasure removes type information needed for
checks of array assignments
• static fields and static methods with type parameters
are not allowed
class Singleton <T> {
private static T singleOne; // ERROR
– since after type erasure, one class and one shared static
field for all instantiations and their objects
Wildcard types
• note that the raw class Pair is not equal Pair <?>
Pair pair1 = . .;
pair1.first = new Double (10.0); // WARNING
Pair <?> pair2 = . .;
pair2.first = new Double (10.0); // ERROR

• but some operations have no type constraints:


public static boolean hasNulls (Pair <?> p) {
return p.first == null || p.second == null;
}
• alternatively, you could provide a generic method
public static <T> boolean hasNulls (Pair <T> p)
• generally, prefer wildcard types (but use generic
method with type T when multiple parameters)
Wildcard capture

• the wildcard type ? cannot be used as a declared


type of any variables (as in the previous slide)
Pair <?> p = new Pair <String> ("one", "two"); . .
p.first = p.second; // ERROR: unknown type
• but, can sometimes use a generic method to
capture the wildcard:
public static <T> void rotate (Pair <T> p) {
T temp = p.first; p.first = p.second;
p.second = temp;
}
• the compile checks that such a capture is legal
– e.g., the context ensures that T is unambiguous
Collections and algorithms

• goal: design a minimal interface that you need


• e.g., for max, implement to take any Collection
public static <T extends Object &
Comparable <? super T>>
T max (Collection <? extends T> c) {
// a hypothetical implementation:
Iterator <T> it = c.iterator ();
T largest = it.next (); // or throws NoSuchElement
while (it.hasNext ()) {
T val = it.next ();
if (largest.compareTo (val) < 0) largest = val;
}
return largest;
}
Exceptions and Assertions
Syntax Errors
• Syntax errors arise because the rules of the
language have not been followed. They are
detected by the compiler
Runtime Errors
• Runtime errors occur while the program is
running if the environment detects an
operation that is impossible to carry out.
Logic Errors
• Logic errors occur when a program doesn't
perform the way it was intended to.
Exception,error and Runtime Exception
Classes
ClassNotFoundException

IOException
ArithmeticException
Exception AWTException
NullPointerException
RuntimeException
IndexOutOfBoundsException
Object Throwable Several more classes
IllegalArgumentException

LinkageError Several more classes

VirtualMachineError
Error
AWTError

Several more classes


Checked Exceptions vs. Unchecked
Exceptions
• RuntimeException, Error and their subclasses
are known as unchecked exceptions. All other
exceptions are known as checked exceptions,
meaning that the compiler forces the
programmer to check and deal with the
exceptions.
Declaring, Throwing, and Catching Exceptions

method1() { declare exception


method2() throws Exception {
try {
invoke method2; if (an error occurs) {
}
catch exception catch (Exception ex) { throw new Exception(); throw exception
Process exception; }
} }
}
Declaring Exceptions
Every method must state the types of checked
exceptions it might throw. This is known as
declaring exceptions.

public void myMethod()


throws IOException

public void myMethod()


throws IOException, OtherException
Throwing Exceptions
When the program detects an error, the
program can create an instance of an
appropriate exception type and throw it. This is
known as throwing an exception. Here is an
example,

throw new TheException();

TheException ex = new TheException();


throw ex;
Throwing Exceptions Example
/** Set a new radius */
public void setRadius(double newRadius)
throws IllegalArgumentException {
if (newRadius >= 0)
radius = newRadius;
else
throw new IllegalArgumentException(
"Radius cannot be negative");
}
Catching Exceptions
try {
statements; // Statements that may throw exceptions
}
catch (Exception1 exVar1) {
handler for exception1;
}
catch (Exception2 exVar2) {
handler for exception2;
}
...
catch (ExceptionN exVar3) {
handler for exceptionN;
}
Rethrowing Exceptions
try {
statements;
}
catch(TheException ex) {
perform operations before exits;
throw ex;
}
The finally Clause
try {
statements;
}
catch(TheException ex) {
handling ex;
}
finally {
finalStatements;
}
Trace a Program Execution
Suppose no exceptions in
the statements

try {
statements;
}
catch(TheException ex) {
handling ex;
}
finally {
finalStatements;
}

Next statement;
Trace a Program Execution
The final block is always
try { executed
statements;
}
catch(TheException ex) {
handling ex;
}
finally {
finalStatements;
}

Next statement;
Trace a Program Execution
Next statement in the
try { method is executed
statements;
}
catch(TheException ex) {
handling ex;
}
finally {
finalStatements;
}

Next statement;
Trace a Program Execution
try { Suppose an exception of
statement1; type Exception1 is thrown in
statement2
statement2;
statement3;
}
catch(Exception1 ex) {
handling ex;
}
finally {
finalStatements;
}

Next statement;
Trace a Program Execution
try { The exception is handled.
statement1;
statement2;
statement3;
}
catch(Exception1 ex) {
handling ex;
}
finally {
finalStatements;
}

Next statement;
Trace a Program Execution
try { The final block is always
statement1; executed.
statement2;
statement3;
}
catch(Exception1 ex) {
handling ex;
}
finally {
finalStatements;
}

Next statement;
Trace a Program Execution
try { The next statement in the
statement1; method is now executed.
statement2;
statement3;
}
catch(Exception1 ex) {
handling ex;
}
finally {
finalStatements;
}

Next statement;
Trace a Program Execution
try {
statement1; statement2 throws an
statement2; exception of type
Exception2.
statement3;
}
catch(Exception1 ex) {
handling ex;
}
catch(Exception2 ex) {
handling ex;
throw ex;
}
finally {
finalStatements;
}

Next statement;
Trace a Program Execution
try {
statement1; Handling exception
statement2;
statement3;
}
catch(Exception1 ex) {
handling ex;
}
catch(Exception2 ex) {
handling ex;
throw ex;
}
finally {
finalStatements;
}

Next statement;
Trace a Program Execution
try {
statement1; Execute the final block
statement2;
statement3;
}
catch(Exception1 ex) {
handling ex;
}
catch(Exception2 ex) {
handling ex;
throw ex;
}
finally {
finalStatements;
}

Next statement;
Trace a Program Execution
try {
statement1; Rethrow the exception and
statement2; control is transferred to the
statement3; caller
}
catch(Exception1 ex) {
handling ex;
}
catch(Exception2 ex) {
handling ex;
throw ex;
}
finally {
finalStatements;
}

Next statement;
Creating Custom Exception Classes
• Use the exception classes in the API whenever
possible.
• Create custom exception classes if the
predefined classes are not sufficient.
• Declare custom exception classes by
extending Exception or a subclass of
Exception.
Assertions
• An assertion is a Java statement that enables
you to assert an assumption about your
program. An assertion contains a Boolean
expression that should be true during program
execution. Assertions can be used to assure
program correctness and avoid logic errors.
Declaring Assertions
An assertion is declared using the new Java
keyword assert in JDK 1.4 as follows:

assert assertion; or
assert assertion : detailMessage;

where assertion is a Boolean expression and


detailMessage is a primitive-type or an Object
value.
Executing Assertions
When an assertion statement is executed, Java evaluates the
assertion. If it is false, an AssertionError will be thrown. The
AssertionError class has a no-arg constructor and seven
overloaded single-argument constructors of type int, long,
float, double, boolean, char, and Object.

For the first assert statement with no detail message, the no-
arg constructor of AssertionError is used. For the second assert
statement with a detail message, an appropriate
AssertionError constructor is used to match the data type of
the message. Since AssertionError is a subclass of Error, when
an assertion becomes false, the program displays a message on
the console and exits.
Executing Assertions Example
public class AssertionDemo {
public static void main(String[] args) {
int i; int sum = 0;
for (i = 0; i < 10; i++) {
sum += i;
}
assert i == 10;
assert sum > 10 && sum < 5 * 10 : "sum
is " + sum;
}
}
Compiling Programs with Assertions
Since assert is a new Java keyword introduced in
JDK 1.4, you have to compile the program using
a JDK 1.4 compiler. Furthermore, you need to
include the switch –source 1.4 in the compiler
command as follows:

javac –source 1.4 AssertionDemo.java

NOTE: If you use JDK 1.5, there is no need to use


the –source 1.4 option in the command.
Running Programs with Assertions
By default, the assertions are disabled at runtime. To
enable it, use the switch –enableassertions, or –ea
for short, as follows:

java –ea AssertionDemo

Assertions can be selectively enabled or disabled at


class level or package level. The disable switch is –
disableassertions or –da for short. For example, the
following command enables assertions in package
package1 and disables assertions in class Class1.
java –ea:package1 –da:Class1 AssertionDemo
Using Exception Handling or Assertions
• Assertion should not be used to replace exception
handling. Exception handling deals with unusual
circumstances during program execution.
Assertions are to assure the correctness of the
program. Exception handling addresses
robustness and assertion addresses correctness.
Like exception handling, assertions are not used
for normal tests, but for internal consistency and
validity checks. Assertions are checked at runtime
and can be turned on or off at startup time.

You might also like