A Brief Introduction to the Use of Java Generics

Keywords: Java SQL

Catalog

I. What are generics?

"Genotype", as its name implies, "generic type". We provide the concept of general reference, but specific rules can be used to constrain the implementation. For example, we use a lot of ArrayList as a generic class. ArrayList as a collection can store various elements, such as Integer, String, custom types, etc., but when we use it, we use specific rules. Rules to constrain, such as we can constrain the collection to store only elements of Integer type, such as List < Integer > iniData = new ArrayList <>().

Second, what are the benefits of using generics

Taking collections as an example, the advantage of using generics is that we don't have to define different types of collections, such as integer collections, floating-point collections and string collections, because we add different types of elements. We can define a collection to store integer, floating-point and string data, which is not the most important. Because we only need to set Object to the underlying storage, all the added data can be transformed upward into Object. More importantly, we can control the type of data stored according to our own ideas through rules.

Let's take ArrayList as an example. If we want to put the date from this month to today in ArrayList, if we don't use generics, then we define an ArrayList.

List monthDays = new ArrayList();

We add dates 1 to 4 to it.

public static List addMonthDays(){
    List monthDays = new ArrayList();
    monthDays.add(LocalDate.now().withDayOfMonth(1));
    monthDays.add(LocalDate.now().withDayOfMonth(2));
    monthDays.add(LocalDate.now().withDayOfMonth(3));
    monthDays.add(new Date());
    return monthDays;
}

Is there any problem with that? You can see that, of course, although we can express the date, we use Date, LocalDate, and we call the method to print it directly, that's it.

public static void main(String[] args) {
    List monthDays = addMonthDays();
    for(Object day : monthDays){
        System.out.println(day);
    }
}
2019-08-01
2019-08-02
2019-08-03
Sun Aug 04 10:27:10 CST 2019

We certainly don't want such a result. What we want is

2019-08-01
2019-08-02
2019-08-03
2019-08-04

If only these two types of elements are stored (if we know them), then we will judge them manually.

public static void main(String[] args) {
    List monthDays = addMonthDays();
    for(Object day : monthDays){
        if (day instanceof Date){
            Date date = (Date) day;
            System.out.println(LocalDate.of(date.getYear(), date.getMonth(), date.getDay()));
        }else {
            System.out.println(day);
        }
    }
}

At this time, we can achieve the above goal, but as you all know, this writing problem is very big.

  1. We can't control whether the stored elements are related to the date or not. For example, if we store "1" or "65536" non-date, the defined method will not report errors, but it will inevitably report errors when invoking type conversion.
  2. We don't know exactly what types of elements are stored in the collection, such as date strings like "2019/08/04" and java.sql.Date. It's hard to guarantee that they can be exhausted.
  3. The code is too complex to maintain.

At this point, generics provide a good solution, from the source of good rules, can only add the type of LocalDate, then the above problems have been solved.

public static List<LocalDate> addFormatMonthDays(){
    List<LocalDate> monthDays = new ArrayList<>();
    monthDays.add(LocalDate.now().withDayOfMonth(1));
    monthDays.add(LocalDate.now().withDayOfMonth(2));
    monthDays.add(LocalDate.now().withDayOfMonth(3));
    monthDays.add(new Date());//This code will not pass during compilation
    return monthDays;
}

Not only does it improve the readability of the code, we can see at a glance that we are storing the LocalDate type. At the same time, the compiler can also make good use of this information. During compilation, type checking can be carried out to ensure security. When get, no mandatory type conversion is needed.

III. Generic Classes

In order to adapt the class to more situations and have better scalability, we can set it as a generic class, that is, a class with one or more type variables, as follows:

Public class ClassName < generic identifier, which can be alphabetic, Chinese characters, etc., but usually in capitalized English characters >{
    private generic identity property;
}

For generic identifiers, there are some commonly used expressions. If it is an element type representing a collection, it is usually written with the letter E, such as our commonly used ArrayList, public class ArrayList < E>. We define it as follows:

//Garage
public class Garage<E> {
    //Add a car to the garage
    public void add(E car){
        //...
    }
}

We can also use the characters K and V to represent the types of keywords and values, such as our commonly used HashMap, public class HashMap < K, V>, we can also customize as follows:

//Mapping relation
//K,V: Vegetables, Organic or not; Fruits, Origin; Clothing, Type; Automobile, Brand
public class Mapping<K, V> {
    
    private K key;
    
    private V value;
}

We also often use a character T to represent the type.

public class Person<T> {
    private T t;
    public Person(T t){
        this.t = t;
    }
    public void run(){
        System.out.println(t);
    }
}

How to use generic classes and how to instantiate types, we just need to ensure that the type of arguments passed in is the same as the type of generic parameters.

//Normal usage
Person<String> s1 = new Person<String>("Zhang San");
//After jdk7.0, the following parameter types can be omitted
Person<String> s2 = new Person<>("Zhang San");
s2.run();

//Of course, the definition of generics can help us to do things according to certain rules. If we don't make restrictions, we won't compile errors, but generics are meaningless.
Person t = new Person(111);
t.run();

//Generic types can't be the eight basic types. The following compilation errors occur
Person<int> s = new Person<>(1);

IV. Generic Interface

The definition of generic interfaces and generic classes is basically the same, as follows:

public interface Person<T> {
    public T parent();
    public String eat();
}

When we define a class to implement the interface, the generic type of the class must be the same as the generic type of the interface class. Without passing arguments, we continue to use the generic type T. When passing arguments, the generic type must use the parameter type.

public class Teacher<T> implements Person<T> {
    @Override
    public T parent() {
        return null;
    }

    @Override
    public String eat() {
        return null;
    }
}
//Teacher doesn't need to define types anymore, because generic types are already defined at Person.
public class Teacher implements Person<Integer> {
    //The return type here must be Integer or something must go wrong
    @Override
    public Integer parent() {
        return null;
    }

    @Override
    public String eat() {
        return null;
    }
}

V. Generic Approaches

Generic methods can be defined in general classes and generic classes. For example, generic classes are more commonly used. Generally, problems that can be solved by generic methods prefer generic methods to generic classes. Type variables are placed behind modifiers, such as public static, public final, etc.

public class Teacher {
    public static <T> T println(T t){
        System.out.println(t);
        return t;
    }
}

Call is very simple, very general method calls are the same, more convenient is that the type is not as limited as the general method.

String s = Teancher.println("str");

It should also be noted that generic variables defined in generic classes for generic methods are not related, such as code.

public class Teacher<T> {
    T teacher;
    public Teacher(T t){
        this.teacher = t;
    }
    public <T> T println(T t){
        System.out.println(t);
        return t;
    }
}
Teacher<String> teacher = new Teacher<>("Zhang San");
Integer in = teacher.println(123456);

Class generic type is String, method generic type is Integer, although they are all expressed in T.

At the same time, the generic method needs to be explained as follows:

It's very important between the modifier public xx and the method name. A < T > is a generic method; using only generic variables is not a generic method.

Limited type variables

Whether it is a generic class or a generic method, at present, there is no type restriction, no matter what type of variable we pass in, because we do not use the type-specific things (member variables, methods, etc.) in the processing logic. What if the parameter type we want to pass is just a few small classes (subclasses) below a large class (parent class)?

public class ArrayFlag {
    public static <T> T getMax(T[] array){
        if(array == null || array.length == 0){
            return null;
        }
        T maxValue = array[0];
        for(int i = 0; i < array.length; i++){
            if(array[i].compareTo(maxValue) > 0){
                maxValue = array[i];
            }
        }
        return maxValue;
    }
}

As you can see, we use compareTo method to compare in getMax method, but if the type T we pass in does not have compareTo method, should we not report errors, so we need to make restrictions, as long as the restriction is that Comparable interface must have compareTo method, then this will be the case after transformation.

public class ArrayFlag {
    public static <T extends Comparable> T getMax(T[] array){
        if(array == null || array.length == 0){
            return null;
        }
        T maxValue = array[0];
        for(int i = 0; i < array.length; i++){
            if(array[i].compareTo(maxValue) > 0){
                maxValue = array[i];
            }
        }
        return maxValue;
    }
}

At the same time, it needs to be noted that the extension keyword is used here, which means that the Comparable interface and its subtypes are bound. It means "binding, restriction", not "inheritance", and can be followed by interfaces or classes. If there are multiple restrictions, you can use & separate, such as:

public static <T extends Comparable & Serializable> T getMax(T[] array)

7. Generic wildcards

For example, a Book class and a novel class are defined.

//Define a Book Class
public class Book {}
//Definition of a novel book class inheritance book class
public class Novel extends Book {}

Let's define a bookcase class to hold books and novels.

//Define a bookcase class to hold books
public class Bookcase<T> {
    T b; 
    public Bookcase(T t){
        this.b = t;
    }
    public void set(T t) {
        b=t;
    } 
    public T get() {
        System.out.println(b.getClass());
        return b;
    }
}

Now let's use the bookcase to hold the novel.

//Previous writing, unable to compile, prompt Incompatible types, Required Book Found Novel
Bookcase<Book> bc = new Bookcase<Novel>(new Novel());

But after jdk7.0, new Bookcase can be written without giving generic types. The omitted types can be inferred from the types of variables, so if the following is the way to write

Bookcase<Book> bc = new Bookcase<>(new Novel());
System.out.println(bc.getClass());
bc.get();

At this time, it can be compiled and passed. The result of our execution is as follows:

class generic.Bookcase
class generic.Novel

Of course, we can also solve this problem by wildcards, which include the following:

Upper bound wildcards, lower bound wildcards, indefinite wildcards

7.1 Upper bound wildcards

The definition of upper wildcard is as follows: using extends keyword, the meaning is that the bookcase can only place fiction books (such as any city fiction, love fiction, fantasy fiction), but not father books, other categories such as history books, career books, financial books, etc., which are limited when they are used. Such as:

Bookcase<? extends Book> bc = new Bookcase<Novel>(new Novel());

This definition will not compile errors. In addition, regarding the characteristics of upper bound wildcards, the upper bound is limited. According to the principle of upward modeling of java polymorphism, it is not suitable for frequent insertion of data and frequent reading of data scenarios.

7.2 Lower bound wildcards

The definition of the lower bound wildcard is as follows: with the super keyword, the meaning is that the bookcase is placed with a lower bound. We can only place Book s and Novel books, but we can no longer put subdivided urban fiction and love fiction books in it.

Bookcase<? super Novel> bc = new Bookcase<Novel>(new Novel());

In addition, the characteristics of lower-bound wildcards are just the opposite of upper-bound wildcards, which are not suitable for frequent data reading and frequent data insertion scenarios.

7.3 Infinite wildcards

Infinite wildcards mean that any object can be used, so using them is similar to using native types. But it works. A native type can hold any type, while an unrestricted wildcard-decorated container holds a specific type.

For instance:

List<?> list = new ArrayList<>();
//Cannot compile through
list.add(new Object());

//Here's how you can add any type
List<Object> list = new ArrayList<>();
list.add(new Object());

Again, the difference between < T > and <?> seems at first glance to be that they can all represent generic variables, all extends, but they do have different usage scenarios.

  • Type parameter < T > declares a generic class or generic method
  • Infinite wildcards <?> Use generic classes or generic methods

VIII. Summary

Generics are very common in java. The collection classes we mentioned earlier, such as ArrayList, HashSet and Map, all use generics. Generics are often used in some component encapsulation. This paper mainly introduces the basic concepts of generics, the benefits of using generics, generic classes, interfaces, methods and wildcards. In the end, generics and reflective sets can be used to transfer types flexibly, and data information of entities and classes can be obtained by reflection, so as to encapsulate some frameworks and components. If there are any mistakes, please criticize and correct them, and hope to make progress together. Thank you.

Posted by badboy1245 on Sat, 10 Aug 2019 02:41:07 -0700