public class StrBox

Download Report

Transcript public class StrBox

CS3240 - Generics
L. Grewe
1
What is Generics?



Data Structures that contain data (such as lists)
are not defined to operate over a specific type of
data; instead, they operate over a homogeneous
set, where the set type is defined at declaration.
Helps with Reusability (no longer need multiple
data structures to hold different types of data)
Helps control run-time errors if you instead could
have data structures containing different
(heterogeneous) kinds of data.
2
Concept in other languages
e.g. C++
In C++, would write generic stack
class using templates

template <type t> class Stack {
private: t data; Stack<t> * next;
public: void push (t* x) { … }
t* pop (
){…}
};
3
Java Generic Programming



Java has class Object
• Supertype of all object types
• This allows “subtype polymorphism”
 Can apply operation on class T to any subclass S <: T
Java 1.0 – 1.4 did not have generics
• No parametric polymorphism
• Many considered this the biggest deficiency of Java
Java type system does not let you “cheat”
• Can cast from supertype to subtype
• Cast is checked at run time
4
Run-Time Error without Generics

Example from sun.java.com: In the first example (lines 13 to 20), you might believe you're working with a list
of Integer objects, when in reality it's a list of Strings. In the second example (lines 11 and 22 to 27), you
might think you're working with a homogenous set of String, but this is a heterogeneous set of both String and
Integer elements. So unless you create a new list subclass for every element type (which would undermine the
advantages of OO reuse), there's no way to statically constrain the list to a set of homogeneous elements. And
in this simple example, the errors are fairly easy to catch. In a bigger program, you'd have even bigger
problems.
1.
2. List stringList = new LinkedList();
3. List integerList = new LinkedList();
4.
5. integerList.add(new Integer(1));
6. integerList.add(new Integer(2));
7.
8. stringList.add(new String("I am a String"));
9.
10. // Nothing constrains the elements to a homogeneous set.
11. stringList.add(new Integer(1));
12.
13. Iterator listIterator = integerList.iterator();
14.
15. // Compiler unaware of the list's return type and the illegal cast.
16. // Developer meant to iterate through the string list.
17. while(listIterator.hasNext()) {
18.
19. // Illegal cast caught at runtime.
20. String item = (String)listIterator.next();
21. }
22.
23. listIterator = stringList.iterator();
24. // No guarantee of homogeneous containers.
25. while (listIterator.hasNext()) {
26. // fail at runtime due to heterogeneous set
27. String item = (String)listIterator.next();
28. }
29.
5
Can generics help this problem

With generics, you achieve
polymorphic behavior similar to the
previous code, but with strong static
type-checking; the compiler knows
that the two lists are different
because they contain different
elements, and these lists are
guaranteed to contain only a
homogeneous set of elements.
6
Previous Example with Generics


As you can see from comments in the code, all the errors are caught at compile
time. Don't worry about the syntax for now -- we'll cover that shortly.
In comparing the two examples, you should notice that additional type information
is included in the generics code, which directs the compiler as to what type each
container should contain.
1.
2. import java.util.LinkedList;
3. import java.util.Collections;
4. import java.util.Iterator;
5.
6. public class genericsExample2{
7.
8. static public void main(String[] args) {
9.
LinkedList<String> stringList = new LinkedList<String>();
10. LinkedList<Integer> integerList = new LinkedList<Integer>();
11.
12. integerList.add(new Integer(1));
13. integerList.add(new Integer(2));
14.
15. stringList.add(new String("I am a String"));
16. stringList.add(new Integer(1)); // causes a compilation error
17.
18.
7
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
/* genericsExample2.java:16: cannot resolve symbol
** symbol : method add (java.lang.Integer)
*/
Iterator<Integer> listIterator = integerList.iterator();
String item;
while(listIterator.hasNext()) {
item = listIterator.next(); // causes a compilation error
}
/* genericsExample2.java:25: incompatible types
** found : java.lang.Integer
** required: java.lang.String
*/
listIterator = stringList.iterator(); // causes a compilation error
/* genericsExample2.java:33: incompatible types
** found : java.util.Iterator<java.lang.String>
** required: java.util.Iterator<java.lang.Integer>
*/
39. // the iterator is guaranteed to be homogeneous
40. while (listIterator.hasNext()) {
41.
item = listIterator.next();
42.
43.
/* genericsEx2.java:41: incompatible types
44.
** found : java.lang.Integer
45.
** required: java.lang.String
46.
*/
47. }
48. } // main
49. } // class genericsExample2
8
ANOTHER Example of Problem without
Generics: Cast Exceptions at Runtime
public class OldBox {
Object data;
public OldBox(Object data) {
this.data = data;
}
public Object getData() {
return data;
}
}
OldBox intBox = new OldBox(42);
int x = (Integer) intBox.getData();
OldBox strBox = new OldBox(“Hi”);
String s = (String) strBox.getData();
int y = (Integer) strBox.getData();
intBox = strBox;
ClassCastException!
Compiles but fails at runtime
9
Without Generics - NOT A GOOD SOLUTION
public class IntBox {
Integer data;
public IntBox(Integer data) {
this.data = data;
}
public Integer getData() {
return data;
}
}
public class StrBox {
String data;
public StrBox(String data) {
this.data = data;
}
public String getData() {
return data;
}
}
public class FooBox {
Foo data;
public FooBox(Foo data) {
StrBox strBox = new StrBox(“Hi”);
this.data = data;
String s = strBox.getData();
}
public Foo getData() {
int y = (Integer) strBox.getData();
return data;
intBox = strBox;
}
Errors caught by compiler
}
IntBox intBox = new IntBox(42);
int x = intBox.getData();
Infinite many classes possible
10
Java Generics: Key Idea

Parameterize type definitions
• Parameterized classes and methods

Provide type safety
• Compiler performs type checking
• Prevent runtime cast errors
11
Generics: Parameterized Classes
USING Generics
public class OldBox {
Object data;
public OldBox(Object data) {
this.data = data;
}
public Object getData() {
return data;
}
}
public class Box<E> {
E data;
public Box(E data) {
this.data = data;
}
public E getData() {
return data;
}
}
• We want the box to hold a “specific”
class – abstractly represented
• Object does not work as we have
seen earlier
• Solution – parameterize the class
definition
• E refers to a particular type
• The constructor takes an object of
type E, not any object
• To use this class, E must be
replaced with a specific class
12
How to Use Parameterized Classes
public class Box<E> {
E data;
public Box(E data) {
this.data = data;
}
public E getData() {
return data;
}
}
Box<Integer> intBox =
new Box<Integer>(42);
int x = intBox.getData();//no cast needed
Box<String> strBox =
new Box<String>(“Hi”);
String s = strBox.getData();//no cast needed
Following lines will not compile anymore:
String s = (String) intBox.getData();
int y = (Integer) strBox.getData();
intBox = strBox;
Runtime errors now converted to
compile time errors
13
When to Use Parameterized
Classes

Particularly useful for “container”
classes
• Containers hold but do not process data

All collections framework classes
in Java 5.0 and on defined using
generics
• See the Java API documentation
14

Collections now use Generic (see
java.util)
Generic interface
interface Collection<A> {
public void add (A x);
public Iterator<A> iterator ();
}

interface Iterator<E> {
E next();
boolean hasNext();
}
Generic class implementing Collection
interface
class LinkedList<A> implements
Collection<A> {
protected class Node {
A elt;
Node next = null;
Node (A elt) { this.elt = elt; }
15
More Collections Code from java.util

}
public interface List<E> {
void add(E x);
Iterator <E> iterator();
public interface Iterator<E> {
E next();
boolean hasNext();
}
LinkedList is in java.util and implements List interface as
a Generic Class.
HOW TO USE THIS CODE
List<Integer> myIntList = new LinkedList<Integer>();
myIntList.add(new Integer(0));
Integer x = myIntList.iterator().next();
16
Parameterized Classes: Syntax
Note
A class can have multiple parameters, e.g:
public class Stuff<A,B,C> { … }
Subclassing parameterized classes allowed, e.g:
/* Extending a particular type */
class IntBox extends Box<Integer> { … }
Or
/* Extending a parameterized type */
class SpecialBox<E> extends Box<E> { … }
SpecialBox<String> is a subclass of Box<String>.
/* Following assignment is legal */
Box<String> sb = new SpecialBox<String>(“Hi”);
17
Parameterized Classes in Methods
A parameterized class is a type just like any other class.
It can be used in method input types and return types, e.g:
Box<String> aMethod(int i, Box<Integer> b) { … }
If a class is parameterized, that type parameter can be used for
any type declaration in that class, e.g:
public class Box<E> {
E data;
public Box(E data) {
this.data = data;
}
public E getData() {
return data;
}
public void copyFrom(Box<E> b) {
this.data = b.getData();
}
}//We have added an infinite number of types of Boxes
//by writing a single class definition
18
So Far…

Type safety violations
• Using casts


Parameterized classes solve this problem
Provide type safety
• Enforced by the compiler



Particularly useful for container classes
A parameterized class is another type
Next – bounded parameterized classes
19
Bounded Parameterized Types
Sometimes we want restricted parameterization of classes.
We want a box, called MathBox that holds only Number objects.
We can’t use Box<E> because E could be anything.
We want E to be a subclass of Number.
public class MathBox<E extends Number> extends Box<Number> {
public MathBox(E data) {
super(data);
}
public double sqrt() {
return Math.sqrt(getData().doubleValue());
}
}
20
Bounded Parameterized Types (Contd.)
public class MathBox<E extends Number> extends Box<Number> {
public MathBox(E data) {
super(data);
}
public double sqrt() {
return Math.sqrt(getData().doubleValue());
}
}
The <E extends Number> syntax means that the type parameter of
MathBox must be a subclass of the Number class.
We say that the type parameter is bounded.
new MathBox<Integer>(5);//Legal
new MathBox<Double>(32.1);//Legal
new MathBox<String>(“No good!”);//Illegal
21
Bounded Parameterized Types (Contd.)
Inside a parameterized class, the type parameter serves as a valid type.
So the following is valid.
public class OuterClass<T> {
private class InnerClass<E extends T> {
…
}
…
}
Syntax note: The <A extends B> syntax is valid even if B is an interface.
22
Bounded Parameterized Types (Contd.)
Java allows multiple inheritance in the form of implementing multiple
interfaces. So multiple bounds may be necessary to specify a type
parameter. The following syntax is used then:
<T extends A & B & C & …>
For instance:
interface A {
…
}
interface B {
…
}
class MultiBounds<T extends A & B> {
…
}
23
Parameterized Methods
public class Bar<T> {
//Bar is parameterized
public T aMethod(T x) {
return x;
}
public static void
main(String[] args) {
Bar<Integer> bar =
new Bar<Integer>();
int k = bar.aMethod(5);
String s = bar.aMethod("abc");
//Compilation error here
}
}
Once Bar<T> object is fixed, we are locked to a
specific T.
24
Use of Parameterized Methods

Adding type safety to methods that
operate on different types
• Return type dependent on input type
25
Upper Bounded Wildcards in Parameterized Types
The following is a PROBLME:
Box<Number> numBox = new Box<Integer>(31);
Compiler comes back with an “Incompatible Type” error message.
This is because numBox can hold only a Number object and nothing else,
not even an object of type Integer which is a subclass of Number.
SOLUTION
The type of numBox we desire is “a Box of any type which extends
Number”.
Box<? extends Number> numBox = new Box<Integer>(31);
26
Upper Bounded Wildcards in Parameterized Types
(Contd.)
public class Box<E> {
public void copyFrom(Box<E> b) {
this.data = b.getData();
}
}
//We have seen this earlier
//We can rewrite copyFrom() so that it can take a box
//that contains data that is a subclass of E and
//store it to a Box<E> object
public class Box<E> {
public void copyFrom(Box<? extends E> b) {
this.data = b.getData();//b.getData() is a
//subclass of this.data
}
}
<? extends E> is called “upper bounded wildcard” because it
defines a type that is bounded by the superclass E.
27
Lower Bounded Wildcards in Parameterized Types
Suppose we want to write copyTo() that copies data in the opposite
direction of copyFrom().
copyTo() copies data from the host object to the given object.
This can be done as:
public void copyTo(Box<E> b) {
b.data = this.getData();
}
Above code is fine as long as b and the host are boxes of exactly same type.
But b could be a box of an object that is a superclass of E.
This can be expressed as:
public void copyTo(Box<? super E> b) {
b.data = this.getData();
//b.data() is a superclass of this.data()
}
<? super E> is called a “lower bounded wildcard” because it defines a type
that is bounded by the subclass E.
28
Unbounded Wildcards
Use unbounded wildcards when any type parameter works.
<?> is used to specify unbounded wildcards.
The following are legal statements.
Box<?> b1 = new Box<Integer>(31);
Box<?> b2 = new Box<String>(“Hi”);
b1 = b2;
Wildcard capture:
The compiler can figure out exactly what type b1 is above from the right
hand side of the assignments.
This “capturing” of type information means:
1. The type on the left hand doesn’t need to be specified.
2. The compiler can do additional type checks because it knows the type
of b1.
29
Conclusion

Java generics
• Parameterized classes and methods
• Type safety
• Syntax and semantics through examples
30