Recently, in a technology forum, a friend asked: what is the difference between Object and generic T? After answering the question, I couldn’t help but think, with the interview coming up, there are so many friends who can’t be generic? It’s time to put together an article that covers generics so you don’t have to worry about questions about generics in interviews or in practice.

What are generics

Generics are a new feature introduced in JDK 5, also known as “parameterized typing.” In plain English, this means that an original concrete type is defined by parameterization, and then the concrete type (type argument) is passed in when used or called.

The nature of generics is to parameterize types (to control the specific types of parameters through the different types specified by generics without creating new types). In the use of generics, the data type of an operation is specified as a parameter. This parameter type can be used in classes, interfaces, and methods, known as generic classes, generic interfaces, and generic methods, respectively.

Why generics

Without generics, arguments can be “arbitrary” with Object, but this has the disadvantage of requiring explicit casting, which requires the developer to know the actual type.

Casting errors can occur, such as Object forcing an Integer from the actual type String. There are no errors at compile time, and exceptions are thrown at run time, which is an obvious security risk.

Java introduces the generics mechanism to check the hidden dangers mentioned above at compile time. Developers can not only know the actual type clearly, but also can check errors at compile time, thus improving the security and robustness of the code.

Before and after using generics

Take a classic example of what can happen when generics are not used.

List list = new ArrayList(); list.add(1); list.add("zhuan2quan"); List.add (" program new horizon "); for (int i = 0; i < list.size(); i++) { String value = (String) list.get(i); System.out.println("value=" + value); }Copy the code

The above code does not raise any errors in the compiler, but does raise the following exception when executed:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Copy the code

So, can we fix this problem at the compiler instead of throwing an exception at runtime? Generics came into being. When written using generics, the code looks like this:

List<String> list = new ArrayList<>(); list.add(1); list.add("zhuan2quan"); List.add (" program new horizon "); for (String value : list) { System.out.println("value=" + value); }Copy the code

As you can see, the code is much cleaner and simpler, and the list.add(1) line directly prompts an error message in the IDE:

Required type: String
Provided: int
Copy the code

The error message is that generics constrain the data added to the List to only be strings.

Wildcards in generics

What do wildcards like T, E, K, and V mean when you use generics?

They’re essentially wildcards, there’s no difference between them, any letter between A and Z. But there are some unwritten agreements among developers:

  • T (type) indicates a specific Java type;
  • K and V represent key values in Java keys.
  • E stands for element;

Why are Java generics false generics

For downward compatibility, generics in Java are just syntactic sugar, not true generics like C++.

Adding an int to a List of generic strings will cause an error:

List<String> list = new ArrayList<>();
list.add(1);
Copy the code

For the above code, we use reflection to call the add method indirectly:

@Test public void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { List<Integer> list = new ArrayList<>(); list.add(1); Method add = list.getClass().getMethod("add", Object.class); Add.invoke (list," program new horizon "); System.out.println(list); System.out.println(list.get(1)); }Copy the code

When we execute the above code, we find that the program does not throw an exception, and normally prints out and out:

[1, program new horizon] Program new horizonCopy the code

A List that could only load integers successfully loaded a String value. Thus, so-called generics are indeed false generics.

We can also prove this by bytecode. Using the javap -c command to take a look at the bytecode using the generics example above:

Code: 0: new #2 // class java/util/ArrayList 3: dup 4: invokespecial #3 // Method java/util/ArrayList."<init>":()V 7: astore_1 8: aload_1 9: ldc #6 // String zhuan2quan 11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;) Z 16: pop 17: aload_1 18: LDC #7 invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;) Z 25: pop 26: aload_1 27: invokeinterface #18, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 32: astore_2 33: aload_2 34: invokeinterface #19, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 39: ifeq 80Copy the code

As you can see from the bytecode, the list. add method is essentially an Object. Again, Java generics are only valid at compile time and are erased at run time, meaning that all generic parameter types are erased at compile time. This is what we often call type erasure.

Thus, it can be said that generic types are logically seen as different types, but are actually the same basic type.

Definition and use of generics

There are three types of generics: generic classes, generic interfaces, and generic methods.

Before we look at these three types of generic usage scenarios, we need to clarify a basic rule that the declaration of generics is usually defined by <> with uppercase letters, such as <T>. It’s just that different types are declared in different places and used in different ways.

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

Example:

Public class Foo<T> {// a generalized member variable, the type of T is specified externally. Public Foo(T info){this.info = info; Public T getInfo() {return info;} public T getInfo() {return info; } public static void main(String[] args) {// When instantiating a generic class, you must specify the specific type of T, which is String. // The argument type passed in must be of the same type as the generic type parameter, in this case String. Foo<String> Foo = new Foo<>(" program new horizon "); System.out.println(foo.getInfo()); }}Copy the code

Of course, in the example above, it is possible to use generic classes without specifying the actual type, which is syntactically supported, so it is not recommended at this point as if generics were not defined.

Foo foo11 = new Foo(1);
Copy the code

For example, the above writing is also possible, but the time to define generic meaning.

A generic interface

A generic interface is declared in the same way as a generic class.

public interface Context<T> {
    T getContext();
}
Copy the code

Generic interfaces can be implemented in two ways: subclasses explicitly declare generic types and subclasses unexplicitly declare generic types.

Let’s look at an example where a subclass explicitly declares a generic type:

// The argument type is passed in to implement the generic interface, Public class TomcatContext implements Context<String> {@override public String getContext() {Override public String getContext() { return "Tomcat"; }}Copy the code

Subclasses do not explicitly declare generic types:

// When a generic argument is not passed, it is the same as the definition of a generic class. Public class SpringContext<T> implements Context<T>{@override public T getContext() {return null; }}Copy the code

There is, of course, another case where we use classes defined as generics as normal classes.

The examples above all have one generic parameter, although two or more can be specified:

public interface GenericInterfaceSeveralTypes< T, R > {
    R performAction( final T action );
}
Copy the code

Multiple generic parameters can be separated by commas (,).

Generic method

A generic class specifies the specific type of the generic when instantiating the class; A generic method specifies the specific type of the generic when the method is called. Generic methods can be ordinary methods, static methods, abstract methods, final modified methods, and constructors.

Generic methods have the following syntax:

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

Angle brackets contain a list of type parameters, before the method return value T or void keyword. T, defined in Angle brackets, can be used anywhere in a method, such as parameters, inside the method, and return values.

protected abstract<T, R> R performAction( final T action ); static<T, R> R performActionOn( final Collection< T > action ) { final R result = ... ; // Implementation here return result; }Copy the code

As you can see from the examples above, generic methods can also define multiple generic types.

Here’s another example of code:

Public class GenericsMethodDemo1 {//1, public and return value <T>, declare the generic type of this method. <T> <T> <T> <T> <T> <T> <T> <T> <T> <T> <T> indicates that the method will use the generic type T, then you can use the generic type T in the method. //4, T can be any identifier, such as T, E, K, V, etc. public static <T> T printClass(T obj) { System.out.println(obj); return obj; } public static void main(String[] args) { printClass("abc"); printClass(123); }}Copy the code

It is important to note that generic methods are independent of whether the class is generic or not. In addition, static methods cannot access generics defined on a class; If the reference data type of static method operation is uncertain, generics must be defined on the method.

In the example above, if GenericsMethodDemo1 is defined as GenericsMethodDemo1<T>, then the printClass method cannot use T directly on the class, but can only access T as defined in the code above.

Generic methods are different from ordinary methods

Let’s compare generic and non-generic methods:

Public T getKey(){return key; } public <T> T showKeyName(T T){return T; }Copy the code

Method 1 uses the T generic declaration, but it uses a variable defined in a generic class, so it is not a generic method. Method 2, which declares T with two Angle brackets, is a true generic method.

For method two, there is another case where T is declared in a class, and the parameter T refers only to the method’s T, not the class’s T.

Generic methods and mutable parameters
@SafeVarargs
public final <T> void print(T... args){
	for(T t : args){
		System.out.println("t=" + t);
	}
}

public static void main(String[] args) {
	GenericDemo2 demo2 = new GenericDemo2();
	demo2.print("abc",123);
}
Copy the code

The print method prints the results in the args mutable arguments, which can be passed in different concrete types.

Print result:

t=abc
t=123
Copy the code

The summary of generic methods is this: if you can use generic methods, use generic methods as much as possible, so that you can use generic methods to the extent that they are most needed. If you use a generic class, the entire class is generalized.

Generic wildcard

The type wildcard is usually used with? Instead of a concrete type argument (type argument, not type parameter). When you manipulate a type without using the specific functions of the type, you can use the functions of the Object class. Wildcard to list unknown types. For example the List <? > is logically the parent of List<String>, List<Integer>, and all List< concrete type arguments >.

/** * An instance of List<Ingeter> cannot be passed in when a List<Number> is used as a parameter. */ public static void getNumberData(List<Number> data) { System.out.println("data :" + data.get(0)); } /** * an instance of List<Number> cannot be passed in for a method that uses List<String> as a parameter; */ public static void getStringData(List<String> data) { System.out.println("data :" + data.get(0)); } /** * Use type wildcards to indicate reference types that are both List<Integer> and List<Number> and List<String>. * Type wildcards are usually used in? Instead of a concrete type argument, note that this is a type argument; * is an actual type, like Number, String, and Integer. As the parent of all types. */ public static void getData(List<? > data) { System.out.println("data :" + data.get(0)); }Copy the code

Of the three methods, getNumberData can only pass arguments of type List<Number> and getStringData can only pass arguments of type List<String>. If they all use only the functionality of the Object class, they can be declared in the form of the getData method, which supports both types.

Wildcards of this type are also known as unbounded wildcards and 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.

Is used in getData? As a wildcard, but in some cases, you need to restrict the upper and lower bounds of generic type arguments. For example, a type argument can only be passed to a parent or subclass of a type.

The following is an example of an upper bound wildcard:

/** * The type wildcard upper limit is defined by the form List, so the definition is that the wildcard generic value accepts Number and its subclass types. */ public static void getUperNumber(List<? extends Number> data) { System.out.println("data :" + data.get(0)); }Copy the code

Extends limits the upper bounds of wildcards by accepting only Number and its subclass types. The implementation of an interface and the integration of a class can both be represented through extends.

The Number can also be replaced by T, indicating that the wildcard represents a subclass of type T.

public static void getData(List<? extends T> data) {
    System.out.println("data :" + data.get(0));
}
Copy the code

There are also lower bound wildcards in contrast to upper bound wildcards:

public static void getData(List<? super Integer> data) {
    System.out.println("data :" + data.get(0));
}
Copy the code

A lower-bound wildcard indicates that the type it represents is a parent of type T.

Limitations of generics

Primitive types (e.g., int,long,byte, etc.) cannot be used with generics and need to be replaced by their wrapper classes (e.g., Integer, long,byte, etc.).

final List< Long > longs = new ArrayList<>();
final Set< Integer > integers = new HashSet<>();
Copy the code

Of course, in the process of use will involve the operation of automatic unpacking and automatic packing:

final List< Long > longs = new ArrayList<>(); longs.add( 0L ); Longs = longs. Get (0); // 'Long' unpack 'Long'Copy the code

Type inference for generics

When generics are introduced, developers need to add generic types wherever they are used, such as:

final Map<String, Collection<String>> map =
    new HashMap<String, Collection<String>>();
 
for(final Map.Entry< String, Collection<String> > entry: map.entrySet()) {
}

Copy the code

To solve the above problem, the <> operator was introduced in Java7, and the compiler can infer the primitive type that the operator represents.

Therefore, in Java7 and beyond, the creation of generic objects takes the following form:

final Map< String, Collection<String>> map = new HashMap<>();
Copy the code

summary

This article takes you step-by-step from why to how to use generics in different scenarios. After learning this article, you will be able to handle 90% of the post-interview scenarios. If it helps you, give it a thumbs up.

Refer to the article: blog.csdn.net/s10461/arti… www.cnblogs.com/jingmoxukon… Blog.csdn.net/lxxiang1/ar… www.javacodegeeks.com/2015/09/how…