For generic classes and methods, only concrete types can be used: either primitive types or custom classes. This rigid constraint can be very restrictive if you want to write code that can be applied to multiple types. Sometimes, sticking to a single inheritance system can limit your application so much that you may not be able to improve it even if you use interfaces.

Generics realize the concept of parameterized types, allowing code to be applied to multiple types by decoupling the constraints between a class or method and the type being used.

There are many reasons for generics, most notably the creation of container classes. One of the main purpose of the generic type is used to specify the container to hold what kind of Object, instead of using the Object, we prefer to temporarily do not specify a type, but later decided to specific use what type, and by the compiler to ensure that the correct type (type “hold” is for generic understanding of core concepts, to determine its usage scenario).

In addition to applying generics to entire classes and interfaces, you can also include generic methods in your classes. Having a generic method doesn’t matter if the class you’re in is generic. Whenever you can, you should use generic methods, and if using generic methods can replace the whole class genericization, then just use generic methods. If static methods need to use generics capabilities, they must be made generic (after all, they are independent of runtime objects, so generics need to be used separately)

public class GenericMethods { public <T> void f(T x) { System.out.println(x.getClass().getName()): } public static void main(String[] args) { GenericMethods gm = new GenericMethods(); gm.f(""); gm.f(1); }}Copy the code

Type erasure

Erase to dragon

Because generics have not been a part of the Java language since its inception, the implementation is a compromise, and the core motivation for erasers is that it makes it possible for generalized clients to use non-generalized libraries, and vice versa. Java generics need to support backward compatibility: existing code and class files are still legal and retain their previous meaning, and they need to support migration compatibility: Causes libraries to become generic at their own pace, and when a library becomes generic it does not break the code and applications that depend on it, so evidence that a particular library uses generics must be erased.

Erase the performance

Inside the generic code, you can’t get any information about generic parameter types. When you use generics, any specific type information is erased, and the only thing you know is that you are using an object (the wording is a bit of an exaggeration, it is only erased inside the generic, but the internal and external invocation communication can still keep the type flexible and consistent). Generics cannot be used in operations that explicitly reference runtime types, such as transformations, instanceof operations, and new expressions, because all type information about parameters is lost.

pubilc class Erased<T> { private final int SIZE = 100; public static void f(Object arg) { // if (arg instanceof T) {} // T var = new T(); // T[] array = new T[SIZE]; }}Copy the code

Generic types appear only during static type checking, after which all generic types in the program are erased and replaced by their non-generic upper bounds. Type annotations such as List<T> are erased to List, and ordinary type variables are erased to Object without specifying boundaries.

public class HasF { public void f() { Sysytem.out.println("HasF.f()"): } } class Manipulator<T> { private T obj; public Manipulator(T x) { obj = x; } public void manipulate() { // Error: cannot find symbol: method f(); // obj.f() } } class Manipulator2<T extends HasF> { private T obj; public Manipulator2(T x) { obj = x; } public void manipulate() { obj.f(); } public T get() { return obj; }}Copy the code

To call f (), we have to assist in a generic class, given a generic class boundaries, to tell the compiler can only accept follow this boundary type, reuse the extends keyword here, control the boundary type erasure, erased tooling with common polymorphism makes no difference, but in fact the generic class internal control is a specific type, For example, you can return a specific T type.

Erase the compatible

Erasure and compatibility mean that the use of generics is not mandatory.

class GenericBase<T> { private T element; public void set(T arg) { element = arg; } public T get() { return element; } } class Derived1<T> extends GenericBase<T> {} class Derived2 extends GenericBase {} // No warning // class Derived3 extends GenericBase<? > {} // required: class or interface without bounds public class ErasureAndInheritance { @SuppressWarnings("uncheck") public static void main(String[] args) { Derived2 d2 = new Derived2(); Object obj = d2.get(); d2.set(obj); // Warning here } }Copy the code

Erase the implementation

Because erasing removes type information in the method body, the problem at run time is boundaries: the places where objects enter and leave the method, and these are the places where the compiler performs type checking and inserts transition code at compile time.

For set(), Java itself supports upscaling, and the compiler mainly checks for boundary validity; In the case of GET (), the downward transformation of the returned value is additional work that the compiler does automatically.

The border

Boundaries and Inheritance

Java generics reuse the extends keyword, which has a different meaning in the generic boundary context than it does in the ordinary case. Multiple extensions are also supported here.

interface HasColor { java.awt.Color getColor(); } class Dimension { public int x, y, z; } // class must be first, then interfaces class ColoredDimesion<T extends Dimesion & HasColor> { T item; ColoredDimesion(T item) { this.item = item; } T getItem() { return item; } java.awt.Color color() { return item.getColor(); } int getX() { return item.x; } int getY() { return item.y; } int getZ() { return item.z; } } interface Weight { int weight(); } class Solid<T extends Dimesion & HasColor & Weight> { T item; Solid(T item) { this.item = item; } T getItem() { return item; } java.awt.Color color() { return item.getColor(); } int getX() { return item.x; } int getY() { return item.y; } int getZ() { return item.z; } int weight() { return item.weight(); }}Copy the code

Inheritance and boundaries can coexist, and base classes and inherited classes need to be associated on generic parameters.

class HoldItem<T> { T item; HoldItem(T item) { this.item = item; } T getItem() { return item; } } class Colored2<T extends HasColor> extends HoldItem<T> { Colored2(T item) { super(item); } java.awt.Color color() { return item.getColor(); }}Copy the code

A List<Fruit> reference can’t refer to a List<Apple> object. After all, List only declares a T. Fruit and Apple are not the same T. From the point of view of holding logic, although all apples are “Fruit”, not all apples are “Apple”. An Orange is not allowed to be added to an Apple container.

As with any other syntax, multiple inheritance in generics can have conflicts that need to be reconciled. A class cannot have two variants of the same generic interface. Because of erasing, these two variants become the same interface, meaning that implementing the same interface twice will fail compilation. But in normal, non-generic versions this does work, and the compiler can safely help us remerge without type parameter conflicts.

interface Payable<T> {}

class Employee implements Payable<Employee> {}
class Hourly extends Employ implements Payable<Hourly> {}
Copy the code

Border cut

If the caller uses a wildcard to lower the generic reference boundary, the assignment is limited by the compiler. In <? With extends T> generics, the compiler only knows that the type it holds is a subclass of T, but it has no way of knowing which subclass it is, so it intercepts everything at the set() entry, except for Object, whereas at the get() exit it is safe to go up to T because it is explicitly a subclass of T. (Perhaps this scenario is mainly used for entry security control. If you want to open entry inheritance, you don’t have to raise the generic boundary at all, just use type parameter polymorphism.)

class Fruit {} interface Eat {} class Apple extends Fruit implements Eat {} class Jonathan extends Apple {} class Orange  extends Fruit {} public class Holder<T> { private T value; public Holder() {} public Holder(T t) { value = t; } public void set(T val) { value = val; } public T get() { return value; } public boolean equals(Object obj) { return value.equals(obj); } public staic void main(String[] args) { Holder<Apple> apple = new Holder<>(new Apple()); Apple d = apple.get(); // Holder<Fruit> fruit = apple; // cannot upcast Holder<? extends Fruit> fruit = apple; Fruit p = fruit.get(); d = (Apple) fruit.get(); try { Orange c = (Orange) fruit.get(); } catch(Exception e) { // fruit.set(new Apple()); // cannot call set // fruit.set(new Fruit()); // cannot call set System.out.println(fruit.equals(d)); }}}Copy the code

Boundary increases

In addition to using wildcards to lower generic reference boundaries, you can also do the opposite. In
generic, any superclass of T (base class or interface, because of multiple inheritance, not pure single-inheritance) can receive an object of T or its subclasses (cascading upward). However, you can only get Object when you get(), because you can’t determine which superclass the generic holds, and you can’t directly cast it down.

public class GenericWriting { staitc List<Fruit> fruits = new ArrayList<>(); staitc List<Eat> eats = new ArrayList<>(); static <T> void writeWithWildcard(List<? super T> list, T item) { list.add(item): Object obj = list.get(0); } static void f() { writeWithWildcard(fruits, new Fruit()); writeWithWildcard(fruits, new Apple()); writeWithWildcard(fruits, new Jonathan()); writeWithWildcard(fruits, new Orange()); // writeWithWildcard(eats, new Fruit()); writeWithWildcard(eats, new Apple()); writeWithWildcard(eats, new Jonathan()); // writeWithWildcard(eats, new Orange()); }}Copy the code

In invocation comprehension, replace the type parameter of the generic argument with the? In the method generic parameter. , the specific type of T, to determine whether the compiler meets the boundary, and in return need to note that Java naturally supports upward transformation, so the external may receive with a more generic type reference.

Unbounded wildcard

Unbounded wildcard <? > seems to mean “anything,” so using unbounded wildcards seems equivalent to using native types. In effect, it declares: “I want to write this code using Java generics. I’m not using native types here, but in this case, generic parameters can hold any type.”

Since the generic argument is erased to its first boundary, the List<? > looks equivalent to List<Object>, which actually stands for “holding any native List of type Object”, whereas List<? > means “non-native List of some particular type,” except we don’t know what that type is.

public class UnboundedWildCards1 { static List list1; static List<? > list2; static List<? extends Object> list3; static void assign1(List list) { list1 = list; list2 = list; list3 = list; // Warning: Unchecked assignment: 'java.util.List' to 'java.util.List<? extends java.lang.Object>' } static void assign2(List<? > list) { list1 = list; list2 = list; list3 = list; } static void assign3(List<? extends Object> list) { list1 = list; list2 = list; list3 = list; } public static void main(String[] args) { assign1(new ArrayList()); assign2(new ArrayList()); assign3(new ArrayList()); // Warning: Unchecked assignment: 'java.util.List' to 'java.util.List<? extends java.lang.Object>' assign1(new ArrayList<String>()); assign2(new ArrayList<String>()); assign3(new ArrayList<String>()); List<? > wildList = new ArrayList<String>(); assign1(wildList); assign1(wildList); assign1(wildList); List list4 = new ArrayList(); list4.add("s"); Object o = list4.get(0); List<? > list5 = new ArrayList<String>(); // list5.add("s"); // Error: Required type: capture of ? Provided: String Object o = list5.get(0); }}Copy the code

From the difference between list4 and list5 performance, here? And the front of the border down? Extends T also has write security properties.

The advantage of using exact types instead of wildcard types is that you can do more with generic parameters, but using wildcards forces you to accept a wider range of parameterized types as parameters, so you have to weigh the pros and cons on a case-by-case basis to find an approach that best suits your needs.

Since the limit

There’s a usage that looks like a nesting doll

class SelfBounded<T extends SelfBounded<T>> {}
Copy the code

SelfBounded accepts a generic parameter T, and has a boundary constraint on T, that is, T needs to inherit SelfBounded. The essence of this is that the base class replaces its arguments with an derived class, which means that the generic base class becomes a template for the common functionality of all its derived classes, which will use the derived types for all of its arguments and return values.

class SelfBounded<T extends SelfBounded<T>> {
    T element;
    SelfBounded<T> set(T arg) {
        element = arg;
        return this;
    }
    T get() {
        return element;
    }
}

class A extends SelfBounded<A> {}
class B extends SelfBounded<A> {} // it's ok

class C extends SelfBounded<C> {
    C setAndGet(C arg) {
        set(arg);
        return get();
    }
}

class D {}
// class E extends SelfBounded<D> {} // Error

class F extends SelfBounded {}
Copy the code

From the analysis of compilation grammar, SelfBounded

refers to the generic class SelfBounded

holding the type parameter T. As can be seen from the return value of set method, T extends SelfBounded

defines T to inherit SelfBounded as described above. Therefore, the first T is used to define the boundary of T, and the second T indicates the type parameters already held by the generic class. Collectively, T needs to inherit a SelfBounded person who holds T as a type parameter. In this way, the interpretation can be freed from infinite dolls. As for why B is legal, since A has realized self-limiting, SelfBounded only limited its parameter type T, but not its derived classes. The birth of these qualified T just happens to be the declaration of the class.


Since SelfBounded clearly holds T, even if T inherits SelfBounded, polymorphism cannot be applied to treat SelfBounded as T, which is a clear feature of generic types.

A generic Class

It is possible to declare arrayList. class, but not ArrayList<Integer>. Class. ArrayList<String> and ArrayList<Integer> can easily be thought of as different types, but in fact they are the same type.

public class ErasedTypeEquivalence {
    public static void main(String[] args) {
        Class c1 = new ArrayList<String>().getClass();
        Class c2 = new ArrayList<Integer>().getClass();
        System.out.println(c1 == c2); // return true
    }
}
Copy the code

Class.gettypeparameters () returns an array of TypeVariable objects representing the type parameters declared by the generic declaration, but returns only the identifiers T, E, K, and V used as parameter placeholders. (This also prevents some generic classes from dynamically deserializing their types. Because identifiers have no practical use for serialization).

Sometimes you have to introduce type tags to compensate for erasures, which means you need to explicitly pass the Class object of your type so that you can use it in type expressions, and the compiler will make sure that the type tags match the generic arguments (sure enough, Class is treated differently).

class Building {} class House extends Building {} public class ClassTypeCaptute<T> { Class<T> kind; public ClassTypeCapture(Class<T> kind) { this.kind = kind; } public boolean f(Object arg) { return kind.isInstance(arg); } public static void main(String[] args) { ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<Building>(Building.class); System.out.println(ctt1.f(new Building()); System.out.println(ctt1.f(new House()); ClassTypeCapture<Building> ctt2 = new ClassTypeCapture<House>(House.class); System.out.println(ctt2.f(new Building()); System.out.println(ctt2.f(new House()); }}Copy the code

Generic array

Erased in Java, there is no way to create generic arrays, but the general solution is to use ArrayList everywhere you want to create a generic array.

summary

Java generics as a Java language structure is the most difficult to chew bones, I spent a weekend to revisit and wrote the article, write here, this column for Java language core features basically come to an end, the back of the container, enumeration, annotations, concurrency, IO main partial tool apis, like deep and remote states fighters south of the north China plain, the basic can penetrate. Ha ha.