Ch. 19 Notes

advertisement
CS 1302 – Ch 19 – Generics
These notes cover Sections 19.1-19.4, 19.7
Section 19.1 – Introduction
1. An ArrayList uses generics to specify the type of elements it will hold:
private ArrayList <String>
cities;
private ArrayList <Double>
salaries;
private ArrayList <Employee> employees;
2. In a sense, generics lets us define a reference type as a variable. For example, ArrayList is a generic class:
public class ArrayList<E> extends ...
public void add( E e )...
public E get( int index )...
The E is called a generic type parameter. When the program is compiled, the actual type (e.g. String, etc) is
substituted for the generic type (E).
3. Why is generics useful? Hopefully, from the example above it is obvious: we can define an ArrayList class that
can hold anything! It allows us to write code that can be used in more situations and this is the heart of
reusability. The use of generics in Java essentially allows a reference type to be a variable. More formally,
generics is the ability of a language to allow for parameterized types.
Section 19.2 – Motivations & Benefits
1. Another key benefit of generics is to enable errors to be detected at compile time rather than at runtime. A
generic class or method permits you to specify allowable types of objects that the class or method may work
with. If you attempt to use the class or method with an incompatible object, a compile error occurs.
2. Example:
Without Generics (Prior to Java 1.5)
With Generics (Java 1.5 or higher)
ArrayList dogs = new ArrayList();
dogs.add( new Dog("Spot") );
dogs.add( new Computer("fast") );
ArrayList<Dog> dogs = new ArrayList<>();
dogs.add( new Dog("Spot") );
dogs.add( new Computer("fast") );
Nothing prevents us from putting any object in The last line generates a compile error. We say that this
the ArrayList.
ArrayList is type safe.
Dog d = (Dog)dogs.get(0);
Dog d = (Dog)dogs.get(0);
Elements are stored as Object instances, thus, a Elements are stored as Dog instances, thus, no cast is
cast is required when retrieving elements.
required.
1
3. The Comparable interface is defined without generics prior to Java 1.5 as shown below in a. Later versions of
Java use generics to define Comparable as shown in b below.
package java.lang;
package java.lang;
public interface Comaprable {
public int compareTo(Object o)
}
public interface Comaprable<T> {
public int compareTo(T o)
}
(b) JDK 1.5
(a) Prior to JDK 1.5
4. Example:
Without Generics
With Generics
class Employee implements Comparable {
public int compareTo(Object o) {
Employee e;
if( o instanceof Employee )
e = (Employee)o;
// compare this and e
}
}
class Employee2 implements Comparable<Employee> {
public int compareTo(Employee e) {
// compare this and e
}
}
Without generics, (a) we should check the type With generics, we have type safety, we know the argument
of the argument, o, (b) we must cast it to do a is an Employee.
meaningful comparison, (c) what do we do if o
is not an Employee?
Employee e = new Employee();
Dog d = new Dog();
e.compareTo(d);
Employee2 e2 = new Employee2();
Dog d = new Dog();
e2.compareTo(d);
Without generics this is probably a run-time With generics this is a compile error.
error.
5. Example:
Without Generics
With Generics
class Date implements ..., Comparable {
class Date implements ..., Comparable<Date> {
Without Generics
With Generics
Comparable d = new Date();
d.compareTo("Aardvark");
Run-time error
Comparable<Date> d2 = new Date();
d2.compareTo("Aardvark");
The first line shows generic instantiation and the second
gives a compile error
2
6. Consider the generic ArrayList class as shown on the right below. The generic type parameter, E can be used as a
type for any instance variables, method parameters or return types, or local variables.
java.util.ArrayList
java.util.ArrayList<E>
+ArrayList()
+ArrayList()
+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
(b) ArrayList in JDK 1.5
(a) ArrayList before JDK 1.5
7. Generic types must be reference types; they cannot be primitive types. For example, this will generate a compile
error:
ArrayList<int> vals = new ArrayList<int>();
We would need to use the Integer wrapper class:
ArrayList<Integer> vals = new ArrayList<Integer>();
Note that we can still write a statement like this:
vals.add(7);
Java automatically converts the primitive value 7 into: new Integer(7) through a process called autoboxing.
We can also retrieve a value from the previous example and store it in a primitive data type through a process
called autounboxing.
int i = vals.get(0);
Homework:
a. Create an ArrayList of Dog objects and add a few Dogs.
b. Repeat step a (i.e. create another)
c. Create an ArrayList that holds ArrayList’s of Dogs.
d. Add the two lists (from a and b) to the list from c.
e. Write code to loop over the ArrayList from c and display all Dogs
3
Homework: Fill in the blank with the appropriate reference type
____________________________________ dogLists = new ArrayList<>();
_____________________ dogs1 = new ArrayList<>(Arrays.asList(new Dog(), new Dog()));
_____________________ dogs2 = new ArrayList<>(Arrays.asList(new Dog(), new Dog(), new Dog()));
dogLists.add(dogs1);
dogLists.add(dogs2);
for( _____________________ dogs : dogLists ) {
for(__________________ d : dogs)
System.out.println(d);
}
Section 19.3 – Defining Generic Classes & Interfaces
1. A queue is a structure that models objects (people, print jobs, etc) waiting in a line. It is a FIFO (first-in first-out)
structure. When a new person shows up they go to the end of the queue. When it is time to take the next
person from the queue, we take the person in the front of the queue.
2. We can define a generic queue:
public class Queue<E> {
private ArrayList<E> queue;
public Queue() {
queue = new ArrayList<E>();
}
public void add( E item ) {
queue.add(item);
}
public E removeFirst() {
return queue.remove(0);
}
public int count() {
return queue.size();
}
Here, E is a generic type (or type variable). The symbol E is used
here, but in fact any symbol could be used. The following symbols
are commonly used for type variables T-type, E-element, V-value,
K-key.
We can now create Queue’s of different types of objects:
Queue<Person> people = new Queue<Person>();
people.add( new Person("Dave",22) );
...
Queue<House> houses = new Queue<House>();
houses.add( new House(3365,4,3.5) );
...
}
3. The text defines a GenericStack class. You should understand that too.
4. A generic class can have more than one parameter. A good example is HashMap or TreeMap that have two
generic parameters: K and V. We will consider these in another chapter.
4
Section 19.4 – Generic Methods
1. A method can use generic types as shown above when the class defines a generic type parameter. However,
now we consider writing a generic method, one that defines a generic type parameter in the method
declaration. In general, to declare a generic method, place the type parameter before the method return type.
For example:
public static <E> void method( ... )
2. We can also define a bounded type
<E extends S>
Which specifies that any class (or interface) that is a subtype of S (or S itself) is acceptable.
3. Consider the example below to find the larger of two objects. We considered an example similar to this in an
earlier chapter.
public static Comparable max(Comparable o1, Comparable o2) {
if (o1.compareTo(o2) > 0)
return o1;
else
return o2;
}
4. A better way to write the max method is to use a bounded generic type. Notice that now we can use E as the
return type of the method, and the type of the two objects to compare.
public static <E extends Comparable<E>>
if (o1.compareTo(o2) > 0)
return o1;
else
return o2;
}
E
max( E o1, E o2 ) {
Section 19.7 – Wildcard Generic Types
1. Consider the classes shown on the right and the method below:
public static void print(ArrayList<GeometricObject> shapes) {
for( GeometricObject shape : shapes)
System.out.println(shape);
}
Next, consider this code (which works as expected):
Rectangle rectangle = new Rectangle(2, 2);
Circle circle = new Circle(2);
Circle circle2 = new Circle(4);
ArrayList<GeometricObject> shapes =
new ArrayList<>(Arrays.asList(rectangle, circle, circle2));
print(shapes);
5
Next, consider this code which generates a compile error:
ArrayList<Circle> circles = new ArrayList<>(Arrays.asList(circle, circle2));
print(circles);
Although Circle is a subtype of GeometricObject, ArrayList< GeometricObject > is not a subtype of ArrayList
<Circle>.
2. One solution to fix the problem above is to use a wildcard to specify the generic type. This is an example of a
(upper) bounded wildcard.
<? extends T>
which means T or any subtype of T.
3. Consider the example above.
public static void print2(ArrayList<? extends GeometricObject> shapes) {
for( GeometricObject shape : shapes)
System.out.println(shape);
}
4. A lower bound wildcard is specified like this:
<? super T>
which means T or any super type T. We will see how this is useful in another chapter.
5. There are several restrictions on generics:
1. Can’t create an instance of a generic type.
For example: E object = new E(); gives a compile error. The reason this is incorrect is because
the instantiation occurs at run-time, but the generic type E is not available at run-time.
2. Can’t create a generic array. For example:
E[] elements = new E[10];
Is not allowed. However, you can circumvent this with (get compile warning):
E[] elements = (E[]) new Object[10];
6
Download