Download presentation
Presentation is loading. Please wait.
Published byJaromír Tichý Modified over 5 years ago
1
Generics and Wildcards CSC 300 Fall 2019 Howard Rosenthal
LESSON 7 Generics and Wildcards CSC 300 Fall 2019 Howard Rosenthal
2
Course References Materials for this course have utilized materials in the following documents. Additional materials taken from web sites will be referenced when utilized. Anderson, Julie and Franceschi, Herve, Java Illuminated 5TH Edition,, Jones and Bartlett, 2019 Bravaco, Ralph and Simonson, Shai, Java Programming From The Ground Up, McGraw Hill, 2010 Deitel, Paul and Deitel, Harvey, Java, How To Program, Early Objects, Eleventh Edition, Pearson Publishing, 2018 Gaddis, Tony, Starting Out With Objects From Control Structures Through Objects, Seventh Edition, Pearson Publishing, 2019 Horstmann, Cay, Core Java For The Impatient, Addison Wesley- Pearson Education, 2015 Naftalin, Maurice and Wadler, Philip, Java Generics and Collections, O’Reilly Media, 2007 Schmuller, Joseph, Teach Yourself UML In 24 Hours Second Edition, SAMS Publishing, 2002 Urma, Raoul-Gabriel, Fusco, Mario and Mycroft, Alan, Java 8 in Action: Lambdas, Streams, and Functional-Style Programming, Manning Publishing, 2014 Wirfs-Brock, Rebecca, Wilkerson, Brian and Wiener, Laura, Designing Object-Oriented Software, Prentice Hall, 1990 Oracle On-Line Documentation and Tutorials,
3
Lesson Goals Describe wildcards and wildcard rules
Bounded and unbounded wildcards Wildcards and subtyping Wildcards versus type parameters Guidelines and restrictions Compare the ArrayList and Arrays Describe type erasure and generics inside the JVM Discuss restrictions on generics
4
Wildcards
5
What Is A Wildcard? In generic code, the question mark (?), called the wildcard, represents an unknown type. The wildcard can be used in a variety of situations including: The type of a parameter The type for a field The type for a local variable Sometimes as a return type (though it is better programming practice to be more specific). The wildcard is never used as a type argument for a generic method invocation, a generic class instance creation, or a supertype.
6
Quick Refresher – The Number Hierarchy
Just a Reminder – Number has multiple independent subclasses – each is at the same level
7
Upper Bounded Wild Cards
You can use an upper bounded wildcard to relax the restrictions on a variable. For example, say you want to write a method that works on List<Integer>, List<Double>, and List<Number>; you can achieve this by using an upper bounded wildcard. To declare an upper-bounded wildcard, use the wildcard character ('?'), followed by the extends keyword, followed by its upper bound. Note that, in this context, extends is used in a general sense to mean either "extends" (as in classes) or "implements" (as in interfaces). To write the method that works on lists of Number and the subtypes of Number, such as Integer, Double, and Float, you would specify List<? extends Number>. The term List<Number> is more restrictive than List<? extends Number> because the former matches a list of type Number only, whereas the latter matches a list of type Number or any of its subclasses. Consider the following process method: public static void process(List<? extends Foo> list) { /* ... */ } The upper bounded wildcard, <? extends Foo>, where Foo is any type, matches Foo and any subtype of Foo. The process method can access the list elements as type Foo: public static void process(List<? extends Foo> list) { for (Foo elem : list) { // ... } } Now look at the program SumLists.java, SumListsV2.java Note that you don’t create overloaded methods, instead you use a wildcard which allows you to send lists of different types. That is how you reuse the sumOfList method with any numeric wrapper type.
8
Unbounded Wild Cards The unbounded wildcard type is specified using the wildcard character (?), for example, List<?>. This is called a list of unknown type. There are two scenarios where an unbounded wildcard is a useful approach: If you are writing a method that can be implemented using functionality provided in the Object class. When the code is using methods in the generic class that don't depend on the type parameter. For example, List.size or List.clear. (These work for any type of list) In fact, Class<?> is so often used because most of the methods in Class<T> do not depend on T. Consider the following method, printList: public static void printList(List<Object> list) { for (Object elem : list) System.out.printf(”%s \n”, elem); System.out.printf(“\n”); } The goal of printList is to print a list of any type, but it fails to achieve that goal — it prints only a list of Object instances; it cannot print List<Integer>, List<String>, List<Double>, and so on, because they are not subtypes of List<Object>. To write a generic printList method, use List<?>: Because for any concrete type A, List<A> is a subtype of List<?>, you can use printList to print a list of any type See PrintList.java
9
Lower Bounded Wild Cards
A lower bounded wildcard restricts the unknown type to be a specific type or a super type of that type. A lower bounded wildcard is expressed using the wildcard character ('?'), following by the super keyword, followed by its lower bound: <? super A> You can specify an upper bound for a wildcard, or you can specify a lower bound, but you cannot specify both. Say you want to write a method that puts Integer objects into a list. To maximize flexibility, you would like the method to work on List<Integer>, List<Number>, and List<Object> — anything that can hold Integer values. To write the method that works on lists of Integer and the supertypes of Integer, such as Integer, Number, and Object, you would specify List<? super Integer>. The term List<Integer> is more restrictive than List<? super Integer> because the former matches a list of type Integer only, whereas the latter matches a list of any type that is a supertype of Integer. See AddToList.java
10
Wild Cards And Subtyping(1)
Generic classes or interfaces are not related merely because there is a relationship between their types. However, you can use wildcards to create a relationship between generic classes or interfaces. Given the following two regular (non-generic) classes: class A { /* ... */ } class B extends A { /* ... */ } It would be reasonable to write the following code: B b = new B(); A a = b; This example shows that inheritance of regular classes follows this rule of subtyping: class B is a subtype of class A if B extends A. This rule does not apply to generic types: List<B> listB = new ArrayList<>(); List<A> listA = listB; // compile-time error Although Integer is a subtype of Number, List<Integer> is not a subtype of List<Number> and, in fact, these two types are not related.
11
Wild Cards And Subtyping(2)
However, look at the figure below. The common parent of List<Number> and List<Integer> is List<?> In order to create a relationship between these classes so that the code can access Number's methods through List<Integer>'s elements, use an upper bounded wildcard: List<? extends Integer> intList = new ArrayList<>(); List<? extends Number> numList = intList; // OK. List<? extends Integer> is a subtype of List<? extends Number> Because Integer is a subtype of Number, and numList is a list of Number objects, a relationship now exists between intList (a list of Integer objects) and numList
12
Wild Cards And Subtyping(3)
The following diagram shows the relationships between several List classes declared with both upper and lower bounded wildcards.
13
Final Notes On Methods That Accept Type Parameters
Because the wildcard (?) in the method’s header does not specify a type-parameter name, you cannot use it as a type name throughout the method’s body You could, however, declare method sum as follows: public static <T extends Number> double sum(List<T> list) This allows the method to receive an List that contains elements of any Number subclass. You could then use the type parameter T throughout the method body. If the wildcard is specified without an upper bound, then only the methods of type Object can be invoked on values of the wildcard type. Also, methods that use wildcards in their parameter’s type arguments cannot be used to add elements to a collection referenced by the parameter.
14
The Get And Put Principle
It may be good practice to insert wildcards whenever possible, but how do you decide which wildcard to use? Where should you use extends, where should you use super, and where is it inappropriate to use a wildcard at all? The Get and Put Principle: Use an extends wildcard when you only get values out of a structure Use a super wildcard when you only put values into a structure Don’t use a wildcard when you both get and put. The Get and Put Principle also works the other way around. If an extends wildcard is present, pretty much all you will be able to do is get but not put values of that type; and if a super wildcard is present, pretty much all you will be able to do is put but not get values of that type. Whenever you use an iterator, you get values out of a structure, so use an extends wildcard. See ExtendExample.java In this program, without extends the program wouldn’t work Whenever you use the add method, you put values into a structure, so use a super wildcard. See SuperExample.java In this program, without super the program wouldn’t work Whenever you both put values into and get values out of the same structure, you should not use a wildcard. See GetAndPutExample.java In this program the collection is passed to both sum and count, so its element type must both extend Number (as sum requires) and be super to Integer (as count requires). The only two classes that satisfy both of these constraints are Number and Integer, and we have picked the first of these.
15
Arrays and Lists – A Comparison
16
Comparing Arrays And Lists (1)
We will be discussing Lists and other generic interfaces and classes in the near future, but it is useful to compare arrays vs. lists briefly at this time In Java, array subtyping is covariant, meaning that type S[] is considered to be a subtype of T[] whenever S is a subtype of T. Look at the code fragment, which allocates an array of Integers, assigns it to an array of Numbers, and then attempts to assign a Double into the array: Integer[] intArray = new Integer[] {1,2,3}; Number[] nums = intArray; nums[2] = 3.14; // array store exception Where is the problem? Since Integer[] is considered a subtype of Number[], according to the Substitution Principle the assignment on the second line must be legal. Instead, the problem is caught on the third line, and it is caught at run time. When an array is allocated (as on the first line), it is tagged with its reified type (a run-time representation of its component type, in this case, Integer), and every time an array is assigned into (as on the third line), an array store exception is raised if the reified type is not compatible with the assigned value In this case, a double cannot be stored into an array of Integer.
17
Comparing Arrays And Lists (2)
However, the subtyping relation for generics is invariant, meaning that type List<S> is not considered to be a subtype of List<T>, except in the trivial case where S and T are identical. Here is a code fragment analogous to the preceding one, with lists replacing arrays: List<Integer> intArray = Arrays.asList(1,2,3); List<Number> nums = intArray; // compile-time error nums.set(2, 3.14); Since List<Integer> is not considered to be a subtype of List<Number>, the problem is detected on the second line, not the third, and it is detected at compile time, not run time. Wildcards reintroduce covariant subtyping for generics, in that type List<S> is considered to be a subtype of List<? extends T> when S is a subtype of T. Here is a third variant of the fragment: List<? extends Number> nums = intArray; nums.set(2, 3.14); // compile-time error As with arrays, the third line is in error, but, in contrast to arrays, the problem is detected at compile time, not run time. The assignment violates the Get and Put Principle, because you cannot put a value into a type declared with an extends wildcard. Wildcards also introduce contravariant subtyping for generics, in that type List<S> is considered to be a subtype of List<? super T> when S is a supertype of T (as opposed to a subtype). Arrays do not support contravariant subtyping. For instance, recall that the method count accepted a parameter of type Collection<? super Integer> and filled it with integers. There is no equivalent way to do this with an array, since Java does not permit you to write (? super Integer)[].
18
Comparing Arrays And Lists (3)
Detecting problems at compile time rather than at run time brings two advantages It is more efficient since the system does not need to carry around a description of the element type at run time, and the system does not need to check against this description every time an assignment into an array is performed. More importantly a common family of errors is detected by the compiler. This improves every aspect of the program’s life cycle: coding, debugging, testing, and maintenance are all made easier, quicker, and less expensive. Apart from the fact that errors are caught earlier, there are many other reasons to prefer collection classes to arrays. Collections are far more flexible than arrays. The only operations supported on arrays are to get or set a component, and the representation is fixed. Collections support many additional operations, including testing for containment, adding and removing elements, comparing or combining two collections, and extracting a sublist of a list. Collections may be either lists (where order is significant and elements may be repeated) or sets (where order is not significant and elements may not be repeated), and a number of representations are available, including arrays, linked lists, trees, and hash tables. Finally, a comparison of the convenience classes Collections and Arrays shows that collections offer many operations not provided by arrays, including operations to rotate or shuffle a list, to find the maximum of a collection, and to make a collection unmodifiable or synchronized.
19
Comparing Arrays And Lists (4)
There are a few cases where arrays are preferred over collections. Arrays of primitive type are much more efficient since they don’t involve boxing; and assignments into such an array need not check for an array store exception, because arrays of primitive type do not have subtypes. And despite the check for array store exceptions, even arrays of reference type may be more efficient than collection classes depending on the compiler, so you may want to use arrays in crucial inner loops. You should measure performance to justify such a design, especially since future compilers may further optimize collection classes. Without covariance and without generics, there would be no way to declare methods that apply for all types. However, now that we have generics, covariant arrays are no longer necessary. Now we can give the methods the following signatures, directly stating that they work for all types: public static <T> void sort(T[] a); public static <T> void fill(T[] a, T val); Some people now argue that arrays are an artifact and that it would make more sense to just use Collections. For instance, it is possible to create multi-dimensional ArrayList objects.
20
More On Wildcards
21
Wildcards Versus Type Parameters – Wildcards (1)
The contains method checks whether a collection contains a given object, and its generalization, containsAll, checks whether a collection contains every element of another collection. There are two approaches to giving generic signatures for these methods. The first approach uses wildcards and is the one used in the Java Collections Framework. The second approach uses type parameters and is often a more appropriate alternative. Here are the types that the methods have in Java with generics: interface Collection<E> { ... public boolean contains(Object o); public boolean containsAll(Collection<?> c); ... } The first method does not use generics. The second method shows an important abbreviation. The type Collection<?> stands for: Collection<? extends Object> Extending Object is one of the most common uses of wildcards, so it makes sense to provide a short form for writing it.
22
Wildcards Versus Type Parameters – Wildcards (2)
These methods let us test for membership and containment: Object obj = "one"; List<Object> objs = Arrays.<Object>asList("one", 2, 3.14, 4); List<Integer> ints = Arrays.asList(2, 4); assert objs.contains(obj); // We will discuss assert later assert objs.containsAll(ints); assert !ints.contains(obj); assert !ints.containsAll(objs); See WildCardTest1.java (use java –ea to run) The given list of objects contains both the String "one" and the given list of integers, but the given list of integers does not contain the string "one", nor does it contain the given list of objects. The tests ints.contains(obj) and ints.containsAll(objs) might seem trivial. Of course, a list of integers won’t contain an arbitrary object, such as the string "one". But it is permitted because sometimes such tests might succeed
23
Using assert assert is a Java keyword used to define an assert statement. An assert statement is used to declare an expected boolean condition in a program. If the program is running with assertions enabled, then the condition is checked at runtime. java –ea classname or java –enableassertions classname If the condition is false, the Java runtime system throws an AssertionError . See WildcardTest1.java Assertion Vs Normal Exception Handling Assertions are mainly used to check logically impossible situations. For example, they can be used to check the state a code expects before it starts running or state after it finishes running. Unlike normal exception/error handling, assertions are generally disabled at run-time. Where to use Assertions Arguments to private methods. Private arguments are provided by developer’s code only and developer may want to check his/her assumptions about arguments. Conditional cases. Conditions at the beginning of any method. Where not to use Assertions Assertions should not be used to replace error messages Assertions should not be used to check arguments in the public methods as they may be provided by user. Error handling should be used to handle errors provided by user. Assertions should not be used on command line arguments.
24
Wildcards Versus Type Parameters – Type Parameters(1)
An alternative design for collections can only test containment for subtypes of the element type: interface MyCollection<E> { // alternative design ... public boolean contains(E o); public boolean containsAll(Collection<? extends E> c); ... } Say we have a class MyList that implements MyCollection. Now the tests are legal only one way around: Object obj = "one"; MyList<Object> objs = MyList.<Object>asList("one", 2, 3.14, 4); MyList<Integer> ints = MyList.asList(2, 4); assert objs.contains(obj); assert objs.containsAll(ints) assert !ints.contains(obj); // compile-time error assert !ints.containsAll(objs); // compile-time error The last two tests are illegal, because the type declarations require that we can only test whether a list contains an element of a subtype of that list. So we can check whether a list of objects contains a list of integers, but not the other way around.
25
Wildcards Versus Type Parameters – Type Parameters(2)
Which of the two styles is better is a matter of taste. The first permits more tests, and the second catches more errors at compile time (while also ruling out some sensible tests). The designers of the Java libraries chose the first, more liberal, alternative, because someone using the Collections Framework before generics might well have written a test such as ints.containsAll(objs), and that person would want that test to remain valid after generics were added to Java. However, when designing a new generic library, such as MyCollection, where backward compatibility is less important, the design that catches more errors at compile time might make more sense. Arguably, the library designers made the wrong choice. The same design choice applies to other methods that contain Object or Collection<? > in their signature, such as remove, removeAll, and retainAll.
26
Wildcard Capture And Helper Methods (1)
When a generic method is invoked, the type parameter may be chosen to match the unknown type represented by a wildcard. This is called wildcard capture. Consider the method reverse in the convenience class java.util.Collections, which accepts a list of any type and reverses it. It can be given either of the following two signatures, which are equivalent: public static void reverse(List<?> list); public static void <T> reverse(List<T> list); The wildcard signature is slightly shorter and clearer, and is the one used in the library. If you use the second signature, it is easy to implement the method: public static void <T> reverse(List<T> list) { List<T> tmp = new ArrayList<T>(list); for (int i = 0; i < list.size(); i++) list.set(i, tmp.get(list.size()-i-1)); } This copies the argument into a temporary list, and then writes from the copy back into the original in reverse order. If you try to use the first signature with a similar method body, it won’t work: public static void reverse(List<?> list) List<Object> tmp = new ArrayList<Object>(list); list.set(i, tmp.get(list.size()-i-1)); // compile-time error
27
Wildcard Capture And Helper Methods (2)
It is not legal to write from the copy back into the original, because we are trying to write from a list of objects into a list of unknown type. Replacing List<Object> with List<?> won’t fix the problem, because now we have two lists with (possibly different) unknown element types. Instead, you can implement the method with the first signature by implementing a private method, called a helper method, with the second signature, and calling the second from the first: public static void reverse(List<?> list) { rev(list); } private static <T> void rev(List<T> list) { List<T> tmp = new ArrayList<T>(list); for (int i = 0; i < list.size(); i++) list.set(i, tmp.get(list.size()-i-1)); } Here we say that the type variable T has captured the wildcard. This is a generally useful technique when dealing with wildcards,
28
Guidelines And Restrictions For Wildcards
29
Guidelines Review There are some general rules that can be followed when program with generics is determining when to use an upper bounded wildcard and when to use a lower bounded wildcard. Basic Definition: For purposes of this discussion, it is helpful to think of variables as providing one of two functions: An "in" variable serves up data to the code. Imagine a copy method with two arguments: copy(src, dest). The src argument provides the data to be copied, so it is the "in" parameter. An "out" variable holds data for use elsewhere. In the copy example, copy(src, dest), the dest argument accepts data, so it is the "out" parameter. Some variables are used both for "in" and "out" purposes — this scenario is also addressed in the guidelines. You can use the "in" and "out" principle when deciding whether to use a wildcard and what type of wildcard is appropriate.
30
Wildcard Guidelines An "in" variable is defined with an upper bounded wildcard, using the extends keyword. An "out" variable is defined with a lower bounded wildcard, using the super keyword. In the case where the "in" variable can be accessed using methods defined in the Object class, use an unbounded wildcard. In the case where the code needs to access the variable as both an "in" and an "out" variable, do not use a wildcard. Using a wildcard as a return type should be avoided because it forces programmers using the code to deal with wildcards.
31
Restrictions On Wildcards – Instance Creation (1)
Wildcards may not appear at the top level in class instance creation expressions (new), in explicit type parameters in generic method calls, or in supertypes (extends and implements). Instance Creation In a class instance creation expression, if the type is a parameterized type, then none of the type parameters may be wildcards. For example, the following are illegal: List<?> list = new ArrayList<?>(); // compile-time error Map<String, ? extends Number> map = new HashMap<String, ? extends Number>(); // compile-time error This is expected since the Get and Put Principle tells us that if a structure contains a wildcard, we should only get values out of it (if it is an extends wildcard) or only put values into it (if it is a super wildcard) and for a structure to be useful, we must do both. Therefore, we usually create a structure at a precise type, even if we use wild- card types to put values into or get values from the structure, as in the following ex- ample: List<Number> nums = new ArrayList<Number>(); List<? super Number> sink = nums; List<? extends Number> source = nums; for (int i=0; i<10; i++) sink.add(i); double sum=0; for (Number num : source) sum+=num.doubleValue(); Here wildcards appear in the second and third lines, but not in the first line that creates the list. Only top-level parameters in instance creation are prohibited from containing wild- cards. Nested wildcards are permitted. Hence, the following is legal: List<List<?>> lists = new ArrayList<List<?>>(); lists.add(Arrays.asList(1,2,3)); lists.add(Arrays.asList("four","five"));
32
Restrictions On Wildcards – Instance Creation (2)
One way to remember the restriction is that the relationship between wildcards and ordinary types is similar to the relationship between interfaces and classes Wildcards and interfaces are more general, ordinary types and classes are more specific, and instance creation requires the more specific information. Look at the following: List<?> list = new ArrayList<Object>(); // ok List<?> list = new List<Object>() // compile-time error List<?> list = new ArrayList<?>() // compile-time error The first is legal The second is illegal because an instance creation expression requires a class, not an interface The third is illegal because an instance creation expression requires an ordinary type, not a wildcard.
33
Restrictions On Wildcards – Generic Method Calls
If a generic method call includes explicit type parameters, those type parameters must not be wildcards. For example, say we have the following generic method: class Lists { public static <T> List<T> factory() return new ArrayList<T>(); } You may choose for the type parameters to be inferred, or you may pass an explicit type parameter. Both are legal: List<?> list = Lists.factory(); List<?> list = Lists.<Object>factory(); If an explicit type parameter is passed, it must not be a wildcard: List<?> list = Lists.<?>factory(); // compile-time error As before, nested wildcards are permitted: List<List<?>> = Lists.<List<?>>factory(); // ok
34
Restrictions On Wildcards – Supertypes
When a class instance is created, it invokes the initializer for its supertype. Any restriction that applies to instance creation must also apply to supertypes. In a class declaration, if the supertype or any superinterface has type parameters, these types must not be wildcards. For example, these declarations are illegal: class AnyList extends ArrayList<?> {...} // compile-time error class AnotherList implements List<?> {...} // compile-time error But, as before, nested wildcards are permitted: class NestedList extends ArrayList<List<?>> {...} // ok See additional reference: What is the difference between using generic type and wildcard.pdf
35
Type Erasure And Generics
36
Generics Works Through The Concept Of Erasure
When the compiler translates generic method printArray into Java bytecodes, it removes the type-parameter section and replaces the type parameters with actual types. This process is known as erasure. By default all generic types are replaced with type Object. See GenericMethodsTestWithObject.java Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to: Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods. Insert type casts if necessary to preserve type safety. Generate bridge methods to preserve polymorphism in extended generic types. Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead. Note that the compiler is doing a lot of the work.
37
Examples Of Erasure The erasure of a type is defined as follows
Drop all type parameters from parameterized types, and replace any type variable with the erasure of its bound, or with Object if it has no bound, or with the erasure of the leftmost bound if it has multiple bounds. Examples The erasure of List<Integer>, List<String>, and List<List<String>> is List. The erasure of List<Integer>[] is List[]. The erasure of List is itself, similarly for any raw type (see Section 5.3 for an ex- planation of raw types). The erasure of int is itself, similarly for any primitive type. The erasure of Integer is itself, similarly for any type without type parameters. The erasure of T in the definition of asList is Object, because T has no bound.
38
Erasure In Generic Classes (1)
During the type erasure process, the Java compiler erases all type parameters and replaces each with its first bound if the type parameter is bounded, or Object if the type parameter is unbounded. Consider the following generic class that represents a node in a singly linked list: public class Node<T> { private T data; private Node<T> next; public Node(T data, Node<T> next) this.data = data; this.next = next; } public T getData() { return data; } // ... Because the type parameter T is unbounded, the Java compiler replaces it with Object: public class Node private Object data; private Node next; public Node(Object data, Node next) public Object getData() { return data; }
39
Erasure In Generic Classes (2)
In the following example, the generic Node class uses a bounded type parameter: public class Node<T extends Comparable<T>> { private T data; private Node<T> next; public Node(T data, Node<T> next) { this.data = data; this.next = next; } public T getData() { return data; } // ... The Java compiler replaces the bounded type parameter T with the first bound class, Comparable: public class Node private Comparable data; private Node next; public Node(Comparable data, Node next) public Comparable getData() { return data; }
40
Erasure In Generic Methods
The Java compiler also erases type parameters in generic method arguments. Look at the following generic method: // Counts the number of occurrences of elem in anArray. // public static <T> int count(T[] anArray, T elem) { int cnt = 0; for (T e : anArray) if (e.equals(elem)) ++cnt; return cnt; } Because T is unbounded, the Java compiler replaces it with Object: public static int count(Object[] anArray, Object elem) { for (Object e : anArray) Suppose the following classes are defined: class Shape { /* ... */ } class Circle extends Shape { /* ... */ } class Rectangle extends Shape { /* ... */ } You can write a generic method to draw different shapes: public static <T extends Shape> void draw(T shape) { /* ... */ } The Java compiler replaces T with Shape: public static void draw(Shape shape) { /* ... */ } Note: I won’t be discussing this but when necessary the compiler may create bridge methods (somewhat like helpers)
41
Non-Reifiable Types A reifiable type is a type whose type information is fully available at runtime. This includes primitives, non-generic types, raw types, and invocations of unbound wildcards. Non-reifiable types are types where information has been removed at compile-time by type erasure — invocations of generic types that are not defined as unbounded wildcards. A non-reifiable type does not have all of its information available at runtime. Examples of non-reifiable types are List<String> and List<Number>; the JVM cannot tell the difference between these types at runtime. As shown in Restrictions on Generics, there are certain situations where non-reifiable types cannot be used such as: In an instanceof expression As an element in an array
42
Restrictions On Generics
43
Restrictions On The Use Of Generics
To use Java generics effectively, you must consider the following restrictions: Cannot instantiate generic types with primitive types The instantiated type must be a class or interface Cannot create instances of type parameters Cannot declare static fields whose types are type parameters Cannot use casts or instanceof with parameterized types Cannot create arrays of parameterized types Cannot create, catch, or throw objects of parameterized types Cannot overload a method where the formal parameter types of each overload “erase” to the same raw type
44
Example - Cannot Instantiate Generic Types With Primitive Types
Consider the following parameterized type: class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } // ... When creating a Pair object, you cannot substitute a primitive type for the type parameter K or V: Pair<int, char> p = new Pair<>(8, 'a'); // compile-time error You can substitute only non-primitive types for the type parameters K and V: Pair<Integer, Character> p = new Pair<>(8, 'a’); Note that the Java compiler autoboxes 8 to Integer.valueOf(8) and 'a' to Character('a')
45
Example - Cannot Create Instances of Type Parameters
You cannot create an instance of a type parameter. For example, the following code causes a compile-time error: public static <E> void append(List<E> list) { E elem = new E(); // compile-time error list.add(elem); } As a workaround, you can create an object of a type parameter through reflection: public static <E> void append(List<E> list, Class<E> cls) throws Exception { E elem = cls.newInstance(); // OK You can invoke the append method as follows: List<String> ls = new ArrayList<>(); append(ls, String.class); See DemoInstTypeParam.java
46
Example - Cannot Declare Static Fields Whose Types Are Type Parameters
A class's static field is a class-level variable shared by all non-static objects of the class. Hence, static fields of type parameters are not allowed. public class MobileDevice<T> { private static T operatingSystem; // ... } If static fields of type parameters were allowed, then the following code would be confused: MobileDevice<Smartphone> phone = new MobileDevice<>(); MobileDevice<Pager> pager = new MobileDevice<>(); MobileDevice<TabletPC> pc = new MobileDevice<>(); Because the static field operatingSystem is shared by phone, pager, and pc, what is the actual type of operatingSystem? It cannot be Smartphone, Pager, and TabletPC at the same time. You cannot, therefore, create static fields of type parameters.
47
Example - Cannot Use Casts or instanceof with Parameterized Types
Because the Java compiler erases all type parameters in generic code, you cannot verify which parameterized type for a generic type is being used at runtime: public static <E> void rtti(List<E> list) { if (list instanceof ArrayList<Integer>) // compile-time error // ... } The set of parameterized types passed to the rtti method is: S = { ArrayList<Integer>, ArrayList<String> LinkedList<Character>, ... } The runtime does not keep track of type parameters, so it cannot tell the difference between an ArrayList<Integer> and an ArrayList<String>. The most you can do is to use an unbounded wildcard to verify that the list is an ArrayList: public static void rtti(List<?> list) { if (list instanceof ArrayList<?>) { // OK; instanceof requires a reifiable type Typically, you cannot cast to a parameterized type unless it is parameterized by unbounded wildcards. For example: List<Integer> li = new ArrayList<>(); List<Number> ln = (List<Number>) li; // compile-time error However, in some cases the compiler knows that a type parameter is always valid and allows the cast: List<String> l1 = ...; ArrayList<String> l2 = (ArrayList<String>)l1; // OK
48
Example - Cannot Create Arrays of Parameterized Types
You cannot create arrays of parameterized types. For example, the following code does not compile: List<Integer>[] arrayOfLists = new List<Integer>[2]; // compile-time error The following code illustrates what happens when different types are inserted into an array: Object[] strings = new String[2]; strings[0] = "hi"; // OK strings[1] = 100; // An ArrayStoreException is thrown. If you try the same thing with a generic list, there would be a problem: Object[] stringLists = new List<String>[2]; // compiler error, but pretend it's allowed stringLists[0] = new ArrayList<String>(); // OK stringLists[1] = new ArrayList<Integer>(); // An ArrayStoreException should be thrown, but the runtime can't detect it If arrays of parameterized lists were allowed, the previous code would fail to throw the desired ArrayStoreException.
49
Example - Cannot Create, Catch, or Throw Objects of Parameterized Types
A generic class cannot extend the Throwable class directly or indirectly. For example: // Extends Throwable indirectly class MathException<T> extends Exception { /* ... */ } // compile-time error // Extends Throwable directly class QueueFullException<T> extends Throwable { /* ... */ // compile-time error A method cannot catch an instance of a type parameter: public static <T extends Exception, J> void execute(List<J> jobs) { try for (J job : jobs) // ... } catch (T e) { // compile-time error You can, however, use a type parameter in a throws clause: class Parser<T extends Exception> public void parse(File file) throws T { // OK
50
Example - Cannot Overload a Method Where the Formal Parameter Types of Each Overload Erase to the Same Raw Type A class cannot have two overloaded methods that will have the same signature after type erasure. public class Example { public void print(Set<String> strSet) { } public void print(Set<Integer> intSet) { } } The overloads would all share the same classfile representation and will generate a compile-time error.
51
Exercise 1 Erasure What is the following class converted to after type erasure? public class Pair<K, V> { public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey(); { return key; } public V getValue(); { return value; } public void setKey(K key) { this.key = key; } public void setValue(V value) { this.value = value; } private K key; private V value;
52
Exercise 1 Erasure - Answer
public class Pair { public Pair(Object key, Object value) { this.key = key; this.value = value; } public Object getKey() { return key; } public Object getValue() { return value; } public void setKey(Object key) { this.key = key; } public void setValue(Object value) { this.value = value; } private Object key; private Object value;
Similar presentations
© 2025 SlidePlayer.com Inc.
All rights reserved.