Generic basis

We’ll start by defining a simple Container class:

public class Container { private String object; public void set(String object) { this.object = object; } public String get() { return object; }}Copy the code

This is the most common approach. The downside of this approach is that only String elements can be loaded into a Container. In the future, if we need to load elements of other types such as Integer, we will have to rewrite another Container.

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

So that our Container class can be reused, we can replace T with whatever type we want:

Container<Integer> integerContainer = new Container<Integer>();
Container<Double> doubleContainer = new Container<Double>();
Container<String> stringContainer = newContainer<String>();
Copy the code

Having looked at generic classes, let’s take a look at generic methods. Declaring a generic method is as simple as prefixing the return type with a form like

:
,>

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 the code

We can call a generic method 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 the code

Or after Java1.7, we can use type inference to make Java automatically derive 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 the code

Generic interfaces Generic interfaces are defined and used in much the same way as generic classes. Generic interfaces are often used in producers of various classes. Here is an example:

Public interface Generator<T> {public T next(); }Copy the code

When a class that implements a generic interface passes no generic arguments:

/* FruitGenerator<T> implements Generator<T>{* FruitGenerator<T> implements Generator<T>{* FruitGenerator<T> implements Generator<T>{* FruitGenerator<T> implements Generator<T>{ FruitGenerator implements Generator<T> "Unknown class" */ class FruitGenerator<T> implements Generator<T>{ @Override public T next() { return null; }}Copy the code

When a class that implements a generic interface passes in a generic argument:

/** * When passing generic arguments: * Define a producer to implement this interface. Although we only created a generic interface Generator<T> * we can pass in an infinite number of arguments to T, forming an infinite number of types of Generator interfaces. * When an implementation class implements a generic interface, if the generic type has been passed to the argument type, all uses of the generic type should be replaced with the passed argument type * i.e. Generator<T>, public T next(); The T in is replaced by the String passed in. */ public class FruitGenerator implements Generator<String> { private String[] fruits = new String[]{"Apple", "Banana", "Pear"}; @Override public String next() { Random rand = new Random(); return fruits[rand.nextInt(3)]; }}Copy the code

Boundary operator

Now we want to implement a function to find the number of elements in a generic array that are greater than a particular 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 the code

But this is obviously wrong, for in addition to the short, int, double, long, float, byte, char primitive types, such as other classes is not necessarily can use operators >, so the compiler error, and how to solve this problem? The answer is to use a boundary character.

public interface Comparable<T> {
    public int compareTo(T o);
}
Copy the code

Making a declaration like the following tells the compiler that the type parameter T represents classes that implement the Comparable interface, which tells 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 the code

The wildcard

Before we get to wildcards, we need to clarify a concept. Again, using the Container class we defined above, suppose we add a method like this:

public void boxTest(Container<Number> n) { /* ... */ }
Copy the code

So what types of arguments are Container N allowed to accept now? Can we pass in a Container or Container? The answer is no. Although Integer and Double are subclasses of Number, there is no relationship between containers or between containers in the generic. This point is very important, so let’s go through a complete example to further understand it.

Let’s start by defining a few simple classes that we’ll use:

class Fruit {}
class Apple extends Fruit {}
class Orange extends Fruit {}
Copy the code

In the following example, we create a generic Reader class, and then in f1() when we try Fruit f = fruitreader.readexact (apples); The compiler will get an error because there is no relationship between lists.

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 the code

But if there’s a connection between Apple and Fruit that the compiler can’t recognize, how do you solve this problem in generic code? We can solve this problem by using wildcards:

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 the code

FruitReader’s readCovariant method accepts arguments that are subclasses of Fruit (including Fruit itself), and the relationship between subclasses and their parents is related.

The principle of PECS

Above we see something like <? Extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T 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'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 the code

The answer is no, the compiler won’t let us do that. Why? We can consider this problem from the perspective of the compiler. Because the List <? Extends Fruit> flist extends Fruit> flist itself can have several meanings:

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

When we try to add an Apple, flist might point to new ArrayList (); When we try to add an Orange, flist might point to new ArrayList (); So when we try to add a Fruit, it could be any type of Fruit, and flist might only want a certain type of Fruit, and the compiler won’t recognize it and will get an error. So for implementing the
treats it only as a Producer providing elements (GET) and not as a Consumer getting elements (Add).

So what do we do if we want to add elements? You can use <? Super T > :

public class GenericWriting { static List<Apple> apples = new ArrayList<Apple>(); static List<Fruit> fruit = new ArrayList<Fruit>(); static <T> void writeExact(List<T> list, T item) { list.add(item); } static void f1() { writeExact(apples, new Apple()); writeExact(fruit, new Apple()); } static <T> void writeWithWildcard(List<? super T> list, T item) { list.add(item) } static void f2() { writeWithWildcard(apples, new Apple()); writeWithWildcard(fruit, new Apple()); } public static void main(String[] args) { f1(); f2(); }}Copy the code

So we can add elements to the container, but the downside of using super is that we can’t get the elements in the container in the future, and the reason for that is very simple. Let’s continue to think about this from the compiler’s point of view. Super Apple> list, which can have the following meanings:

List<? super Apple> list = new ArrayList<Apple>();
List<? super Apple> list = new ArrayList<Fruit>();
List<? super Apple> list = new ArrayList<Object>();
Copy the code

When we try to get an Apple through the list, we might get a “Fruit”, which could be Orange or other types of Fruit.

Based on the above example, we can conclude that Producer Extends, Consumer Super:

“Producer Extends” — If you need a read-only List to produce T, use
. “Consumer Super” — If you need a write-only List to consume T, use
. If we need to read and write at the same time, we can’t use wildcards.

hack

If you read the source code for some Java collection classes, you can see that the two are sometimes used together, as follows:

public class Collections { public static <T> void copy(List<? super T> dest, List<? extends T> src) { for (int i=0; i<src.size(); i++) dest.set(i, src.get(i)); }}Copy the code

So if you still want to break the rules and write and read at the same time, you can use the method above: examples

public class PECSTest { private List<? extends Father> glist = new ArrayList<>(); public static void main(String[] args) { PECSTest pecsTest = new PECSTest(); pecsTest.test(); } private void test() { List<Father> list = new ArrayList<>(); Collections.copy(list, this.glist); list.add(new Father("father")); List. add(new Child(23)); // Add a subclass glist = new ArrayList<>(list); System.out.println(glist); } class Father {public String name; public Father(String name) { this.name = name; } @Override public String toString() { return "Father{" + "name='" + name + '\'' + '}'; } } class Child extends Father { public int age; public Child(int age) { super("child"); this.age = age; } @Override public String toString() { return "Child{" + "name='" + name + '\'' + ", age=" + age + '}'; }}}Copy the code

The output

[Father{name='father'}, Child{name='child', age=23}]
Copy the code