: Notebook: This article is filed under “blog”

: Keyboard: The sample code in this article is archived under: “Javacore”

Why generics

JDK5 introduces generics.

Why generics? Before we answer that question, let’s look at an example.

public class NoGenericsDemo {
    public static void main(String[] args) {
        List list = new ArrayList<>();
        list.add("abc");
        list.add(18);
        list.add(new double[] {1.0.2.0});
        Object obj1 = list.get(0);
        Object obj2 = list.get(1);
        Object obj3 = list.get(2);
        System.out.println("obj1 = [" + obj1 + "]");
        System.out.println("obj2 = [" + obj2 + "]");
        System.out.println("obj3 = [" + obj3 + "]");

        int num1 = (int)list.get(0);
        int num2 = (int)list.get(1);
        int num3 = (int)list.get(2);
        System.out.println("num1 = [" + num1 + "]");
        System.out.println("num2 = [" + num2 + "]");
        System.out.println("num3 = [" + num3 + "]"); }}// Output:
// obj1 = [abc]
// obj2 = [18]
// obj3 = [[D@47089e5f]
// Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
// at io.github.dunwu.javacore.generics.NoGenericsDemo.main(NoGenericsDemo.java:23)
Copy the code

Example description:

In the above example, the List container does not specify a data type to store. In this case, any type of data can be added to the List, and the compiler does no type checking. Instead, it silently converts all data to Object.

Let’s say, initially, what we want to store to the List is orthopedic data, and let’s say, some guy accidentally stores some other data type. When you try to fetch integer data from a container, you have to use type casting because the List is stored as an Object. At runtime, the problem of inconsistent data storage in the List is discovered, which brings great risk to the program running (invisible damage is the most deadly).

The emergence of generics solves the problem of type safety.

Generics have the following advantages:

  • Strong type checking at compile time

Generics require that the actual data types be specified at declaration time, and the Java compiler performs strong type checking on generic code during compilation and raises alarms when code violates type safety. Early detection, early treatment, and early prevention of potential problems, at compile time to find and fix errors is far less costly than at run time.

  • Type conversions are avoided

Not using generics:

List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
Copy the code

Using generics:

List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0);   // no cast
Copy the code
  • Generic programming can implement common algorithms

By using generics, programmers can implement common algorithms that can handle collections of different types, can be customized, and are type-safe and easy to read.

The generic type

The generic typeIs a class or interface that is parameterized.

A generic class

Generic class syntax:

class name<T1.T2. .Tn> { / *... * / }
Copy the code

The declaration of generic classes is similar to that of non-generic classes, except that a type parameter declaration section is added after the class name. The type parameter portion, separated by Angle brackets (<>), follows the class name. It specifies type parameters (also known as type variables) T1, T2… And Tn.

Class names in generics are typically called stereotypes, and parameters specified by <> are called type parameters.

  • Classes that do not apply generics

Before generics, if a class wanted to hold data of any type, it could only use Object for type conversion. The following is an example:

public class Info {
	private Object value;

	public Object getValue(a) {
		return value;
	}

	public void setValue(Object value) {
		this.value = value; }}Copy the code
  • Generic classes for single-type parameters
public class Info<T> {
    private T value;

    public Info(a) {}public Info(T value) {
        this.value = value;
    }

    public T getValue(a) {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }

    @Override
    public String toString(a) {
        return "Info{" + "value=" + value + '} '; }}public class GenericsClassDemo01 {
    public static void main(String[] args) {
        Info<Integer> info = new Info<>();
        info.setValue(10);
        System.out.println(info.getValue());

        Info<String> info2 = new Info<>();
        info2.setValue("xyz"); System.out.println(info2.getValue()); }}// Output:
/ / 10
// xyz
Copy the code

In the above example, when initializing a generic class, <> is used to specify the internal concrete type against which strong type checking is performed at compile time.

In fact, it is syntactically supported (and not recommended) not to use <> to specify internal concrete types, as follows:

public static void main(String[] args) {
    Info info = new Info();
    info.setValue(10);
    System.out.println(info.getValue());
    info.setValue("abc");
    System.out.println(info.getValue());
}
Copy the code

Example description:

The above example will run without compiling errors. But such calls lose the advantage of generic types.

  • A generic class with multiple type parameters
public class MyMap<K.V> {
    private K key;
    private V value;

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

    @Override
    public String toString(a) {
        return "MyMap{" + "key=" + key + ", value=" + value + '} '; }}public class GenericsClassDemo02 {
    public static void main(String[] args) {
        MyMap<Integer, String> map = new MyMap<>(1."one"); System.out.println(map); }}// Output:
// MyMap{key=1, value=one}
Copy the code
  • Type nesting of generic classes
public class GenericsClassDemo03 {
    public static void main(String[] args) {
        Info<String> info = new Info("Hello");
        MyMap<Integer, Info<String>> map = new MyMap<>(1, info); System.out.println(map); }}// Output:
// MyMap{key=1, value=Info{value=Hello}}
Copy the code

A generic interface

Interfaces can also declare generics.

Generic interface syntax form:

public interface Content<T> {
    T text(a);
}
Copy the code

Generic interfaces can be implemented in two ways:

  • Subclasses that implement interfaces explicitly declare generic types


public class GenericsInterfaceDemo01 implements Content<Integer> {
    private int text;

    public GenericsInterfaceDemo01(int text) {
        this.text = text;
    }

    @Override
    public Integer text(a) { return text; }

    public static void main(String[] args) {
        GenericsInterfaceDemo01 demo = new GenericsInterfaceDemo01(10); System.out.print(demo.text()); }}// Output:
/ / 10
Copy the code
  • Subclasses that implement interfaces do not explicitly declare generic types
public class GenericsInterfaceDemo02<T> implements Content<T> {
    private T text;

    public GenericsInterfaceDemo02(T text) {
        this.text = text;
    }

    @Override
    public T text(a) { return text; }

    public static void main(String[] args) {
        GenericsInterfaceDemo02<String> gen = new GenericsInterfaceDemo02<>("ABC"); System.out.print(gen.text()); }}// Output:
// ABC
Copy the code

Generic method

Generic methods are methods that introduce their own type parameters. Generic methods can be normal methods, static methods, and constructors.

Generic methods have the following syntax:

public <T> T func(T obj) {}
Copy the code

Whether or not you have a generic method doesn’t matter.

The syntax of a generic method includes a list of type parameters, in Angle brackets, that appear before the method’s return type. For static generic methods, the type parameter part must appear before the method return type. Type parameters can be used to declare return value types and can serve as placeholders for the actual type parameters derived from the generic method.

When using generic methods, it is usually not necessary to specify type parameters, because the compiler will figure out the type for us. This is called type argument inference. Type inference only works for assignment operations, not otherwise. If the result of a generic method call is passed as an argument to another method, the compiler does not perform inference. The compiler thinks that after calling a generic method, the return value is assigned to a variable of type Object.

public class GenericsMethodDemo01 {
    public static <T> void printClass(T obj) {
        System.out.println(obj.getClass().toString());
    }

    public static void main(String[] args) {
        printClass("abc");
        printClass(10); }}// Output:
// class java.lang.String
// class java.lang.Integer
Copy the code

Mutable argument lists can also be used in generic methods

public class GenericVarargsMethodDemo {
    public static <T> List<T> makeList(T... args) {
        List<T> result = new ArrayList<T>();
        Collections.addAll(result, args);
        return result;
    }

    public static void main(String[] args) {
        List<String> ls = makeList("A");
        System.out.println(ls);
        ls = makeList("A"."B"."C"); System.out.println(ls); }}// Output:
// [A]
// [A, B, C]
Copy the code

Type erasure

The Java language introduced generics to provide stricter type checking at compile time and to support generic programming. Unlike C++ ‘s templating mechanism, Java generics are implemented using type erasure, in which any specific type information is erased.

So what does type erasure do? It does the following:

  • Replaces all type parameters in a generic type with Object, using a type boundary if specified. Therefore, the generated bytecode contains only ordinary classes, interfaces, and methods.
  • Erasing the occurrence of a type declaration, that is, removing it<>The content of the. Such asT get()The method declaration becomesObject get()List<String>Becomes aList. If necessary, type conversions are inserted to preserve type safety.
  • Bridge methods are generated to preserve polymorphism in extended generic types. Type erasure ensures that no new classes are created for parameterized types; Therefore, generics incur no runtime overhead.

Let’s look at an example:

public class GenericsErasureTypeDemo {
    public static void main(String[] args) {
        List<Object> list1 = new ArrayList<Object>();
        List<String> list2 = newArrayList<String>(); System.out.println(list1.getClass()); System.out.println(list2.getClass()); }}// Output:
// class java.util.ArrayList
// class java.util.ArrayList
Copy the code

Example description:

In the example above, the class information for list1 and List2 is the same, although different type parameters are specified.

This is because, with generics, any specific type information is erased. This means that ArrayListand ArrayList

are treated as the same type by the JVM at runtime.

Java generics are not implemented in a very elegant way, but this is because generics were introduced in JDK5, and some design compromises had to be made to make them compatible with older code.

Generics and inheritance

Generics cannot be used in operations that explicitly reference runtime types, such as transformations, instanceof operations, and new expressions. Because all the type information about the parameter is lost. When writing generic code, you must always remind yourself that you only appear to have type information about parameters.

Because generics are implemented based on type erasure, generic types cannot be cast upward.

Upcasting refers to initializing a parent class with a subclass instance, which is an important representation of polymorphism in object orientation.

Integer inherits Object; ArrayList inherits List; But List

does not inherit from List
.

This is because generic classes do not have their own Class objects. For example, there is no List. Class or List

. Class; the Java compiler treats both as list.class.

List<Integer> list = new ArrayList<>();
List<Object> list2 = list; // Erorr
Copy the code

Type boundary

Sometimes you may want to restrict the types that can be used as type parameters in parameterized types. Type boundaries can set constraints on type parameters of generics. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses.

To declare bounded type parameters, list the name of the type parameter, followed by the extends keyword, followed by its limiting class or interface.

The syntax for a type boundary is as follows:

<T extends XXX>
Copy the code

Example:

public class GenericsExtendsDemo01 {
    static <T extends Comparable<T>> T max(T x, T y, T z) {
        T max = x; // Suppose x is the initial maximum
        if (y.compareTo(max) > 0) {
            max = y; / / y is greater
        }
        if (z.compareTo(max) > 0) {
            max = z; // Now z is bigger
        }
        return max; // Return the largest object
    }

    public static void main(String[] args) {
        System.out.println(max(3.4.5));
        System.out.println(max(6.6.8.8.7.7));
        System.out.println(max("pear"."apple"."orange")); }}// Output:
/ / 5
/ / 8.8
// pear
Copy the code

Example description:

The above example declares a generic method, and the type parameter T extends Comparable

indicates that the type in the incoming method must implement the Comparable interface.

Multiple type boundaries can be set. The syntax is as follows:

<T extends B1 & B2 & B3>
Copy the code

Note: The first type argument after the extends keyword can be a class or an interface; the other type arguments can only be interfaces.

Example:

public class GenericsExtendsDemo02 {
    static class A { / *... * / }
    interface B { / *... * / }
    interface C { / *... * / }
    static class D1 <T extends A & B & C> { / *... * / }
    static class D2 <T extends B & A & C> { / *... * / } // Compile error
    static class E extends A implements B.C { / *... * / }

    public static void main(String[] args) {
        D1<E> demo1 = new D1<>();
        System.out.println(demo1.getClass().toString());
        D1<String> demo2 = new D1<>(); // Compile error}}Copy the code

Type wildcard

The type wildcard is usually used with? Instead of a specific type parameter. For example the List
is the logical parent of List

, List

, and all List< concrete type arguments >.

Upper bound wildcard

You can use the ** upper bound wildcard ** to narrow the type range of a type parameter.

Its syntax is:

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

    public static void main(String[] args) {
        List<Integer> li = Arrays.asList(1.2.3);
        System.out.println("sum = "+ sumOfList(li)); }}// Output:
/ / sum = 6.0
Copy the code

Lower bound wildcard

** The lower-bound wildcard ** restricts an unknown type to a specific type or superclass type of that type.

Note: Upper and lower wildcards cannot be used at the same time.

Its syntax is:

public class GenericsLowerBoundedWildcardDemo {
    public static void addNumbers(List<? super Integer> list) {
        for (int i = 1; i <= 5; i++) { list.add(i); }}public static void main(String[] args) {
        List<Integer> list = newArrayList<>(); addNumbers(list); System.out.println(Arrays.deepToString(list.toArray())); }}// Output:
// [1, 2, 3, 4, 5]
Copy the code

Unbounded wildcard

Unbounded wildcards can be used in two scenarios:

  • Methods that can be implemented using functionality provided in the Object class.
  • Use methods in generic classes that do not depend on type parameters.

Grammatical form:

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

    public static void main(String[] args) {
        List<Integer> li = Arrays.asList(1.2.3);
        List<String> ls = Arrays.asList("one"."two"."three"); printList(li); printList(ls); }}// Output:
/ / 1 2 3
// one two three
Copy the code

Wildcards and upward transitions

Earlier, we mentioned that generics cannot be upturned. However, we can transition up by using wildcards.

public class GenericsWildcardDemo {
    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        List<Number> numList = intList;  // Error

        List<? extends Integer> intList2 = new ArrayList<>();
        List<? extends Number> numList2 = intList2;  // OK}}Copy the code

Further reading: Oracle generics documentation

Constraints on generics

  • The type parameters of a generic type cannot be value types
Pair<int.char> p = new Pair<>(8.'a');  // Error compiling
Copy the code
  • Cannot create an instance of a type parameter
public static <E> void append(List<E> list) {
    E elem = new E();  // Error compiling
    list.add(elem);
}
Copy the code
  • Static members of type parameters cannot be declared
public class MobileDevice<T> {
    private static T os; // error

    // ...
}
Copy the code
  • Type parameters cannot use type conversion orinstanceof
public static <E> void rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) {  // Error compiling
        // ...}}Copy the code
List<Integer> li = new ArrayList<>();
List<Number>  ln = (List<Number>) li;  // Error compiling
Copy the code
  • Cannot create an array of type parameters
List<Integer>[] arrayOfLists = new List<Integer>[2];  // Error compiling
Copy the code
  • You cannot create, catch, or throw parameterized type objects
// Extends Throwable indirectly
class MathException<T> extends Exception { / *... * / }    // Error compiling

// Extends Throwable directly
class QueueFullException<T> extends Throwable { / *... * / // Error compiling
Copy the code
public static <T extends Exception, J> void execute(List<J> jobs) {
    try {
        for (J job : jobs)
            // ...
    } catch (T e) {   // compile-time error
        // ...}}Copy the code
  • Methods that only have the same generic class but different type parameters cannot be overloaded
public class Example {
    public void print(Set<String> strSet) {}public void print(Set<Integer> intSet) {}// Error compiling
}
Copy the code

Generic best practices

The generic name

Generic types have some common names:

  • E – Element
  • K – Key
  • N – Number
  • T – Type
  • V – Value
  • S,U,V etc. – 2nd, 3rd, 4th types

Recommendations for using generics

  • Clear the type check alarm
  • List takes precedence over array
  • Use of generics is preferred to improve code generality
  • Give preference to generic methods to limit the scope of generics
  • Use restricted wildcards to increase the flexibility of the API
  • Give priority to heterogeneous containers that are type safe

summary

The resources

  • Java Programming ideas
  • JAVA Core Technologies (Volume 1)
  • Effective java
  • Oracle generic documentation
  • Java generics in detail