Java Basic Supplements ● Generics

foreword

Since Java 5 , generics have become part of the Java programming language. Before generics, every object read from a collection had to be converted. If someone accidentally inserts an object of the wrong type, the conversion processing will fail at runtime.

With generics, you can tell the compiler which object types are accepted in each collection. The compiler automatically converts your insertions and tells you at compile time if you inserted objects of the wrong type.

This makes the program more secure and clearer. However, for most newbies, it is still difficult to understand and take advantage of these advantages of generics.

This article is to tell you how to understand and use generics to the greatest extent, and make the whole process as simple as possible.

1. What are generics?

A class or interface declared with one or more type parameters is called a generic class or a generic interface.
The definition format of a generic is: the name of the class or interface + angle brackets <the actual type parameter corresponding to the generic type>

For example: the List interface is a generic interface, the full name of the interface: List<E>, it has only one type parameter E.

As commonly used in our actual development: String list List<String>, which is a parameterized type, representing a collection of element type String, String is the actual type parameter corresponding to the generic type parameter E.

2. Original ecological type

As mentioned at the beginning of the article, generics only appeared in Java 5. Can a class like interface List that did not use generics work properly? What should we call it?
The answer is: the previous code works fine, it is called: primitive type, that is, a generic name without any actual type parameters , for example: the primitive type corresponding to List<String> is List.
The raw type is like removing the generic parameter information from the type declaration, and its role is mainly to be compatible with code before generics.

Knowing the function description of the original ecological type, everyone should understand and pay attention: please do not use the original ecological type!
For example, in the following code, before Java added generics, this definition was completely fine. Even at the beginning of Java 9, it is still legal, but it has no reference value.

List nameList = new ArrayList();
nameList.add("listen");
nameList.add(1024);

The original intention of the nameList collection is to store names of type String, but if you accidentally put another type such as Integer into the collection, even though the compiler will warn you, this code can still be compiled and run.
It's not until you get the data from nameList that you have to cast due to lack of generic information, at which point the error surfaced and you get an exception: ClassCastException .

for (Object name : nameList) {
    // 此处出现异常:ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    String nameStr = (String) name;
    System.out.println(nameStr);
}

One of the most important of good programming habits is that errors should be found as soon as possible, preferably at compile time . In the above example, the error is not found until runtime, like a time bomb in the code, and the daylily will be cold when it explodes.
One more word: Although it is said that the development can use artificial conventions and comments to explain that this List can only be put into the String type, but the code that relies on artificial conventions is extremely fragile and unsound!

Summary: Native types are only provided for compatibility with legacy code prior to the introduction of generics. So you shouldn't use primitive types, because then you lose all the safety and descriptive advantages of generics.

3. Unlimited wildcard types

If you want to use generics, but are unsure or don't care about the actual parameter types, you can use a question mark instead , like: List<?>
Example: If you need to define operations on a collection, but you don't care and don't need to Knowing the type of elements in the collection, then the role of unlimited wildcards is reflected.

 /**
 * 计算两个集合中包含相同元素的数量
 * 注:本段代码只是为了演示无限制通配符的作用,不代表实现是最优解
 *
 * @param list1
 * @param list2
 * @return 相同元素数量
 * @author Turbolisten
 */
public static int findSameCount(List<?> list1, List<?> list2) {
    int count = 0;
    for (Object obj : list1) {
        if (list2.contains(obj)) {
            count++;
        }
    }
    return count;
}

Seeing the above code, some students may have questions: It seems that the above example can also use the original ecological type List. What is the difference between the unlimited wildcard and the original ecological type? Does this question mark really work?
The answer is: still mentioned above, the original ecological type is unsafe. If you use the original ecological type List in the above method, you will be able to add any element to list1 or list2 in the method. Causes exceptions in other places where this collection is used.
Although the question mark with unlimited wildcards does not limit the type of elements, it acts as a generic identifier. The compiler will prompt an obvious error, preventing you from putting any element (except null) into list1 and list2, because in the absence of With wildcards restricted, the compiler can't guess at all what type of object you'll get.

4. Restricted wildcard types

Imagine such a scenario, you don't want to repeat the code, and you write a method to operate a List<E> collection that defines the generic parameter type. This method benefits the masses, and you feel extremely happy.
As the project iterates, one day this parameter type derives many subtypes, for which you have to write a lot of the same methods, but change the generic parameter type to a subtype,
even if you know these methods The operations are based on the public parameters or methods provided by the parent class or interface, so you are very tired and troubled.

Then at this time, the restricted wildcard type comes in handy. Example: a fruit class Fruit, only two attribute names - name, price - price, and later it has its subclasses: Apple and Banana, you need a method to traverse the lowest price fruit in the collection

public class Fruit {
    private String name;
    private Double price;
}

public class Apple extends Fruit {

    public Apple(String name, Double price) {
        super(name, price);
    }
}

public class Banana extends Fruit {

    public Banana(String name, Double price) {
        super(name, price);
    }
}

public static void main(String[] args) {
    List<Apple> appleList = new ArrayList<>();
    appleList.add(new Apple("红富士苹果", 1.1));
    appleList.add(new Apple("乔纳金苹果", 2.2));

    List<Banana> bananaList = new ArrayList<>();
    bananaList.add(new Banana("北蕉", 1.3));
    bananaList.add(new Banana("仙人蕉", 2.4));

    System.out.println(findMinPrice(appleList).getName());
    System.out.println(findMinPrice(bananaList).getName());
}

/**
 * 查询最小价格的水果
 * 注:示例代码
 *
 * @param fruitList
 * @return 返回最小价格的水果, 集合为空则返回null
 * @author Turbolisten
 */
public static Fruit findMinPrice(List<? extends Fruit> fruitList) {
    if (null == fruitList || fruitList.size() == 0) {
        return null;
    }
    return fruitList.stream().min(Comparator.comparingDouble(Fruit::getPrice)).get();
}

List<? extends Fruit> is an example of the use of restricted wildcards, which restricts the parameter of this method to only Fruit or its subtype, but does not restrict which subtype it is. This brings a lot of flexibility to this method, and no matter how many subclasses of Fruit will be derived in the future, this method will always work correctly.
The form List<? extends Fruit> is also called: upper bound wildcard (generic covariance), which restricts the parameter type to only the base class Fruit or its descendants.

There is another form corresponding to it: List<? super Apple> is called: the lower bound wildcard (generic contravariance) constrains the parameter type to be only the subclass Apple or its parent and ancestor.

5. Generic methods

Just as classes can benefit from generics, so can methods. Static utility methods are especially suitable for generalization, such as the Collections class in the java.util class library, most of which are generic methods. Such as sort:

public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

The method declares a type parameter T, which represents the parameter list set, the comparator Comparator, and the return value, all of which are of the same parameter type. One or more (comma-separated) type parameters of the declaration are placed between the method's modifiers and the return value.

We can write a simple generic method ourselves: such as copy object properties

/**
 * 复制对象
 *
 * @param source 源 要复制的对象
 * @param target 目标 复制到此对象
 * @return 返回类型的实例,如果source或class空则返回null
 */
public static <T> T copy(T source, Class<T> target) {
    if (source == null || target == null) {
        return null;
    }
    T newInstance = target.newInstance();
    BeanUtils.copyProperties(source, newInstance);
    return newInstance;
}

This method constrains both parameters and return value to be the same type parameter.

6. Naming of generic type parameters

By convention, it is usually composed of a single letter so that it can be easily distinguished when using ordinary class or interface names.

In general, five types are commonly named:
T represents any type,
E represents the element type of the set,
K and V represent the key and value types of the map,
X represents the exception, and
R usually represents the return type of the function.
Of course, these are not absolute Yes, even if you use U, S, B to name the type parameters, there is no problem, but usually the rules agreed by everyone are used, which is easy for everyone to understand and improve the readability of the code.

7. Use generics first

In general, parameterizing collection declarations and using the generic methods provided by the JDK are not too difficult. Writing your own generic classes is a little harder, but worth the time to learn.

For example, in our daily back-end API writing, we usually need to return a unified wrapper object to the front end, and the data in the wrapper object returned in each request is different. Without using generics, you might write:

@Data
public class ResponseDTO {

    private Integer code;

    private String msg;

    private Object data;

    public static void main(String[] args) {
        ResponseDTO responseDTO = new ResponseDTO();
        responseDTO.setCode(1);
        responseDTO.setMsg("success");
        responseDTO.setData(new UserEntity());
        UserEntity userEntity = (UserEntity) responseDTO.getData();

        ResponseDTO responseDTO2 = new ResponseDTO();
        responseDTO2.setCode(1);
        responseDTO2.setMsg("success");
        responseDTO2.setData(new EmployeeEntity());
        EmployeeEntity employeeEntity = (EmployeeEntity) responseDTO2.getData();
    }
}

Because Object is the parent class of all classes, this design also works.
But as mentioned earlier, the biggest drawback of such a design is the insecurity of type conversion. If the back-end friends need to call your method, when using data, either you have already agreed on the class type, or you can do it yourself Find the code of setData and see what is set.

The code in this case is prone to type conversion exceptions , so simply transforming this class with generics can easily avoid the above situation.

@Data
public class ResponseDTO<T> {

    private Integer code;

    private String msg;

    private T data;

    public static void main(String[] args) {
        ResponseDTO1<UserEntity> responseDTO = new ResponseDTO1();
        responseDTO.setCode(1);
        responseDTO.setMsg("success");
        responseDTO.setData(new UserEntity());
        UserEntity userEntity = responseDTO.getData();

        ResponseDTO1<EmployeeEntity> responseDTO2 = new ResponseDTO1();
        responseDTO2.setCode(1);
        responseDTO2.setMsg("success");
        responseDTO2.setData(new EmployeeEntity());
        EmployeeEntity employeeEntity =  responseDTO2.getData();
    }
}

Because the generic parameter type information is carried, the compiler can clearly know the parameter type, avoid the unsafety of coercion, and do not need to worry about the headache of ClassCastException.

All in all, the emergence of Java5 generics brings advantages and improves the robustness and readability of the code. It is necessary for us to seriously study the basics of generics. Due to space limitations, this time I only talk about some basic concepts and daily usage of generics. There are other more in-depth concepts of generics, such as: generic erasure, recursive type restrictions, etc., which are left to you. The soldiers searched and researched by themselves, and quietly improved it.

end

This article refers to and cites the introduction to generics in the book "Effective Java", but due to the descriptions and examples in the original book, it is really obscure. I try to use concise and easy-to-understand text and examples here, hoping to bring you some help.

Finally, this is my first article on the 1024 official account. I am in a hurry and I feel uneasy in my heart. If there are any mistakes or imprecise places in it, please feel free to let me know. If you have other content you want to see, please leave a message, we may see you in the next issue~

If you are interested in our articles, please pay attention to our WeChat public account, which has the latest and most complete articles~

contact us

1024 Innovation Lab

1024Lab official WeChat account ( add me to pull you into the group! ):

SmartAdmin official WeChat account

Official account QR code-12com.jpg

Official Donation (WeChat)

Appreciation code.jpg

{{o.name}}
{{m.name}}

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=324075829&siteId=291194637