Oracle - Generics

1. Generic types

1.1 A simple Box Class

package org.oracle;

public class Box {
private Object object;

public void set(Object object) {
this.object = object;
}

public Object get() {
return object;
}

} 

package org.oracle;

public class Main {
public static void main(String[] args) {
Box box = new Box();
box.set(5);
String name = (String) box.get();
}
}


1.2 A Generic version of the Box Class

package org.oracle;

public class Box<T> {
private T t;

public void set(T t) {
this.t = t;
}

public T get() {
return t;
}
}
package org.oracle;

public class Main {
public static void main(String[] args) {
Box<String> box = new Box<>();
box.set("abc");
String s = box.get();
}
}


1.3 Multiple type parameters

package org.oracle;

public interface Pair<K, V> {
public K getKey();
public V getValue();
}
package org.oracle;

public class OrderPair <K, V> implements Pair<K, V>{
private K key;
private V value;

public OrderPair(K key, V value) {
this.key = key;
this.value = value;
}

@Override
public K getKey() {
return key;
}

@Override
public V getValue() {
return value;
}
}
package org.oracle;

public class Main {
public static void main(String[] args) {
Pair<String, Integer> p1 = new OrderPair<>("One", 1);
Pair<String, String> p2 = new OrderPair<>("Two", "Iki");
System.out.println(p1.getKey());
System.out.println(p1.getValue());
}
}


2. Raw types

2.1 For backward compatibility, assigning a parameterized type to its raw type is allowed:

package org.oracle;

public class Main {
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
}
}


2.2 But if you assign a raw type to a parameterized type, you get a warning:

package org.oracle;

public class Main {
public static void main(String[] args) {
Box rawBox = new Box();
Box<Integer> intBox = rawBox; // warning: unchecked conversion

}
}


You also get a warning if you use a raw type to invoke generic methods in the corresponding generic type:

package org.oracle;

public class Main {
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8); // warning: unchecked invocation to set(T)
}
}

The warning shows that raw types bypass generic type checks, deferring the catch of unsafe code to runtime. Therefore, you should avoid raw types. 


3. Generic Methods

package org.oracle;

public class Pair<K, V> {
private K key;
private V value;

public Pair(K key, V value) {
this.key = key;
this.value = value;
}

public K getKey() {
return key;
}

public void setKey(K key) {
this.key = key;
}

public V getValue() {
return value;
}

public void setValue(V value) {
this.value = value;
}
}
package org.oracle;

public class Util {
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue());
}
}
package org.oracle;

public class Main {
public static void main(String[] args) {
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.compare(p1, p2);
}
}


4. Bounded type parameters

There may be times when you want to restrict the types that can be used as type arguments in a parameterized type. 

package org.oracle;

public class Box <T> {
private T t;

public T getT() {
return t;
}

public void setT(T t) {
this.t = t;
}

public <U extends Number> void inspect(U u) {
System.out.println("T: " + t.getClass().getName());
System.out.println("U: " + u.getClass().getName());
}
}
package org.oracle;

public class Main {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<>();
integerBox.setT(new Integer(10));
integerBox.inspect("abc"); // error: this is String
}
}


5. Generic methods and bounded type parameters

Bounded type parameters are key to the implementation of generic algorithms. Consider the following 

method that counts the number of elements in an array T[] that are greater than a specified element.

public static <T> int countGreaterThan(T[] array, T element) {
int count = 0;
for (T e : array)
if (e > element) // compiler error
count++;
return count;
}

The implementation of the method is straightforward, but it does not compile because the greater than operator(>) applies only to primitive types such as short, int, double, long, float, byte and char. You cannot use the > operator to compare objects. To fix the problem, use a type parameter bounded by the Comparable<T> interface:

public static <T extends Comparable<T>> int countGreaterThan(T[] array, T element) {
int count = 0;
for (T e : array)
if (e.compareTo(element) > 0)
count++;
return count;
}


6. Generics, Inheritance, and Subtypes

As you already know, it is possible to assign an object of one type to an object of another type provided that the types are compatible. For example, you can assign an Integer to an Object, since Object is one of Integer's supertypes:

Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger;

But Integer is also a kind of Number, so the following code is valid as well:

public void someMethod(Number n) { /* ... */ }

package org.oracle;

public class Main {
public static void main(String[] args) {
foo(new Integer(10));
foo(new Double(5.5));
}

public static void foo(Number n) {

}
}


The same is also true with generics. You can perform a generic type invocation, passing Number as its type argument, and any subsequent invocation of add will be allowed if the argument is compatible with Number:

package org.oracle;

public class Main {
public static void main(String[] args) {
Box<Number> box = new Box<>();
box.setT(new Integer(10));
box.setT(new Double(5.5));
}
}


Now consider the following method:

public static void foo(Box<Number> box) {
/*
some code
*/
}

What type of argument does it accept? By looking at its signature, you can see that it accepts a single argument whose type is Box<Number>. But what does that mean? Are you allowed to pass in Box<Integer> or Box<Double>, no of course. Because, Box<Integer> and Box<Double> are not subtypes of Box<Number>.


7. Type interface and Generic methods

Generic methods introduced you to type interface, which enables you to invoke a generic method as you would and ordinary method, without specifying a type between angle brackets. 

package org.oracle;

import java.util.List;

public class BoxDemo {

public static <U> void addBox(U u, List<Box<U>> boxes) {
Box<U> box = new Box<>();
box.setT(u);
boxes.add(box);
}

public static <U> void outputBoxes(List<Box<U>> boxes) {
int counter = 0;
for (Box<U> box : boxes) {
U boxContents = box.getT();
System.out.println("Box #" + counter + " contains [" + boxContents.toString() + "]");
counter++;
}
}
}
package org.oracle;

import java.util.ArrayList;

public class Main {
public static void main(String[] args) {
ArrayList<Box<Integer>> list = new ArrayList<>();
BoxDemo.<Integer>addBox(Integer.valueOf(10), list);
BoxDemo.addBox(Integer.valueOf(20), list);
BoxDemo.addBox(Integer.valueOf(30), list);
BoxDemo.outputBoxes(list);
}
}


8. Upper bounded wildcards

To write the method that works on list 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 then 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 subclass. 

package org.oracle;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3);
System.out.println(sumOfList(integers));

List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
System.out.println(sumOfList(doubles));
}

public static double sumOfList(List<? extends Number> list) {
double s = 0;
for (Number n : list)
s += n.doubleValue();
return s;
}
}


9. Unbounded wildcards

package org.oracle;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3);
printList(list);
}

public static void printList(List<Object> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
}

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<?>

package org.oracle;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3);
printList(list);
}

public static void printList(List<?> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
}

Because for any concrete type A, List<A> is a subtype of List<?>, you can  use printList to print a list of any type.


*** It is important to note that List<Object> and List<?> are not the same. You can insert an Object, or any subtype of Object, into a List<Object>. But you can insert null into a List<?>. 


10. Lower bounded wildcard

A lower bounded wildcard restricts the unknown type to be a specific type or a super type of that type.


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 than can hold Integer values. 

To write the method that works on lists 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. 

package org.oracle;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
list.add(new Object());
addNumber(list);
System.out.println(list);
list.add(new Object());
System.out.println(list);
list.add(2.2);
System.out.println(list);
}

public static void addNumber(List<? super Integer> list) {
for (int i = 1; i <= 10; i++)
list.add(i);
}

} 


11. Wildcard capture and Helper methods

In some cases, the compiler infers the type of a wildcard. For example a list may be defined as List<?> but, when evaluating an expression, the compiler infers a particular type from the code. This scenario is known as a wildcard capture. 

public static void foo(List<?> i) {
i.set(0, i.get(0));
}

In this example, the compiler processes the i input parameter as being of type Object. When the foo method invokes List.set(int, E), the compiler is not able to confirm the type of object that is being inserted into the list, and an error is produced. When this type of error occurs it typically means that the compiler believes that you are assigning the wrong type to a variable. 


You can fix it by writing a private helper method which captures the wildcard. 

public static void foo(List<?> i) {
i.set(0, i.get(0));
}

public static <T> void fooHelper(List<T> l) {
l.set(0, l.get(0));
}


In this example, the code is attempting an unsafe operation

package org.oracle;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<Integer> li = Arrays.asList(1, 2, 3);
List<Double> ld = Arrays.asList(1.1, 2.2, 3.3);
swapFirst(li, ld);
}

public static void swapFirst(List<? extends Number> l1, List<? extends Number> l2) {
Number temp = l1.get(0);
l1.set(0, l2.get(0));
l2.set(0, temp);
}
}

While List<Integer> and List<Double> both fulfill the criteria of List<? extend Number>, it is clearly incorrect to take an item from a list of Integer values and attempt to place it into a list of Double values.


12. Guidelines for Wildcard use

An "in" variable:

    An "in" variable serves up data to 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:

    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.

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 code needs to access the variable as both "in" and an "out" variable, do not use wildcard. 


These guidelines do not apply to a method's return type. Using a wildcard as a return type should be avoided because it forces programmers using the code dealing with wildcards. 


A list defined by List<? extends ...> can be informally thought as read-only, but that is not strict guarantee. Suppose you have the following two classes: 

package org.oracle;

public class NaturalNumber {
private int i;

public NaturalNumber(int i) {
this.i = i;
}
}
package org.oracle;

public class EvenNumber extends NaturalNumber{

public EvenNumber(int i) {
super(i);
}
}
package org.oracle;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<EvenNumber> le = new ArrayList<>();
List<? extends NaturalNumber> ln = le;
ln.add(new NaturalNumber(35)); // compile-time error
}
}

Because List<EvenNumber>  is a subtype of List<? extends NaturalNumber>, you can assign le to ln. But you cannot use ln to add a natural number to a list of even numbers. The following operations on the list are possible:

* You can add null

* You can invoke clear

* You can get the iterator and invoke remove

* You can capture the wildcard and write elements that you have read from the list. 


13. Erasure of generic types

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:

package org.example;

public class Node<T> {
public T data;
public Node<T> next;

public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
}

Because the type parameter T is unbounded, the Java compiler replaces it with Object:

package org.example;

public class Node {
public Object data;
public Node next;

public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
}


In the following example, the generic Node class uses a bounded type parameter:

package org.example;

public class Node<T extends Comparable<T>> {
public T data;
public Node<T> next;

public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
}

The Java compiler replaces the bounded type parameter T with the first bounded class, Comparable:

package org.example;

public class Node {
public Comparable data;
public Node next;

public Node(Comparable data, Node next) {
this.data = data;
this.next = next;
}
}


14. Erasure of Generic methods

The Java compiler also erases type parameters in generic method arguments. 

public static <T> int count(T[] arr, T elem) {
int cnt = 0;
for (T e : arr)
if (e.equals(elem))
++cnt;
return cnt;
}

Because T is unbounded, the Java compiler replaces it with Object:

public static int count(Object[] arr, Object elem) {
int cnt = 0;
for (Object e : arr)
if (e.equals(elem))
++cnt;
return cnt;
}


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) { /* ... */ }


15. Effects of type erasure and bridge methods

package org.example;

public class Node<T> {
public T data;

public Node(T data) {
this.data = data;
}

public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
}
package org.example;

public class MyNode extends Node<Integer>{

public MyNode(Integer data) {
super(data);
}

public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}

Consider the following code:

MyNode mn = new MyNode(5);
Node n = mn; // A raw type - compiler throws an unchecked warning
n.setData("Hello"); // Causes a ClassCastException to be thrown
Integer x = mn.data;

After type erasure, this code becomes:

MyNode mn = new MyNode(5);
Node n = mn;            // A raw type - compiler throws an unchecked warning
                        // Note: This statement could instead be the following:
                        //     Node n = (Node)mn;
                        // However, the compiler doesn't generate a cast because
                        // it isn't required.
n.setData("Hello");     // Causes a ClassCastException to be thrown.
Integer x = (Integer)mn.data; 

The next section explains why a ClassCastException is thrown at the n.setData("Hello"); statement.


Bridge methods

When compiling a class or interface that extends a parameterized class or implements a parameterized interface, the compiler may need to create a synthetic method, which is called a bridge method, as part of the type erasure process.

After type erasure, the Node and MyNode classes become:

public class Node {

    public Object data;

    public Node(Object data) { this.data = data; }

    public void setData(Object data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node {

    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}


After type erasure, the method signatures do not match; the Node.setData(T) method becomes Node.setData(Object). As a result, the MyNode.setData(Integer) method does not everride the Node.setData(Object) method. 

To solve this problem and preserve the polymorphism of generic types after type erasure, the Java compiler generates a bridge method to ensure that subtyping works as expected. 

For the MyNode class, the compiler generates the following bridge method for setData:

class MyNode extends Node {

    // Bridge method generated by the compiler
    //
    public void setData(Object data) {
        setData((Integer) data);
    }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }

    // ...
}

The bridge method MyNode.setData(Object) delegates to the original MyNode.setData(Integer) method. As a result, the n.setData("Hello"); statement calls the method MyNode.setData(Object), and ClassCastException is thrown because "Hello" can't be cast to Integer.












-----------------------------------------------------------------------------------------------------------------------------

***

package org.example;

public class GenericMethodDemo {
public static void main(String[] args) {
Integer[] nums = {1, 2, 3, 4, 5}, nums2 = {1, 2, 3, 4, 5};
String[] svals = {"1", "2", "3", "4", "5"};

if (arraysEqual(nums, nums)) System.out.println("nums in equal nums2");
if (!arraysEqual(nums, svals)) System.out.println("nums is not equal svals");
}

public static <T> boolean arraysEqual(T[] x, T[] y) {
if (x.length != y.length) return false;

for (int i = 0; i < x.length; i++)
if (!x[i].equals(y[i])) return false;
return true;
}
}


Because Integer and String are both Object types. You need to give T some kind of more specific bounds. It has compile time type checking only. After type erasure it's always Object. 


Arrays and generics do not work well together. Also, arrays have a broken type system. That broken type system part, that explains why your code compiles here.

To explain what's broken about it, and why that then results in your code working, first, some background info is required.


Variance

In java, the types themselves are covariant. This means Whenever something of type A is required, anything of a subtype of A is also okay.


This works fine:

Object o = "hello";


Because "hello" is a string, and String is a subtype of Object, therefore this is okay.

But generics are invariantWhenever something of type A is required, only A will do.

Let's try the same construct in generics:


List<Object> o = new ArrayList<String>();

This is a compiler error: thus showing that generics start off invariant.

But, generics are interesting in that you can choose your variance. For example, if you want covariance, you can ask for this:


List<? extends Object> o = new ArrayList<String>();


That does compile: You now have a List of 'covariant Object'.

The reason that generics are invariant by default is because math / life / the universe. It is the correct way to do it. After all, imagine that generics were covariant like the basic types were, then you could write:


List<String> stringsOnly = new Arraylist<String>();
List<Object> objects = stringsOnly;
objects.add(5);
String string = stringsOnly.get(0); // but this is '5', a number!


The above code fortunately does not compile, because the second line won't, because generics are invariant. Opt into covariance and it still does not compile:

List<String> stringsOnly = new Arraylist<String>();
List<?> objects = stringsOnly;
objects.add(5);
String string = stringsOnly.get(0); // but this is '5', a number!

This time, the third line won't compile. You can't add anything to a List of covariant anything (well, except the literal null).

Arrays, unfortunately, breaks all this. Let's do the first snippet in array form:



String[] stringsOnly = new String[1];
Object[] objects = stringsOnly;
objects[0] = 5;
String string = stringsOnly[0];



This actually compiles. However, if you run it, you get an ArrayStoreException on the third line.

The reason your code does work, is:

T is bound to be Object. Which works: String[] and Integer[] are both valid, in that Object[] x = new String[10]; is valid java. This is mathematically unsound, as this last snippet showed, but java thinks it is okay.

As your arraysEqual method only reads, and never writes anything, you don't run into the ArrayStoreException.

I strongly recommend you actually write these snippets and experiment; variance is a hard concept, but fundamental to programming.



Arrays in java

Arrays are low level constructs, especially arrays of non-primitive types. You should not use them unless you have a performance reason (and you rarely should).

  • They have broken variance.
  • They will never be fixed (because there are better alternatives, and their problems are sometimes used by code that has performance requirements, so these problems cannot be fixed without breaking old code, and for the past 25 years, java has been highly resistant to making changes that break old code, and there are no indications this is going to change anytime soon).
  • They have broken hash and equals and toString implementations.
  • They cannot be coerced into having a read-only view or being immutable.
  • They cannot grow or shrink.

Use List<T> instead which suffers from none of these problems.


-----------------------------------------------------------------------------------------------------------------------------

Proble1:

package org.oracle;

public class MyClassIntegers {

Integer i;

public MyClassIntegers(Integer i) {
this.i = i;
}

Integer returnStuff() {
return i;
}
}
package org.oracle;

public class MyClassDouble {

Double d;

public MyClassDouble(Double d) {
this.d = d;
}

Double returnStuff() {
return d;
}
}
package org.oracle;

public class MyClassObject {
Object obj;

public MyClassObject(Object obj) {
this.obj = obj;
}

Object returnStuff() {
return obj;
}
}


Solution: We can create one class and it can take in different types of variables

package org.oracle;

public class MyClass<T> {
T ob;

public MyClass(T ob) {
this.ob = ob;
}

void showType() {
System.out.println(ob.getClass().getName());
}
}


Problem2:

package org.oracle;

public class NumberFns<T>{
T ob;

NumberFns(T ob) {
this.ob = ob;
}

double square() {
return ob * ob;
}
}

Solution: We can use bounded type

package org.oracle;

public class NumberFns<T extends Number>{
T ob;

NumberFns(T ob) {
this.ob = ob;
}

double square() {
return ob.intValue() * ob.doubleValue();
}
}
package org.oracle;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Main {
public static void main(String[] args) {
NumberFns<Integer> iob = new NumberFns<>(4);
System.out.println(iob.square());
}
}


Problem3:

package org.oracle;

public class NumberFns<T extends Number> { // upper bound
T num;

NumberFns(T num) {
this.num = num;
}

boolean absEqual(NumberFns<T> ob) {
if (Math.abs(num.doubleValue()) == Math.abs(ob.num.doubleValue()))
return true;
return false;
}
}
package org.oracle;

public class Main {
public static void main(String[] args) {
NumberFns<Integer> iob = new NumberFns<>(4);
NumberFns<Double> dob = new NumberFns<>(-4.0);
iob.absEqual(dob);
}
}

Solution:

package org.oracle;

public class NumberFns<T extends Number> {
T num;

NumberFns(T num) {
this.num = num;
}

boolean absEqual(NumberFns<?> ob) { // ? unbounded type
if (Math.abs(num.doubleValue()) == Math.abs(ob.num.doubleValue()))
return true;
return false;
}
}






























Комментарии

Популярные сообщения из этого блога

Lesson1: JDK, JVM, JRE

SE_21_Lesson_11: Inheritance, Polymorphism

SE_21_Lesson_9: Initialization Blocks, Wrapper types, String class