--- title: Java Programming 2 - Generics tags: java, generics --- # Java Generics ### References * https://courses.cs.washington.edu/courses/cse331/18wi/lectures/lec13-generics.pdf * https://www.oracle.com/technetwork/articles/java/juneau-generics-2255374.html * https://www.journaldev.com/1663/java-generics-example-method-class-interface ## Why We Need Generics? * For example, we consider a collection of books like this: ```java= public class BoxOfBooks { private Book[] books; public BoxOfBooks(int size) { books = new Book[size]; } public Book get(int idx) { Book output = books[idx]; books[idx] = null; return output; } public void add(int idx, Book book) { books[idx] = book; } } ``` * Now we consider another collection, say toys: ```java= public class BoxOfToys { private Toy[] toys; public BoxOfToys(int size) { toys = new Toy[size]; } public Toy get(int idx) { Toy output = toys[idx]; toys[idx] = null; return output; } public void add(int idx, Toy toy) { toys[idx] = toy; } } ``` * Stop!!! **Don’t repeat yourself!** ```java= public class Box<T> { private T[] items; public Box(int size) { items = (T[]) new Object[size]; // Why this works? } public T get(int idx) { T output = items[idx]; items[idx] = null; return output; } public void add(int idx, T item) { items[idx] = item; } } ``` * So we can store books and toys by using this **generic class**. * You may see Jave Generics widely used in Java Collections. ```java= public class GenericsDemo1 { public static void main(String[] args) { Box<Book> c1 = new Box<>(); c1.add(new Book("Java Programming 2")); Box<Toy> c2 = new Box<>(); c2.add(new Toy("Snoopy")); } } ``` * In this way, we could **reuse** our codes, just like what we learned before. * For example, loops, methods, and inheritance in OOP. ## Syntax of Generic Classes * To declare the **type parameters**, we put these type variables in a pair of angle brackets <>. ```java= class ClassName<T1, T2, ...> {} interface InterfaceName<T1, T2, ...> {} ``` * For example, ```java= public interface List<E> { E get(); void add(E e); } ``` * Another example shows how to declare multiple type parameters. ```java= public interface Map<K, V> { V get(K key); void put(K key, V value); boolean contains(K key); K[] keys(); V[] values(); } ``` * For convention, we use letters like: * ``E``: element * ``T``: type * ``K``, ``V``: key and value * Could you explain the following code snippet? ```java= public class Graph<N> extends Iterable<N> { List<List<N>> neighbors; public Graph(Set<N> nodes, Set<Tuple<N, N>> edges) { ... } } ``` ## Upper Bound of Type Parameters * We can put restrictions on the type parameters. ```java= interface List1<E extends Object> { ... } interface List2<E extends Number> { ... } public class UpperBoundGenericsDemo { public static void main(String[] args) { List1<Date> lst1 = ... // ok! List2<Date> lst2 = ... // compile-time error! } } ``` * As you can see, you cannot use ``Date`` in ``List2<E extends Number>`` because ``Date`` is not a subtype of ``Number``. * So Java compiler can detect the error before you run the program. ==與其執行的時候才錯誤,不如寫程式的當下就知道有問題!== * Let ``Type1`` and ``Type2`` (and so on) be any type. Then the syntax of **bounded** type parameters is as follws: ```java= class ClassName<T1 extends Tyep1, T2 extends Tyep2, ...> {} interface InterfaceName<T1 extends Tyep1, T2 extends Tyep2, ...> {} ``` * You can try this example: ```java= public interface Path<N, P extends Path<N, P>> extends Iterable<N>, Comparable<Path<?, ?>> { public Iterator<N> iterator(); ... } ``` * For multiple upper bounds on one type variable, you can use the operator & to cascade all types required. ```java= class ClassName<T extends Class1 & Interface2 & Interface3 & ...> {} ``` ## Generics & Subtyping * Assume that ``Integer`` and ``Double`` are subtypes of ``Number``. ```java= Number number = new Integer(100); ... number = new Double(3.14); ``` * Now we consider to use these types in generics. * We proceed to investigate the relation between generics and subtyping. * Java subtyping is **invariant** with respect to generics: neither covariant nor contravariant. * Java adopts a conservative way. * For example, ``List<Integer>`` is not a subtype of ``List<Number>`` and vice versa. * Rule: * Let ``X`` be any type. * If ``T1`` is not identical to ``T2``, then ``X<T1>`` is irrevalent with ``X<T2>``. ==簡而言之,<>內的型態不同時,就沒有繼承關係。== * We can see the reasons below: * **Covariance** allows read-only operations. (Why?) ```java= List<Number> data = new List<Integer>(); Number item = data.get(); // always ok data.add(5.28); // bazinga!!! ``` * **Contravariance** allows write-only operations. (Why?) ```java= List<Integer> data = new List<Number>(); Integer item = data.get(); // bazinga!!! data.add(0); // always ok ``` ### More about the parameters * We have seen ``List<Integer>`` and ``List<Number>`` are not subtype-related. * But there is subtyping as expected on the generic types themselves. * If ``HeftyBag`` extends ``Bag``, then * ``HeftyBag<Integer>`` is a subtype of ``Bag<Integer>``; * ``HeftyBag<Number>`` is a subtype of ``Bag<Number>``; * ``HeftyBag<String>`` is a subtype of ``Bag<String>``. ==<>內形態相同時,holder本身的繼承關係是存在的。== ## Syntax of Generic Methods * Why do we need generic methods? ```java= public class Util { public static void sum(List<Number> lst) { ... } public static void main(String[] args) { List<Integer> data = ... sum(data); // goes wrong in compile time } } ``` * As seen before, ``List<Integer>`` is not a subtype of ``List<Number>``. * We can declare a new type parameter for the method. ```java= public class Util { public <T extends E> static void sum(List<T> lst) { ... } public static void main(String[] args) { List<Integer> data = ... sum(data); // pretty good! } } ``` * Check another example shown below. ```java= public class Util2 { public <T> static void copyTo(List<T> dst, List<T> src) { for (T item: src) dst.add(item); } public static void main(String[] args) { List<Integer> src = Arrays.asList(10, 20, 30); List<Integer> dst = new ArrayList<>(); Util.copyTo(dst, src); } } ``` * What is the restriction of this solution? * You can only duplicate the data in the data structure of the same type. * For example, we can copy data from ``List<Integer>`` to ``List<Integer>``, but not from ``List<Integer>`` to ``List<Number>``. * How about this example? ```java= public class Util2 { public <T1, T2 extends T1> static void copyTo(List<T1> dst, List<T2> src) { for (T item: src) dst.add(item); } public static void main(String[] args) { List<Integer> src = Arrays.asList(10, 20, 30); List<Number> dst = new ArrayList<>(); Util.copyTo(dst, src); } } ``` ## Wildcard * **P**roducer **E**xtends **C**onsumer **S**uper (**PECS**) ```java= public <T> static void copyTo(Collection<? super T> dst, Collection<? extends T> src) { for (T item: src) dst.add(item); } ``` ## Comparison to Native Arrays * Actually, the native array allows covariance. * However, this leads to troubles which cannot be discovered in compile-time. * For example, ```java= public class GenericsDemo2 { public static void main(String[] args) { Animal[] animals = new Dog[256]; // covariance add7(animals, new Cat()); } public static void add7(Animal[] animals, Animal animal) { animals[7] = animal; } } ``` * In this case, you encounter the **ArrayStoreException** at runtime. ## Restrictions on Java Generics * Cannot instantiate generic types with primitive types. * Solution: you need those wrapper classes (``Integer``, ``Double``, ...). * Cannot create instances of type parameters. * Solution: injection * Cannot declare static fields whose types are type parameters. * Solution: * 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 ## Application: Object Comparison ```java= public class ComparisonDemo { public static void main(String[] args) { // TBD } } ```