Tamping Java Foundation Series 13: Deep Understanding of Generics in Java

Keywords: Java Programming github jvm

This series of articles will be sorted out into my Java Interview Guide warehouse on GitHub. For more interesting information, please visit my warehouse.

https://github.com/h2pl/Java-Tutorial

If you like, please trouble Star, Fork, Watch three Lian ha, thank you for your support.

The article was first published on my personal blog:

www.how2playlife.com

This article is part of a series of articles on ramming Java Foundation Series, which is called Java technology. This part of the content comes from the Internet. In order to make the topic clear and clear, it also integrates a lot of technology blog content that I think is good, and cites some good blog articles. If there is any infringement, please contact the author.

This series of blogs will tell you how to learn basic knowledge of Java from step by step, from the entry to the advanced level, and then get to the actual combat, then understand the realization principle behind each Java knowledge point, and understand the whole Java technology system and form its own knowledge framework. In order to better summarize and test your learning results, this series of articles will also provide interview questions and reference answers corresponding to each knowledge point.

@[toc]
If you have any suggestions or questions about this series, you can also pay attention to the public number [Java technology]. Contact your author and welcome you to participate in the creation and revision of this series of blogs.

<!-- more -->

Generic overview

Generics play an important role in java and are widely used in object-oriented programming and various design patterns.

What is generics? Why use generics?

Generics are "parameterized types". When it comes to parameters, the most familiar thing is to define a method with tangible parameters, and then pass arguments when calling the method. So how do you understand parameterized types? As the name implies, the type is parameterized from the original specific type, similar to the variable parameter in the method, in which case the type is also defined as a parameter form (which can be called a type parameter), and then the specific type (type parameter) is passed in when using/calling.

The essence of generics is to parameterize types (in the absence of creating new types, the types specified by generics are used to control the specific restrictions on parameters). That is to say, in the process of generic use, the data type of operation is designated as a parameter, which can be used in classes, interfaces and methods, called generic class, generic interface and generic method respectively.

A chestnut

An example cited countless times:

List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);

for(int i = 0; i< arrayList.size();i++){
    String item = (String)arrayList.get(i);
    Log.d("Generic testing","item = " + item);
}

There is no doubt that the program will end up crashing:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

ArrayList can store any type. In the example, a String type is added, an Integer type is added, and then used as String, so the program crashes. In order to solve such problems (which can be solved at the compilation stage), generics came into being.

We'll change the code that initializes the list in the first line declaration, and the compiler will be able to help us find problems like this in the compilation phase.

List<String> arrayList = new ArrayList<String>();
...
// arrayList.add(100); in the compilation phase, the compiler will report errors

Characteristic

Generics are valid only at the compilation stage. Look at the following code:

List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();

Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();

if(classStringArrayList.equals(classIntegerArrayList)){
    Log.d("Generic testing","Same type");
}

The above examples show that the program will take de-generic measures after compilation. That is to say, generics in Java are only valid at the compilation stage. In the compilation process, after correctly checking the generic results, the generic information will be erased, and the method of type checking and type conversion will be added at the boundary where the object enters and leaves the method. That is to say, generic information does not enter the runtime phase.

To sum up, generic types are logically considered to be multiple different types, but in fact they are all the same basic types.

Usage of generics

There are three ways to use generics: generic classes, generic interfaces, and generic methods.

Generic class

Generic types are used in the definition of classes and are called generic classes. Generics allow you to open the same interface to a group of classes. The most typical container classes are List, Set, Map.

The most basic way to write generic classes (which may be a little dizzy, as detailed in the following examples):

Class class name < generic identifier: you can write any identifier to identify the type of the specified generic type >{
  private generic identifier /* (member variable type)*/ var; 
  .....

  }

One of the most common generic classes:

// Here T can be written as arbitrary identifiers. Common parameters such as T, E, K, V are often used to represent generics.

//When instantiating generic classes, you must specify the specific type of T
public class Generic<T>{
    //Generics declared in a class can be used throughout the class, except for the static part, because generics are declared when instantiated.
    //Code in static areas is determined at compile time and is only related to classes
    class A <E>{
        T t;
    }
    //The method or class in the class again declares that the generic type of the same name is allowed, and that the generic type overrides the generic type T of the same name of the parent class.
    class B <T>{
        T t;
    }
    //Static inner classes can also use generics, which are instantiated with the actual type of generics.
    static class C <T> {
        T t;
    }
    public static void main(String[] args) {
        //Error reporting, T generics cannot be used because generic T belongs to instances and does not belong to classes
//        T t = null;
    }

    //The member variable key is of type T, and the type of T is specified externally.
    private T key;

    public Generic(T key) { //The type of the generic constructor parameter key is also T, and the type of T is specified externally.
        this.key = key;
    }

    public T getKey(){ //The return value type of the generic method getKey is T, and the type of T is specified externally.
        return key;
    }
}

12-27 09:20:04.432 13063-13063/?D/generic test: key is 123456

12-27 09:20:04.432 13063-13063/? D / generic test: key is key_vlaue

Do generic class definitions have to pass in generic type arguments? This is not the case. If a generic parameter is passed in when using a generic type, it will be restricted accordingly according to the incoming generic parameter, and then the generic type will play its due role. If no generic type argument is passed in, the type defined by the generic method or member variable in the generic class can be any type.

Look at an example:

Generic generic = new Generic("111111");
Generic generic1 = new Generic(4444);
Generic generic2 = new Generic(55.55);
Generic generic3 = new Generic(false);

Log.d("Generic testing","key is " + generic.getKey());
Log.d("Generic testing","key is " + generic1.getKey());
Log.d("Generic testing","key is " + generic2.getKey());
Log.d("Generic testing","key is " + generic3.getKey());

D/Generic testing: key is 111111
D/Generic testing: key is 4444
D/Generic testing: key is 55.55
D/Generic testing: key is false

Be careful:
Generic type parameters can only be class types, not simple types.
The instanceof operation cannot be used for exact generic types. The following operations are illegal and errors occur during compilation.

    if(ex_num instanceof Generic<Number>){   
    } 

generic interface

Generic interfaces and generic classes are basically the same in definition and use. Generic interfaces are often used in various classes of producers. Here's an example:

//Define a generic interface
public interface Generator<T> {
    public T next();
}

When a class that implements a generic interface does not pass in a generic argument:

/**
 * When a generic argument is not passed in, it is the same definition as a generic class. When declaring a class, you need to add the generic declaration to the class as well.
 * That is: class FruitGenerator < T > implements Generator < T >{
 * If generics are not declared, such as class FruitGenerator implements Generator < T >, the compiler will report an error: "Unknown class"
 */
class FruitGenerator<T> implements Generator<T>{
    @Override
    public T next() {
        return null;
    }
}

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

/**
 * When a generic argument is introduced:
 * Define a producer to implement this interface, although we only created a generic interface Generator < T >
 * But we can pass in countless arguments for T and form countless types of Generator interfaces.
 * When implementing a generic interface for a class, if a generic type has been passed into an argument type, all places where generics are used are replaced by the incoming argument type.
 * That is: Generator < T >, public T next(); all T in it are replaced by the incoming String type.
 */
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)];
    }
}

Generic wildcards

We know that Ingeter is a subclass of Number, and in the feature section we have also verified that Generic < Ingeter > and Generic < Number > are actually the same basic types. So the question arises. In the method of using Generic < Number > as a parameter, can we use the instance of Generic < Ingeter > to pass in? Are generic types logically similar to Generic < Number > and Generic < Ingeter > considered to be paternal?

To clarify this problem, we use Generic < T > as a generic class to continue with the following examples:

public void showKeyValue1(Generic<Number> obj){
    Log.d("Generic testing","key value is " + obj.getKey());
}

Generic<Integer> gInteger = new Generic<Integer>(123);
Generic<Number> gNumber = new Generic<Number>(456);

showKeyValue(gNumber);

// showKeyValue is a method compiler that will report an error for us: Generic < java.lang.Integer>. 
// cannot be applied to Generic<java.lang.Number>
// showKeyValue(gInteger);

From the prompt information, we can see that Generic < Integer > cannot be regarded as a subclass of `Generic < Number'. It can be seen that the same generic type can correspond to multiple versions (because the parameter type is uncertain), and different versions of generic class instances are incompatible.

Back to the above examples, how to solve the above problems? You can't always define a new way to deal with classes of Generic < Integer > type, which is obviously contrary to multiple concepts in java. So we need a reference type that logically represents both Generic < Integer > and Generic < Number > parent classes. This type of wildcard came into being.

We can change the above method:

public void showKeyValue1(Generic<?> obj){
    Log.d("Generic testing","key value is " + obj.getKey());

Type wildcards are commonly used? Instead of specific type arguments, note that what's here? Like Number, String, Integer, it's a real type, can you? Think of it as the parent of all types. It's a real type.

It can solve the problem that when the specific type is uncertain, the wildcard is?; when the type of operation is not required, only the functions in the Object class are used. So you can use? Wildcards to represent unknown types

public void showKeyValue(Generic<Number> obj){

    System.out.println(obj);
}
Generic<Integer> gInteger = new Generic<Integer>(123);
Generic<Number> gNumber = new Generic<Number>(456);

public void test () {
//        showKeyValue(gInteger); this method will report errors
    showKeyValue1(gInteger);
}

public void showKeyValue1(Generic<?> obj) {
    System.out.println(obj);
}
// showKeyValue is a method compiler that will report an error for us: Generic < java.lang.Integer>.
// cannot be applied to Generic<java.lang.Number>
// showKeyValue(gInteger);

.

generic method

In java, the definition of generic classes is very simple, but the generic method is more complex.

In particular, most of the member methods in generic classes we have seen also use generics, and some even contain generic methods in generic classes, so it is very easy for beginners to misunderstand generic methods.
A generic class specifies the specific type of a generic type when instantiating a class; a generic method specifies the specific type of a generic type when calling a method.

/**
 * Basic introduction of generic methods
 * @param tClass Input generic argument
 * @return T The return value is of type T
 * Explain:
 *     1)public The < T > between the return value and the return value is very important and can be understood as declaring this method generic.
 *     2)The generic method is only the method that declares < T > and the member method that uses generic type in the generic class is not the generic method.
 *     3)<T>It indicates that generic type T will be used in the method, and then generic type T can be used in the method.
 *     4)Like the definition of generic classes, T can be written as arbitrary identifiers here. Common parameters such as T, E, K, V are often used to represent generics.
 */
    public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
      IllegalAccessException{
            T instance = tClass.newInstance();
            return instance;
    }

Object obj = genericMethod(Class.forName("com.test.test"));

Basic usage of generic methods

Looking at the examples above, some students may still be very confused. Let's summarize my generic method through another example.

/** 
 * This is a true generic approach.
 * Firstly, <T> between public and return value is essential, which indicates that this is a generic method and declares a generic T.
 * This T can appear anywhere in this generic method.
 * The number of generics can also be any number 
 *    For example: public < T, K > K showKey Name (Generic < T > container){
 *        ...
 *        }
 */

    public class generic method {
    @Test
    public void test() {
        test1();
        test2(new Integer(2));
        test3(new int[3],new Object());

        //Print results
//        null
//        2
//        [I@3d8c7aca
//        java.lang.Object@5ebec15
    }
    //This method uses generic T
    public <T> void test1() {
        T t = null;
        System.out.println(t);
    }
    //This method uses generic T
    //And both parameters and return values are of type T
    public <T> T test2(T t) {
        System.out.println(t);
        return t;
    }

    //This method uses generic T,E
    //The parameters include T,E
    public <T, E> void test3(T t, E e) {
        System.out.println(t);
        System.out.println(e);
    }
}

‚Äč

Generic methods in classes

Of course, this is not the whole of generic methods. Generic methods can be used anywhere and in any scenario. But there is a very special case, when generic methods appear in generic classes, let's take another look at an example.

//Note that generic classes first write the class name and then write the generic type, and generic methods first write the generic type and then write the method name.
//Generics declared in classes are available in members and methods
class A <T, E>{
    {
        T t1 ;
    }
    A (T t){
        this.t = t;
    }
    T t;

    public void test1() {
        System.out.println(this.t);
    }

    public void test2(T t,E e) {
        System.out.println(t);
        System.out.println(e);
    }
}
@Test
public void run () {
    A <Integer,String > a = new A<>(1);
    a.test1();
    a.test2(2,"ds");
//        1
//        2
//        ds
}

static class B <T>{
    T t;
    public void go () {
        System.out.println(t);
    }
}

Generic Method and Variable Parameters

Look again at an example of generic methods and variable parameters:

public class Generics and Variable Parameters {
    @Test
    public void test () {
        printMsg("dasd",1,"dasd",2.0,false);
        print("dasdas","dasdas", "aa");
    }
    //Ordinary variable parameters can only be adapted to one type
    public void print(String ... args) {
        for(String t : args){
            System.out.println(t);
        }
    }
    //Variable parameters of generics can match all types of parameters. A little invincible.
    public <T> void printMsg( T... args){
        for(T t : args){
            System.out.println(t);
        }
    }
        //Print results:
    //dasd
    //1
    //dasd
    //2.0
    //false

}

Static methods and generics

Static methods use generics for static methods in classes: static methods cannot access generics defined on classes; if the reference data type of static method operations is uncertain, generics must be defined in methods.

That is, if a static method is to use generics, it must also be defined as a generic method.

public class StaticGenerator<T> {
    ....
    ....
    /**
     * If you define a static method using generics in a class, you need to add additional generic declarations (define this method as generic methods)
     * Even static methods cannot use generics already declared in generic classes.
     * For example: public static void show (T) {.}, the compiler will prompt the error message:
          "StaticGenerator cannot be refrenced from static context"
     */
    public static <T> void show(T t){

    }
}

Summary of generic methods

Generic methods enable methods to change independently of classes. The following are basic guidelines:

Whenever you can, you should try to use generic methods. That is to say, if the generic method is used to generize the whole class, then the generic method should be used. In addition, for a static method, the parameters of the generic type cannot be accessed. So if the static method wants to use the generic ability, it must be made a generic method.

Upper and lower boundaries of generics

When using generics, we can also restrict the upper and lower bounds of incoming generic type arguments, such as: type arguments are only allowed to pass in a certain type of parent or a certain type of subclass.

Adding an upper bound to a generic, that is, the incoming type argument must be a subtype of the specified type

public class Generic wildcards and boundaries {
    public void showKeyValue(Generic<Number> obj){
        System.out.println("key value is " + obj.getKey());
    }
    @Test
    public void main() {
        Generic<Integer> gInteger = new Generic<Integer>(123);
        Generic<Number> gNumber = new Generic<Number>(456);
        showKeyValue(gNumber);
        //Subclasses in generics cannot also be passed in as parent references
//        showKeyValue(gInteger);
    }
    //Direct use? Wildcards can accept any type as generic imports
    public void showKeyValueYeah(Generic<?> obj) {
        System.out.println(obj);
    }
    //Only subclasses or numbers of numbers can be passed in
    public void showKeyValue1(Generic<? extends Number> obj){
        System.out.println(obj);
    }

    //Only the parent class of Integer or Integer can be passed in
    public void showKeyValue2(Generic<? super Integer> obj){
        System.out.println(obj);
    }

    @Test
    public void testup () {
        //This line of code compiler will prompt errors because String type is not a subclass of Number type
        //showKeyValue1(generic1);
        Generic<String> generic1 = new Generic<String>("11111");
        Generic<Integer> generic2 = new Generic<Integer>(2222);
        Generic<Float> generic3 = new Generic<Float>(2.4f);
        Generic<Double> generic4 = new Generic<Double>(2.56);

        showKeyValue1(generic2);
        showKeyValue1(generic3);
        showKeyValue1(generic4);
    }

    @Test
    public void testdown () {

        Generic<String> generic1 = new Generic<String>("11111");
        Generic<Integer> generic2 = new Generic<Integer>(2222);
        Generic<Number> generic3 = new Generic<Number>(2);
//        showKeyValue2(generic1); This bank reported an error because String is not the parent of Integer
        showKeyValue2(generic2);
        showKeyValue2(generic3);
    }
}

== A note on generic arrays==

See many articles will mention generic arrays, after looking at sun's documentation, in java is "can not create an exact generic type of array".

That is to say, the following example is not possible:

List<String>[] ls = new ArrayList<String>[10];  

It is possible to create generic arrays using wildcards, as follows:

List<?>[] ls = new ArrayList<?>[10];  

This is also possible:

List<String>[] ls = new ArrayList[10];

Here's an example of Sun's document to illustrate this problem:

List<String>[] lsa = new List<String>[10]; // Not really allowed.    
Object o = lsa;    
Object[] oa = (Object[]) o;    
List<Integer> li = new ArrayList<Integer>();    
li.add(new Integer(3));    
oa[1] = li; // Unsound, but passes run time store check    
String s = lsa[1].get(0); // Run-time error: ClassCastException.

In this case, because of the erasing mechanism of JVM generics, the JVM does not know the generic information at run time, so it can assign an ArrayList to oa[1] without exception, but it needs to do a type conversion when the data is extracted, so ClassCastException will appear. If the generic array declaration can be made, the situation mentioned above will not occur at compile time. Any warnings and errors will occur only at runtime.

Restricting the declaration of generic arrays, in this case, can prompt code at compile time for type safety issues, much better than without any prompts.
The following is allowed by wildcards: the type of an array cannot be a type variable unless it is wildcards, because for wildcards, the final data is extracted by explicit type conversion.

List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.    
Object o = lsa;    
Object[] oa = (Object[]) o;    
List<Integer> li = new ArrayList<Integer>();    
li.add(new Integer(3));    
oa[1] = li; // Correct.    
Integer i = (Integer) lsa[1].get(0); // OK 

Last

The examples in this paper are simply given to illustrate some of the ideas in generics, and do not necessarily have practical usability. In addition, when it comes to generics, I believe that the most commonly used is in collections. In fact, in the actual programming process, I can use generics to simplify development, and can guarantee the quality of code.

Generic Common Interview Questions

  1. What is generics in Java? What are the advantages of using generics?

This is one of the questions you will be asked at the beginning of various Java generic interviews, mainly in junior and intermediate interviews. Anyone with a Java 1.4 or earlier development background knows how inconvenient it is to store objects in collections and type-convert them before they are used. Generics prevent that from happening. It provides type safety at compile time, ensuring that you can only put objects of the correct type into the collection, avoiding the occurrence of ClassCastException at run time.

  1. How does Java's generic work work? What is type erasure?

This is a better generic interview question. Generics are implemented by type erasure. The compiler erases all type-related information at compile time, so there is no type-related information at run time. For example, List < String > is represented by only one List at run time. The purpose of this is to ensure that it is compatible with the binary class library developed before Java 5. You cannot access type parameters at runtime because the compiler has converted generic types into primitive types. According to your answer to this generic question, you will get some follow-up questions, such as why the generic type is implemented by type erasure or shows you some error generic code that will cause the compiler to make mistakes. Read how generics work in my Java for more information.

  1. What are qualified wildcards and unrestricted wildcards in generics?

This is another very popular Java generic interview question. Limited wildcards restrict types. There are two kinds of qualified wildcards, one is <? Extends T > which sets the upper bound of the type by ensuring that the type must be a subclass of T, and the other is <? Super T > which sets the lower bound of the type by ensuring that the type must be a parent of T. Generic types must be initialized with types within limits, otherwise compilation errors will occur. On the other hand, <?> denotes unrestricted wildcards, because <?> can be replaced by any type. For more information, see the difference between qualified and unrestricted wildcards in my article on generics.

  1. What is the difference between List<? Extends T> and List <? Super T>?

This is related to the last interview question. Sometimes the interviewer will use this question to evaluate your understanding of generics rather than directly asking you what qualifying wildcards and unqualified wildcards are. Both List declarations are examples of qualified wildcards. List <? Extends T> can accept any type of List inherited from T, while List <? Super T> can accept a List composed of any T's parent class. For example, List <? Extends Number > can accept List < Integer > or List < Float >. More information can be found in the connections that appear in this section.

  1. How do you write a generic method that accepts generic parameters and returns generic types?

It's not difficult to write generic methods. You need generic types instead of primitive types, such as T, E or K,V and other widely accepted type placeholders. Refer to the Java Collection Class Framework for an example of generic methods. In the simplest case, a generic approach might look like this:

public V put(K key, V value) {

return cache.put(key, value);

}

  1. How do you use generics to write classes with parameters in Java?

This is an extension of the last interview question. The interviewer may ask you to write a type-safe class with generics, rather than a generic method. The key is still to replace the original type with generic types, and to use the standard placeholders used in JDK.

  1. Write a generic program to implement LRU caching?

This is quite a practice for people who like Java programming. To give you a hint, LinkedHashMap can be used to implement a fixed size LRU cache. When the LRU cache is full, it will move the oldest key-value pair out of the cache. LinkedHashMap provides a method called removeEldestEntry(), which is called by put() and putAll() to delete the oldest key-value pairs. Of course, if you have written a runnable JUnit test, you can also write your own implementation code at will.

  1. Can you pass List < String > to a method that accepts List < Object > parameters?

For anyone who is not familiar with generics, this Java generics topic seems confusing, because at first glance String is an Object, so List < String > should be used where List < Object > is needed, but that's not the case. Doing so can lead to compilation errors. If you think about it further, you will find it meaningful for Java to do so, because List < Object > can store any type of object, including String, Integer and so on, while List < String > can only store Strings.

List<Object> objectList;

List<String> stringList;

objectList = stringList; //compilation error incompatible types

  1. Can generics be used in Array?

This is probably the simplest of the Java generic interview questions. Of course, the premise is that you know that Array does not actually support generics, which is why Joshua Bloch suggests List instead of Array in Effective Java, because List can provide compile time type security assurances, but Array can not.

  1. How to prevent warnings in Java that types are not checked?

If you mix generics with primitive types, such as the following code, Java 5's javac compiler produces warnings that types are not checked, such as

List<String> rawList = new ArrayList()

Note: Hello.java uses unchecked or unsafe operations.

This warning can be blocked using the @SuppressWarnings("unchecked") annotation.

Reference articles

https://www.cnblogs.com/huaji...
https://www.cnblogs.com/jpfss...
https://www.cnblogs.com/dengc...
https://www.cnblogs.com/cat52...
https://www.cnblogs.com/copri...

Public address

Java Technology

If you want to keep an eye on my updated articles and share the dry goods, you can pay attention to my public address [Java technology], a technology station of Ali Java engineer, author Huang Xiaoxie, focus on Java related technologies: SSM, SpringBoot, MySQL, distributed, middleware, cluster, Linux, network, multithreading, occasionally talk about Docker, ELK, and also share Technical dry goods and learning experience, committed to Java full stack development!

Java Engineers Necessary Learning Resources: Some Java Engineers commonly use learning resources. After paying attention to the public number, the keyword "Java" can be answered in the background for free without routine access.

Personal Public No. Huang Xiaoxiao

The author is a master of 985, an engineer of Ant Golden Wear JAVA, focusing on the JAVA back-end technology stack: SpringBoot, MySQL, distributed, middleware, micro-services, but also knows how to invest in finance, occasionally talk about the theoretical basis of algorithms and computing mechanisms, adhere to learning and writing, believe in the power of lifelong learning!

Programmer 3T technology learning resources: Some programmers learn technology resources package, after paying attention to the public number, the background reply keyword "information" can be free of charge without routine access.

Posted by bennyboywonder on Thu, 10 Oct 2019 04:08:41 -0700