Home > Java > javaTutorial > body text

A detailed explanation of Java generics

巴扎黑
Release: 2017-04-08 11:51:48
Original
1635 people have browsed it

Introduction

Generics are a very important knowledge point in Java. Generics are widely used in the Java collection class framework. In this article, we will look at the design of Java generics from scratch, which will involve wildcard processing and annoying type erasure.

Generic basics

Generic class

We first define a simple Box class:

public class Box {
    private String object;
    public void set(String object) { this.object = object; }
    public String get() { return object; }
}
Copy after login

This is the most common approach. A disadvantage of this is that only String type elements can be loaded into Box now. If we need to load other types of elements such as Integer in the future, we must rewrite another Box, and the code will be complicated. When it comes to reuse, using generics can solve this problem very well.

public class Box<T> {
    // T stands for "Type"
    private T t;
    public void set(T t) { this.t = t; }
    public T get() { return t; }
}
Copy after login

In this way, our Box class can be reused, and we can replace T with any type we want:

Box<Integer> integerBox = new Box<Integer>();
Box<Double> doubleBox = new Box<Double>();
Box<String> stringBox = new Box<String>();
Copy after login

Generic method

After reading about generic classes, let’s take a look at generic methods. Declaring a generic method is very simple, just add a form like in front of the return type:

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());
    }
}
public class Pair<K, V> {
    private K key;
    private V value;
    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }
    public K getKey()   { return key; }
    public V getValue() { return value; }
}
Copy after login

We can call generic methods like this:

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);
Copy after login

Or use type inference in Java1.7/1.8 to let Java automatically deduce the corresponding type parameters:

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.compare(p1, p2);
Copy after login

Boundary character

Now we want to implement such a function to find the number of elements in a generic array that is greater than a specific element. We can implement it like this:

public static <T> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e > elem)  // compiler error
            ++count;
    return count;
}
Copy after login

But this is obviously wrong, because except for primitive types such as short, int, double, long, float, byte, char, etc., other classes may not be able to use the operator >, so the compiler reports an error, so how to solve this problem Woolen cloth? The answer is to use delimiters.

public interface Comparable<T> {
    public int compareTo(T o);
}
Copy after login

Making a statement similar to the following is equivalent to telling the compiler that the type parameter T represents classes that implement the Comparable interface, which is equivalent to telling the compiler that they all implement at least the compareTo method.

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

Wildcard

Before understanding wildcards, we must first clarify a concept, or borrow the Box class we defined above, assuming we add a method like this:

public void boxTest(Box<Number> n) { /* ... */ }
Copy after login

So what types of parameters does Box n allow to accept now? Can we pass in Box or Box? The answer is no. Although Integer and Double are subclasses of Number, there is no relationship between Box or Box and Box in generics. This is very important. Let’s deepen our understanding through a complete example.

First we define a few simple classes, which we will use below:

class Fruit {}
class Apple extends Fruit {}
class Orange extends Fruit {}
Copy after login

In the following example, we create a generic class Reader, and then in f1() when we try Fruit f = fruitReader.readExact(apples); the compiler will report an error because there is a gap between List and List It doesn't have any relationship.

public class GenericReading {
    static List<Apple> apples = Arrays.asList(new Apple());
    static List<Fruit> fruit = Arrays.asList(new Fruit());
    static class Reader<T> {
        T readExact(List<T> list) {
            return list.get(0);
        }
    }
    static void f1() {
        Reader<Fruit> fruitReader = new Reader<Fruit>();
        // Errors: List<Fruit> cannot be applied to List<Apple>.
        // Fruit f = fruitReader.readExact(apples);
    }
    public static void main(String[] args) {
        f1();
    }
}
Copy after login

But according to our usual thinking habits, there must be a connection between Apple and Fruit, but the compiler cannot recognize it. So how to solve this problem in generic code? We can solve this problem by using wildcard characters:

static class CovariantReader<T> {
    T readCovariant(List<? extends T> list) {
        return list.get(0);
    }
}
static void f2() {
    CovariantReader<Fruit> fruitReader = new CovariantReader<Fruit>();
    Fruit f = fruitReader.readCovariant(fruit);
    Fruit a = fruitReader.readCovariant(apples);
}
public static void main(String[] args) {
    f2();
}
Copy after login

This is equivalent to telling the compiler that the parameters accepted by the readCovariant method of fruitReader only need to be a subclass of Fruit (including Fruit itself), so that the relationship between the subclass and the parent class is also related.

PECS principle

Above we saw usage similar to , using which we can get elements from the list, so can we add elements to the list? Let’s try it:

public class GenericsAndCovariance {
    public static void main(String[] args) {
        // Wildcards allow covariance:
        List<? extends Fruit> flist = new ArrayList<Apple>();
        // Compile Error: can&#39;t add any type of object:
        // flist.add(new Apple())
        // flist.add(new Orange())
        // flist.add(new Fruit())
        // flist.add(new Object())
        flist.add(null); // Legal but uninteresting
        // We Know that it returns at least Fruit:
        Fruit f = flist.get(0);
    }
}
Copy after login

The answer is no, the Java compiler does not allow us to do this, why? We might as well consider this issue from the perspective of the compiler. Because List flist itself can have multiple meanings:

List<? extends Fruit> flist = new ArrayList<Fruit>();
List<? extends Fruit> flist = new ArrayList<Apple>();
List<? extends Fruit> flist = new ArrayList<Orange>();
Copy after login