_ Kotlin_ Series_ 2, Kotlin generic, byte Android Senior Engineer Interview

2. What is the use of generics?

3. How to define and use generics?

1. What is generics?

The popular understanding of generics is that many types allow us to program without specifying specific types by using the concept of parameterized types

2. What is the use of generics?

Generics is a security mechanism introduced by JDK 1.5 and a technology used by the compiler:

1. It improves the reusability of code

2. The type conversion exception in the run time is advanced to the compilation time to ensure the safety of the type and avoid the type conversion exception

3. How to define and use generics?

We can specify generics for a class, method, or interface, and specify specific types where they are used

1, Java generics

To learn Kotlin generics well, we must first have a sufficient understanding of Java generics, because Kotlin generics and Java generics are basically the same, but some things on Kotlin are written in a new way

1. Simple use of generics

In Java, we can specify generics for a class, method, or interface, and specify specific types where they are used

1) Define a generic class and add < T > after the class name. This syntax structure is to define a generic class. There can be any number of generic classes

//Define a generic class
public class JavaGenericClass<T> {

    private T a;

    public JavaGenericClass(T a) {
        this.a = a;
    }

    public T getA() {
        return a;
    }

    public void setA(T a) {
        this.a = a;
    }

    //Generic class usage
    public static void main(String[] args) {
      	//The compiler can infer generic types, so generic types after new objects can be omitted
        JavaGenericClass<String> javaGenericClass1 = new JavaGenericClass<String>("erdai");
        JavaGenericClass<Integer> javaGenericClass2 = new JavaGenericClass<>(666);
        System.out.println(javaGenericClass1.getA());
        System.out.println(javaGenericClass2.getA());
    }
}

//Print results
erdai
666 

2) Define a generic method and add < T > before the return value of the method. This syntax structure is to define a generic method. There can be any number of generics. The genericity of a generic method has nothing to do with its class

public class JavaGenericMethod {

    public <T> void getName(T t){
        System.out.println(t.getClass().getSimpleName());
    }

    public static void main(String[] args) {
        JavaGenericMethod javaGenericMethod = new JavaGenericMethod();
      	//The compiler can infer generic types, so generic types here can also be omitted
        javaGenericMethod.<String>getName("erdai666");
    }
}

//Print results
String 

3) Define a generic interface

The syntax structure of adding < T > after the interface name is to define a generic interface. There can be any number of generic interfaces

public interface JavaGenericInterface<T> {

    T get();
}

class TestClass<T> implements JavaGenericInterface<T>{

    private final T t;

    public TestClass(T t) {
        this.t = t;
    }

    @Override
    public T get() {
        return t;
    }
}

class Client{

    public static void main(String[] args) {
        JavaGenericInterface<String> javaGenericInterface = new TestClass<>("erdai666");
        System.out.println(javaGenericInterface.get());
    }
}
//Print results
erdai666 

2. Generic erase

1. What is generic erasure?

Look at the following code:

//Different generic types are used, resulting in the same data type
public class JavaGenericWipe {
  
    public static void main(String[] args) {
        Class a = new ArrayList<String>().getClass();
        Class b = new ArrayList<Integer>().getClass();

        System.out.println("a = " + a);
        System.out.println("b = " + b);
        System.out.println("a == b: " + (a == b));
    }
}

//Print results
a = class java.util.ArrayList
b = class java.util.ArrayList
a == b: true 

Why does this happen?

Because generics in Java are implemented using erasure Technology: generic erasure refers to associating generic type instances to the same bytecode through type parameter merging. The compiler generates only one bytecode for a generic type and associates its instance with the bytecode

The reason for using generic erasure is to be compatible with the class loader of the runtime before JDK 1.5, so as to avoid unnecessary class creation at runtime due to the introduction of generics

2. Specific steps for generic erasure

1) . erase all type parameter information. If the type parameter is bounded, replace each parameter with its first boundary; If the type parameter is unbounded, replace it with the Object type erasure rule:

<T> Become Object after erasing

< T extends A > becomes A after erasing

<? Extends A > becomes A after erasing

<? Super a > becomes Object after erasing

2) , (if necessary) insert type conversions to maintain type safety

3) , (if necessary) generate bridging methods to preserve polymorphism in subclasses

//Case 1: erase all type parameter information. If the type parameter is bounded, replace each parameter with its first boundary; If the type parameter is unbounded, replace it with Object
class Paint {
    void draw() {
        System.out.println("Paint.draw() called");
    }
}

//If the boundary is not set for T, t in the work method cannot call the draw method
class Painter<T extends Paint> {
    private T t;

    public Painter(T t) {
        this.t = t;
    }
    public void work() {
        t.draw();
    }
}

//Case 2: insert a type conversion (if necessary) to keep the type safe
public class JavaGenericWipe {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        stringList.add("erdai");
        stringList.add("666");

        for (String s : stringList) {
            System.out.println(s);
        }
    }
}

//The bytecode file generated during compilation is translated roughly as follows
public class JavaGenericWipe {
    public JavaGenericWipe() {
    }

    public static void main(String[] args) {
        List<String> stringList = new ArrayList();
        stringList.add("erdai");
        stringList.add("666");
        Iterator var2 = stringList.iterator();

        while(var2.hasNext()) {
            //The compiler did a forced conversion for us
            String s = (String)var2.next();
            System.out.println(s);
        }
    }
}

//Case 3 (if necessary) generates bridging methods to preserve polymorphism in subclasses
class Node {
    public Object data;

    public Node(Object data) {
        this.data = data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

class MyNode extends Node {

    public MyNode(Integer data) {
        super(data);
    }

    public void setData(Integer data) {
        super.setData(data);
    }
}

//The bytecode file generated during compilation is translated roughly as follows
class MyNode extends Node {

    public MyNode(Integer data) {
        super(data);
    }
    // Compiler generated bridging method
    public void setData(Object data) {
        setData((Integer) data);
    }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
} 

3. Pseudo generics

Generics in Java is a special syntax sugar, which is implemented through type erasure. This generics is called pseudo generics. We can bypass the compiler generics check and add a different type of parameter

//Reflection bypasses compiler checks
public static void main(String[] args) {
    
     List<String> stringList = new ArrayList<>();
     stringList.add("erdai");
     stringList.add("666");

     //Add a new element using reflection
     Class<? extends List> aClass = stringList.getClass();
     try {
         Method method = aClass.getMethod("add", Object.class);
         method.invoke(stringList,123);
     } catch (Exception e) {
         e.printStackTrace();
     }

     Iterator iterator = stringList.iterator();
     while (iterator.hasNext()){
         System.out.println(iterator.next());
     }
}
//Print results
erdai
666
123 

4. Generic erasure advanced

Here is a problem I often encounter at work:

When making a network request, you pass in a generic actual type. Why can you correctly obtain the generic type and convert it to an actual object using Gson?

A: because we can use reflection to get specific generic types at run time

What? Aren't generics erased at compile time? Why is it possible to get specific generic types at runtime? 🤔 ️

A: the so-called type erasure in generics only erases the generic information in the Code attribute. In fact, the generic information is still retained in the class constant pool attribute (Signature attribute and LocalVariableTypeTable attribute), and the attributes in the class constant pool can be carried by class files, field tables, method tables, etc., which enables us to retain the declared generic information, This is also the fundamental basis for us to obtain generic information by reflection at run time

//This is the decompiled JavaGenericClass.class file. You can see T
public class JavaGenericClass<T> {

    private T a;

    public JavaGenericClass(T a) {
        this.a = a;
    }

    public T getA() {
        return a;
    }

    public void setA(T a) {
        this.a = a;
    }
  
  //...
} 

Note: Java is a generic introduced in JDK 1.5. In order to make up for the deficiency of generic erasure, the JVM class file has also been modified accordingly. The most important thing is to add the Signature property sheet and LocalVariableTypeTable property sheet

Let's look at the following code:

class ParentGeneric<T> {

}

class SubClass extends ParentGeneric<String>{

}

class SubClass2<T> extends ParentGeneric<T> {

}

public class GenericGet {

    //Gets the actual generic type
    public static <T> Type findGenericType(Class<T> cls) {
        Type genType = cls.getGenericSuperclass();
        Type finalNeedType = null;
        if (genType instanceof ParameterizedType) {
            Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
            finalNeedType = params[0];
        }
        return finalNeedType;
    }
  
    public static void main(String[] args) {
        SubClass subClass = new SubClass();
	SubClass2<Integer> subClass2 = new SubClass2<Integer>();
        //Print generics obtained by subClass
        System.out.println("subClass: " + findNeedClass(subClass.getClass()));
      	//Print generics obtained by subClass2
        System.out.println("subClass2: " + findGenericType(subClass2.getClass()));
    }
}

//Run this code and print the results as follows
subClass: class java.lang.String
subClass2: T 

Above code:

1. SubClass is equivalent to assigning T = String to ParentGeneric. We get the generic type String through reflection

2. SubClass2 does not assign a value to ParentGeneric. We get the generic type T through reflection

There must be a lot of questions here?

1. Why can I get generic types without passing in any generic information in 1?

2. Why is it that when I create an object, the generic type passed in is Integer, and when I get it, it becomes T?

Now let's carefully analyze a wave:

As I mentioned above, type erasure only erases the generic information in the Code attribute and retains the generic information in the class constant pool attribute. Therefore, when compiling, the above subclass and subClass2 will actually retain their generics into the bytecode file, one is String and the other is T. Subclass and subClass2 are created dynamically at runtime. At this time, even if you pass in generic types, they will be erased. Therefore, the above results will appear. Are you clear here?

If it's still a little vague, let's take another example:

class ParentGeneric<T> {

}

public class GenericGet {
    //Gets the actual generic type
    public static <T> Type findGenericType(Class<T> cls) {
       Type genType = cls.getGenericSuperclass();
        Type finalNeedType = null;
        if (genType instanceof ParameterizedType) {
            Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
            finalNeedType = params[0];
        }
        return finalNeedType;
    }

    public static void main(String[] args) {
        ParentGeneric<String> parentGeneric1 = new ParentGeneric<String>();
        ParentGeneric<String> parentGeneric2 = new ParentGeneric<String>(){};

        //Print generics obtained by parentGeneric1
        System.out.println("parentGeneric1: " + findGenericType(parentGeneric1.getClass()));
        //Print generics obtained by parentGeneric2
        System.out.println("parentGeneric2: " + findGenericType(parentGeneric2.getClass()));

    }
}
//Run this code and print the results as follows
parentGeneric1: null
parentGeneric2: class java.lang.String 

The only difference between the above codes parentGeneric1 and parentGeneric2 is that there are more {}, but the results obtained are quite different. Let's carefully analyze a wave:

1. The generic T declared by ParentGeneric is actually retained in the bytecode file when compiling. parentGeneric1 is created at runtime. Due to generic erasure, we cannot obtain the type through reflection, so null is printed

You may have another question in this place. Since you keep the generic type T, it should be t when I get it. Why is the printed result null?

If you have this question in mind, it means that you think very carefully. To understand this problem, we must first have a certain understanding of the Java Type system, which is actually related to the method of obtaining generic types I wrote above:

//Gets the actual generic type
public static <T> Type findGenericType(Class<T> cls) {
    //Gets the parent class with the current generic type
    Type genType = cls.getGenericSuperclass();
    Type finalNeedType = null;
    //If the current genType is a parameterized type, enter the condition body
    if (genType instanceof ParameterizedType) {
      	//Get the values in the parameter type < >, such as map < K,V >, and you will get an array of [K,V]
        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
      	//Assign the first generic type to finalNeedType
        finalNeedType = params[0];
    }
    return finalNeedType;
} 

In the above code, we need to get the generic parent class of this class first. If it is a parameterized type, enter the condition body, get the actual generic type and return it. If not, finalNeedType will be returned directly, and it will be null at this time

In example 1:

SubClass1 subClass1 = new SubClass1();
SubClass2<Integer> subClass2 = new SubClass2<>();
System.out.println(subClass1.getClass().getGenericSuperclass());
System.out.println(subClass2.getClass().getGenericSuperclass());
//Run the program and print the results as follows
com.dream.java_generic.share.ParentGeneric<java.lang.String>
com.dream.java_generic.share.ParentGeneric<T> 

You can see that the generic parent class is obtained, so you will go to the condition body to obtain the actual generic type and return it

In example 2:

ParentGeneric<String> parentGeneric1 = new ParentGeneric<String>();
System.out.println(parentGeneric1.getClass().getGenericSuperclass());
//Run the program and print the results as follows
class java.lang.Object 

You can see that the obtained generic parent class is Object, so the condition body cannot be entered, so null is returned

2. parentGeneric2 adds {} after it is created, which makes parentGeneric2 an anonymous inner class, and the parent class is ParentGeneric. Because the anonymous inner class is created at compile time, it will be created and carry specific generic information at compile time. Therefore, parentGeneric2 can obtain the generic types

Through the above two examples, we can draw a conclusion: if the generic type is saved to the bytecode during compilation, we can obtain it through reflection at run time. If the actual generic type is passed in at run time, it will be erased at this time, and the actual generic type currently passed in cannot be obtained through reflection

In example 1, we specified that the actual type of the generic type is String, which is stored in the bytecode file when compiling, so we obtained the generic type. In example 2, we created an anonymous inner class, which will also be created during compilation and saved the actual generics into the bytecode, so we can get it. parentGeneric1 is created at runtime. Although the generic T declared by ParentGeneric is also retained in the bytecode file at compile time, the actual type it passes in is erased, and this generic cannot be obtained through reflection. Remember the above conclusion, you can easily obtain generic types

5. Summary of generic acquisition experience

In fact, through the above two examples, we can find that when we define a subclass to inherit a generic parent class and give the generic a type, we can get the generic type

//Define a subclass that inherits the generic parent class and give the generic an actual type
class SubClass extends ParentGeneric<String>{

}

//Anonymous inner class. In fact, the anonymous inner class we defined is also a subclass. It inherits the generic parent class and gives the generic an actual type
ParentGeneric<String> parentGeneric2 = new ParentGeneric<String>(){}; 

Therefore, if we want to get a generic type, we can get the generic type with the help of subclasses. A good programming practice is to declare the generic class that needs to be obtained with abstract

3. Boundary

Boundary is to set constraints on generic parameters, which can force the types that generic can use. More importantly, methods can be called according to their own boundary types

1) . the extensions keyword is used to set the boundary in Java. The complete syntax structure is: < T extensions Bound >, which can be classes and interfaces. If no boundary is specified, the default boundary is Object

2) . multiple boundaries can be set, and & connection is used in the middle. Only one boundary among multiple boundaries can be a class, and the class must be placed in the front, similar to this syntax structure

<T extends ClassBound & InterfaceBound1 & InterfaceBound2> 

Let's demonstrate:

abstract class ClassBound{
    public abstract void test1();
}

interface InterfaceBound1{
    void test2();
}

interface InterfaceBound2{
    void test3();
}

class ParentClass <T extends ClassBound & InterfaceBound1 & InterfaceBound2>{
    private final T item;

    public ParentClass(T item) {
        this.item = item;
    }

    public void test1(){
        item.test1();
    }

    public void test2(){
        item.test2();
    }

    public void test3(){
        item.test3();
    }
}

class SubClass extends ClassBound implements InterfaceBound1,InterfaceBound2 {

    @Override
    public void test1() {
        System.out.println("test1");
    }

    @Override
    public void test2() {
        System.out.println("test2");
    }

    @Override
    public void test3() {
        System.out.println("test3");
    }
}

public class Bound {
    public static void main(String[] args) {
        SubClass subClass = new SubClass();
        ParentClass<SubClass> parentClass = new ParentClass<SubClass>(subClass);
        parentClass.test1();
        parentClass.test2();
        parentClass.test3();
    }
}
//Print results
test1
test2
test3 

4. Wildcard

1. Covariance, inversion and invariance of generics

Think about a question. The code is as follows:

Number number = new Integer(666);
ArrayList<Number> numberList = new ArrayList<Integer>();//Compiler error type mismatch 

In the above code, why can the object of Number be instantiated by Integer, but the object of ArrayList < Number > cannot be instantiated by ArrayList < Integer >?

To understand the above problem, we must first understand what is covariance, inversion and invariance of generics

1) Generic covariance, suppose I define A generic Class of Class < T >, where A is A subclass of B, and Class < A > is also A subclass of Class < b >, then we say that Class is covariant on the generic type of T

2) . generic inversion, suppose I define A generic Class of Class < T >, where A is A subclass of B, and Class < b > is also A subclass of Class < A >, then we say that Class is inverted on the generic type of T

3) . generic invariance. Suppose I define A generic Class of Class < T >, where A is A subclass of B, and Class < b > and Class < A > have no inheritance relationship, then we say that Class is invariable in the generic type of T

Therefore, we can know that the object of ArrayList < number > cannot be instantiated by ArrayList < integer > because the current generics of ArrayList are unchanged. To solve the problem of error reporting above, we can make the current generics of ArrayList support covariance, as follows:

Number number = new Integer(666);
ArrayList<? extends Number> numberList = new ArrayList<Integer>(); 

2. Generic upper boundary wildcard

1) , Generic upper boundary wildcard syntax structure: <? Extensions bound >, so that generics support covariance. Its restricted type is the current upper boundary class or its subclass. If it is an interface, it is the current upper boundary interface or implementation class. Variables using upper boundary wildcards are read-only and cannot be written. null can be added, but it has no meaning

public class WildCard {
    public static void main(String[] args) {
        List<Integer> integerList = new ArrayList<Integer>();
        List<Number> numberList = new ArrayList<Number>();
        integerList.add(666);
        numberList.add(123);

        getNumberData(integerList);
        getNumberData(numberList);
    }

    public static void getNumberData(List<? extends Number> data) {
        System.out.println("Number data :" + data.get(0));
    }
}
//Print results
Number data: 666
Number data: 123 

Question: Why are variables that use the upper boundary wildcard read-only but not write?

1,<? Extensions bound >, whose restricted type is the current upper boundary class or its subclass. It cannot determine its specific type, so the compiler cannot verify the security of the type, so it cannot write

2. Assuming that it can be written, we add several subclasses to it, and then use a specific subclass to receive it, which is bound to cause type conversion exceptions

3. Generic lower boundary wildcard

1) , generic lower boundary wildcard syntax structure: <? Super bound >, so that generics support inversion. Its restricted type is the current lower boundary class or its parent class. If it is an interface, it is the current lower boundary interface or its parent interface. Variables using lower boundary wildcards are only written and are not recommended to be read

public class WildCard {
  
    public static void main(String[] args) {
        List<Number> numberList = new ArrayList<Number>();
        List<Object> objectList = new ArrayList<Object>();
        setNumberData(numberList);
        setNumberData(objectList);
    }
    
    public static void setNumberData(List<? super Number> data) {
        Number number = new Integer(666);
        data.add(number);
    }
} 

Question: why can variables using lower boundary wildcards be written instead of read?

1,<? Super bound >, the restricted type is the current lower boundary class or its parent class. Although it cannot determine its specific type, according to polymorphism, it can ensure that the elements it adds are safe, so it can write

2. When obtaining the value, it will return a value of Object type, but cannot obtain the type represented by the actual type parameter. Therefore, it is recommended not to read it. If you really want to read it, you can also pay attention to the exception of type conversion

4. Generic unbounded wildcard

1) . syntax structure of borderless wildcards: <? >, In fact, it is equivalent to <? Extends Object >, that is, its upper boundary is object or its subclasses. Therefore, variables using unbounded wildcards are also read-only and cannot be written. null can be added, but it is meaningless

public class WildCard {
  
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<String>();
        List<Number> numberList = new ArrayList<Number>();
        List<Integer> integerList = new ArrayList<Integer>();
        stringList.add("erdai");
        numberList.add(666);
        integerList.add(123);
        getData(stringList);
        getData(numberList);
        getData(integerList);
    }
    
     public static void getData(List<?> data) {
        System.out.println("data: " + data.get(0));
    }
}
//Print results
data: erdai
data: 666
data: 123 

5. PECS principles

The design of generic code shall follow the PECS principle (producer extensions consumer super):

1) . if you only need to get elements, use <? extends T>

2) . if you only need storage, use <? super T>

//This is the source code of the copy method in Collections.java
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
      //...
} 

This is a classic example. src represents the original set and uses <? Extensions T > can only read elements from it, dest represents the target set, and can only write elements into it, which fully embodies the PECS principle

6. Summary using wildcards

1) When you only want to read the value, use <? extends T>

2) When you only want to write a value, use <? super T>

3) Do not use wildcards when you want to read and write values

5. Limitations of generics

1) Generic types cannot be explicitly referenced in runtime type operations, such as instanceof operations and new expressions. Runtime types are only applicable to native types

public class GenericLimitedClass<T> {
    private void test(){
        String str = "";
      	//The compiler does not allow this operation
        if(str instanceof T){

        }
        //The compiler does not allow this operation
        T t = new T();
    }
} 

2) . you cannot create an array of generic types. You can only declare an array reference of a generic type

public class GenericLimitedClass<T> {
    private void test(){
       GenericLimitedClass<Test>[] genericLimitedClasses;
# end of document

Many people will always encounter some problems when they first come into contact with this industry or when they encounter a bottleneck period. For example, after learning for a period of time, they don't feel a sense of direction and don't know where to start to learn. I have sorted out some materials and can share them for free

Here, the author shares dozens of interview questions of Tencent, headlines, Alibaba, meituan and other companies in 2021 collected and sorted out the above technical system diagram, and sorted the technical points into videos and PDF(In fact, it takes a lot more energy than expected), including the context of knowledge + Many details, due to the limited space, are shown here in the form of pictures.

**[CodeChina Open source projects:< Android Summary of study notes+Mobile architecture video+Real interview questions for large factories+Project practice source code](https://codechina.csdn.net/m0_60958482/android_p7)**

![](https://img-blog.csdnimg.cn/img_convert/fcde45957cb6f6b80511717f960df978.png)

![](https://img-blog.csdnimg.cn/img_convert/8f4300e38a96aa705d3c83a32a7ca1f4.png)

[Video tutorial]

![](https://img-blog.csdnimg.cn/img_convert/796fd0b98626ed8fdf4b570783d603d1.png)

Heaven rewards diligence, as long as you want, big factory offer It's not out of reach! I hope this article can help you. If you have any questions, please leave a message in the comment area.









//The compiler does not allow this operation
        T t = new T();
    }
} 

2) . you cannot create an array of generic types. You can only declare an array reference of a generic type

public class GenericLimitedClass<T> {
    private void test(){
       GenericLimitedClass<Test>[] genericLimitedClasses;
# end of document

Many people will always encounter some problems when they first come into contact with this industry or when they encounter a bottleneck period. For example, after learning for a period of time, they don't feel a sense of direction and don't know where to start to learn. I have sorted out some materials and can share them for free

Here, the author shares dozens of interview questions of Tencent, headlines, Alibaba, meituan and other companies in 2021 collected and sorted out the above technical system diagram, and sorted the technical points into videos and PDF(In fact, it takes a lot more energy than expected), including the context of knowledge + Many details, due to the limited space, are shown here in the form of pictures.

**[CodeChina Open source projects:< Android Summary of study notes+Mobile architecture video+Real interview questions for large factories+Project practice source code](https://codechina.csdn.net/m0_60958482/android_p7)**

[External chain picture transfer...(img-4tl6Yjuz-1630589481727)]

[External chain picture transfer...(img-MjKcUHFV-1630589481730)]

[Video tutorial]

[External chain picture transfer...(img-0bS59B4B-1630589481731)]

Heaven rewards diligence, as long as you want, big factory offer It's not out of reach! I hope this article can help you. If you have any questions, please leave a message in the comment area.









Keywords: Java Android Design Pattern kotlin

Added by magic2goodil on Fri, 03 Sep 2021 09:17:54 +0300