CS 201: Introduction to Programming With Java

CS203 Lecture 3
John Hurley
Cal State LA
Some data structures are suitable for handling objects of many different types.
• Every ArrayList is a list of objects of some type, but the types might be
Strings, Rectangles, or any other objects.
Yet, Java is a strongly-typed language, one in which references can only be
passed if they are of the same type the receiver expects.
Compare this Java code to the JavaScript on the next slide:
public static void main(String[] args) {
String myName = "Earl";
public static void printOut(String s) {
<script lang = "JavaScript">
function printOut(v){
document.write(v + "<br />");
var myName = "Earl";
var myAge = 42;
Generic classes and data structures are designed with the capability to
incorporate any of a range of other types. They are parameterized by the type
that is "plugged in". This is somewhat similar to the concept of an array as a
collection of values of the same data type. Unlike arrays, though, generic
data types can only use classes or interfaces as their underlying type
• You can create an ArrayList<Integer>, using the class Integer, but not an
ArrayList <int> using the primitive data type int.
It is possible to create Lists in Java which are not parameterized and can
accept values of any type. This is left over from before generics were
introduced to Java. Don’t do this unless you find some very good reason.
The generic type is defined with a placeholder that is essentially a
variable representing the parameterizing type, for example
• ArrayList<E>
We can and should declare variables with the least-specific type possible.
For example, we can declare a variable of type List<E> and then
instantiate an ArrayList<E> and assign this object to the variable.
This way, if we decide later to use some other kind of List, it is very easy
to change the code. We can even write code in which the type of list
depends on things that happen at runtime.
This only works if we can limit our use of methods to those that are in
the List interface
Similarly, we can choose the most general "plugged in" type available
and actually add objects of subclasses or implementing classes to the
generic data structure
The rules for which types can be used in a parameterized data structure are
similar to the rules for variable types:
If the data structure calls for a type, you can add objects of that type or its
subtypes, but not of its supertypes
• A subtype may be either a subclass, a subinterface, or a class that
implements an interface
This is easy to understand using some examples:
• Suppose Monster is a class with two subclasses, Zombie and Demon, which do not
have any subclasses of their own
• An ArrayList<Monster> can hold any combination of Monsters, Zombies, and
• An ArrayList<Zombie> can only hold Zombies
• Suppose TVShow is an interface, and the classes GameShow and SitCom implement
it and do not have any subclasses
• An ArrayList<TVShow> can hold any combination of GameShows and
• An ArrayList<GameShow> can only hold GameShows
Generic Type
Although you could achieve similar results by just using the type Object instead
of using generics, generics provide stricter compile-time type checking. This
replaces runtime errors with syntax errors, which are much easier to deal with.
package java.lang;
package java.lang;
public interface Comparable {
public int compareTo(Object o)
public interface Comparable<T> {
public int compareTo(T o)
(a) Prior to JDK 1.5
Runtime error
Comparable c = new Date();
(a) Prior to JDK 1.5
Improves reliability
(b) JDK 1.5
Generic Instantiation
Comparable<Date> c = new Date();
(b) JDK 1.5
Compile error
Generic ArrayList in JDK 1.5
+add(o: Object) : void
+add(o: E) : void
+add(index: int, o: Object) : void
+add(index: int, o: E) : void
+clear(): void
+clear(): void
+contains(o: Object): boolean
+contains(o: Object): boolean
+get(index: int) : Object
+get(index: int) : E
+indexOf(o: Object) : int
+indexOf(o: Object) : int
+isEmpty(): boolean
+isEmpty(): boolean
+lastIndexOf(o: Object) : int
+lastIndexOf(o: Object) : int
+remove(o: Object): boolean
+remove(o: Object): boolean
+size(): int
+size(): int
+remove(index: int) : boolean
+remove(index: int) : boolean
+set(index: int, o: Object) : Object
+set(index: int, o: E) : E
(a) ArrayList before JDK 1.5
(b) ArrayList in JDK 1.5
Several sections of CS202 over the past year have completed a lab using
the MyMath interface:
public interface MyMath<T> {
public T add(T o);
public T subtract(T o);
public T divide(T o);
public T multiply(T o);
In implementing MyMath <T>, you chose the type that T represents
and applied binary operations like add(), whose operands were the
current object and an object of class T
Note that, when we implement MyMath <T>, we define what
type T stands for. Therefore, the parameter types for the
methods are determined at compile time, and there is no
ambiguity for the compiler.
The generic method public static <E> void print(E[] list) is
different, because the type of the generic parameter may not be
known until runtime.
Generic Methods
Methods in non-generic classes may take generic parameters, but the syntax for
this may be unexpected:
public static <E> void print(E[] list) {
for (int i = 0; i < list.length; i++)
System.out.print(list[i] + " ");
Try this with an array of Doubles, Strings, etc. It won’t work with a primitive
data type.
Bounded Generic Type
Methods that use generic types can limit the possible types
to ones that extend a particular class or implement a
particular interface
For example, if your method finds the largest element in a
list by using compareTo(), it will only work if the parameter
objects include this method. You can make sure they do by
only using objects of classes that implement Comparable.
Bounded Generic Type
package demos;
import java.util.ArrayList;
import java.util.List;
public class Demo {
public static void main(String[] args) {
List<String> myList = new ArrayList<String>();
String[] myArray = {"Godzilla", "Dracula", "Frankenstein"};
for(String s:myArray) myList.add(s);
public static <E extends Comparable <E>> E min(List<E> theList) {
E smallest = theList.get(0);
for (E e: theList)
if(e.compareTo(smallest) < 0) smallest = e;
return smallest;
Bounded Generic Type
The placeholder E is an arbitrary choice. This min
method is equivalent to the one on the last slide:
public static <Z extends Comparable <Z>> Z
min(List<Z> theList) {
Z smallest = theList.get(0);
for (Z z: theList)
if(z.compareTo(smallest) < 0) smallest = z;
return smallest;
Bounded Generic Type
public static void main(String[] args ) {
Rectangle rectangle = new Rectangle(2, 2);
Circle9 circle = new Circle9(2);
System.out.println("Same area? " + equalArea(rectangle, circle));
public static <E extends GeometricObject> boolean
equalArea(E object1, E object2) {
return object1.getArea() == object2.getArea();
? extends T
? super T
unbounded wildcard
bounded wildcard
lower bound wildcard
Generic Types and Wildcard Types
? super E
E’s superclass
E’s subclass
? extends E
The Java collections we have been using are themselves
programmed in Java and follow some of the same patterns we
are learning.
List<E> is an interface
• There is a List class, but it is the old, pre-generics List. Eclipse or
the compiler will produce a warning but let you use it. Don’t.
ArrayList<E> is a class that extends Abstract List<E>, which
implements List
• There are various other interfaces and classes involved too
Type Erasure and Restrictions on
Generics are implemented using an approach called type erasure. The
compiler uses the generic type information to compile the code, but
erases it afterwards. So the generic information is not available at run
time. This approach enables the generic code to be backwardcompatible with legacy code that uses raw types.
Compile Time Checking
For example, the compiler checks whether generics is used correctly
for the following code in (a) and translates it into the equivalent code
in (b) for runtime use. The code in (b) uses the raw type.
ArrayList<String> list = new ArrayList<String>();
String state = list.get(0);
ArrayList list = new ArrayList();
String state = (String)(list.get(0));
Type Erasure
A generic class is shared by all its instances
regardless of its actual generic type.
GenericStack<String> stack1 = new GenericStack<String>();
GenericStack<Integer> stack2 = new GenericStack<Integer>();
Although GenericStack<String> and GenericStack<Integer> are two
types, there is only one class GenericStack loaded into the JVM.
Stack1 and stack2 in this example are still two different instances of the
GenericStack class
Type Erasure
Type Erasure is the reason why you can't construct
objects of a generic class, even though it seems that the
JVM should know at runtime what the class should be:
This will *NOT* work:
public static <E> void addOne(List<E> theList) {
theList.add(new E());
Type Erasure
A method may take parameters of generic types (eg
• addOne(List<E> theList)
In such a method, you can declare and instantiate a list of
the generic type. However, due to type erasure, you
cannot actually add anything to the list.
This will *NOT* work:
public static <E> void addOne(List<E> theList) {
List<E> myList = new ArrayList<E>();
String[] myArray = {"Godzilla", "Dracula",
for(String s:myArray) myList.add(s);

similar documents