Generics are that simple

Posted by MK27 on Tue, 23 Nov 2021 15:30:11 +0100

1, What is generics

Java generic design principle: as long as there is no warning at compile time, there will be no ClassCastException exception at run time

The so-called genericity: postpone the time when the type is explicit until the object is created or the method is called

Parameterization type:

  • Pass the type as an argument
  • < data type > can only be a reference type

Related terms:

  • E in ArrayList < E > is called type parameter variable

  • The Integer in ArrayList < Integer > is called the actual type parameter

  • The whole is called ArrayList < E > generic type

  • The entire ArrayList < integer > is called ParameterizedType

2, Why do I need generics

Early Java used Object to represent any type, but the downward transformation had the problem of strong transformation, so the program was not very safe

First, let's imagine: what would happen to a collection without generics

  • Collection and Map collections have no restrictions on the type of elements. Originally, all Dog objects were loaded in my collection, but there was no syntax error when storing Cat objects in the collection.
  • When an Object is thrown into a collection, the collection does not know what the type of element is, but only knows that it is an Object. Therefore, in get(), Object is returned. To get the Object outside, you also need to cast

With generics:

  • More concise code [no coercion]
  • The program is more robust [as long as there is no warning at compile time, there will be no ClassCastException exception at run time]
  • Readability and stability [when writing a collection, it defines the type]

2.1 generics can use enhanced for loops

After specifying the type of collection, we can use the enhanced for loop

//Create collection object
ArrayList<String> list = new ArrayList<>();

list.add("hello");
list.add("world");
list.add("java");

//Traversal, because the type is clear. We can enhance for
for (String s : list) {
    System.out.println(s);
}

3, Generic basis

3.1 generic classes

A generic class defines a generic type on a class. When users use the class, they define the type... In this way, the user knows what type the class represents... When users use it, they don't have to worry about forced conversion and run-time conversion exceptions

Generics defined on a class can also be used in class methods!

/*
    1:Define generics on classes
    2:Type variables are defined on classes and can also be used in methods
 */
public class ObjectTool<T> {
    private T obj;

    public T getObj() {
        return obj;
    }

    public void setObj(T obj) {
        this.obj = obj;
    }
}

The type you want to use is specified when you create it. When used, the class will be automatically converted to the type that the user wants to use.

Test code

public static void main(String[] args) {
    //Create an object and specify the element type
    ObjectTool<String> tool = new ObjectTool<>();

    tool.setObj(new String("Zhong Fucheng"));
    String s = tool.getObj();
    System.out.println(s);


    //Create an object and specify the element type
    ObjectTool<Integer> objectTool = new ObjectTool<>();
    /**
         * If I pass in String in this object, it won't pass at compile time
         */
    objectTool.setObj(10);
    int i = objectTool.getObj();
    System.out.println(i);
}

3.2 generic methods

Generic classes have been introduced earlier. Generic types defined on classes can also be used in methods

Now, we may only need to use generics on a certain method... The outside world only cares about the method and does not care about other properties of the class... In this case, we will make a fuss about defining generics on the whole class.

Defining generic methods.... generics are defined first and then used

//Defining generic methods
public <T> void show(T t) {
    System.out.println(t);
}

Test code

public static void main(String[] args) {
    //create object
    ObjectTool tool = new ObjectTool();

    //When calling a method, the type of the parameter passed in and the type of the return value
    tool.show("hello");
    tool.show(12);
    tool.show(12.5);
}

3.3 subclasses derived from generic classes

We have defined a generic class earlier. A generic class is a class with the feature of genericity. It is essentially a Java class, so it can be inherited

How was it inherited?? There are two cases

  1. Subclasses specify the type parameter variables of generic classes
  2. Subclass ambiguous type parameter variable of generic class

3.3.1 subclasses specify the type parameter variables of generic classes

generic interface

/*
    Define generics on interfaces
 */
public interface Inter<T> {
    public abstract void show(T t);

}

Classes that implement generic interfaces

/**
 * Subclasses specify the type parameter variables of generic classes:
 */

public class InterImpl implements Inter<String> {
    @Override
    public void show(String s) {
        System.out.println(s);

    }
}

3.3.2 the subclass does not specify the type parameter variable of the generic class

When the subclass does not specify the type parameter variable of the generic class, when the subclass is used by the outside world, the type parameter variable also needs to be passed in, and the type parameter variable needs to be defined on the implementation class

/**
 * Subclass ambiguous type parameter variable of generic class:
 *      The implementation class should also define the < T > type
 *
 */
public class InterImpl<T> implements Inter<T> {

    @Override
    public void show(T t) {
        System.out.println(t);

    }
}

Test code:

public static void main(String[] args) {
    //Test the first case
    //Inter<String> i = new InterImpl();
    //i.show("hello");

    //Second case test
    Inter<String> ii = new InterImpl<>();
    ii.show("100");
}

It is worth noting that:

  • If the implementation class overrides the method of the parent class, the type of return value should be the same as that of the parent class!
  • A generic declared on a class is valid only for non static members

3.4 type wildcards

Why do I need type wildcards???? Let's look at a demand

Now there is a requirement: the method receives a set parameter, traverses the set and prints out the set elements. What should I do?

Before we learn generics, we might do this:

public void test(List list){
    for(int i=0;i<list.size();i++){
        System.out.println(list.get(i));
    }
}

The above code is correct, but a warning will appear when compiling, saying that the type of collection elements is not determined... This is not elegant

So we've learned generics. What should we do now?? Some people may do this:

public void test(List<Object> list){
    for(int i=0;i<list.size();i++){
        System.out.println(list.get(i));
    }
}

There is nothing wrong with this syntax, but it is worth noting here that the test() method can only traverse the collection loaded with objects!!!

Emphasize that < Object > in generic types is not inherited as before, that is, list < Object > and list < string > are irrelevant!!!!

What now??? We don't know what type of elements are loaded in the list collection. List < objcet > doesn't work... So Java generics provide type wildcards?

So the code should be changed as follows:

public void test(List<?> list){
    for(int i=0;i<list.size();i++){
        System.out.println(list.get(i));
    }
}

? The wildcard number indicates that any type can be matched, and any Java class can be matched

Now it is very noteworthy that when we use? No. wildcard: you can only call object type independent methods, and cannot call object type related methods.

Remember, you can only call object independent methods, not object type related methods. Because the specific type is not known until it is used by the outside world. That is, in the List set above, I can't use the add() method. Because the add() method throws the object into the collection, and now I don't know what the type of the object is.

3.4.1 setting the upper limit of wildcards

First, let's take a look at where to set the upper limit of wildcards

Now, I want to receive a List set, which can only operate on elements of numeric type (Float, Integer, Double, Byte and other numeric types are OK). What should I do???

We learned about wildcards, but if we use wildcards directly, the set can not only operate on numbers. Therefore, we need to set the upper limit of wildcards

List<? extends Number>

The above code indicates that the elements loaded in the List collection can only be subclasses of Number or themselves

public static void main(String[] args) {


//The List collection is loaded with Integer, which can be called
List<Integer> integer = new ArrayList<>();
test(integer);

//The List collection is loaded with strings, and an error is reported during compilation
List<String> strings = new ArrayList<>();
test(strings);

}


public static void test(List<? extends Number> list) {

}

3.4.2 setting the lower limit of wildcard

Since we have explained how to set the upper limit of wildcards, it is not strange to set the lower limit of wildcards. Let's go straight to grammar

//Only Type or parent class of Type can be passed in
<? super Type>

It is not uncommon to set the lower limit of wildcards. There are... In the TreeSet set. Let's take a look

public TreeSet(Comparator<? super E> comparator) {
	this(new TreeMap<>(comparator));
}

What's the use of it?? Let's think about it. When we want to create a variable of TreeSet < String > type, we pass in a Comparator that can compare the size of String.

There are many choices for this Comparator. It can be Comparator < String >, or the parent class whose type parameter is String, such as Comparator < objcet >

This is very flexible. That is, as long as it can compare the string size

It is worth noting that no matter whether you set the upper or lower limit of wildcards, you cannot operate methods related to objects. As long as wildcards are involved, their types are uncertain!

3.5 wildcard and generic methods

Most of the time, we can use generic methods instead of wildcards

//Use Wildcards 
public static void test(List<?> list) {

}

//Using generic methods
public <T> void  test2(List<T> t) {

}

Both of the above two methods are OK... Now the question arises, shall we use wildcards or generic methods??

principle:

  • If the types of parameters are dependent, or the return value is dependent on the parameters. Then use generic methods
  • If there are no dependencies, use wildcards, which will be more flexible

3.6 generic erasure

Generics are provided for the javac compiler to use. They are used to limit the input type of the collection and let the compiler insert illegal data into the collection at the source code level. However, after the compiler compiles the java program with generics, the generated class file will no longer contain generics information, so that the running efficiency of the program will not be affected. This process is called "erasure".

3.6.1 compatibility

JDK5 puts forward the concept of generics, but JDK5 did not have generics before. That is, generics need to be compatible with collections below JDK5.

When a collection with generic characteristics is assigned to an old version of the collection, the generics are erased.

It is worth noting that it retains the upper limit of the type parameter.

        List<String> list = new ArrayList<>();

        //The type is erased and the upper limit of the type is reserved. The upper limit of String is Object
        List list1 = list;

What if I assign a set without type parameters to a set with type parameters??

        List list = new ArrayList();
        List<String> list2 = list;

It also does not report an error, but simply prompts "unchecked conversion"

4, Application of generics

When we write web pages, we often have multiple Daos. We have to write several Daos every time, which will be a little troublesome

So what effect do we want?? Write only one abstract DAO, and other Daos will have corresponding methods as long as they inherit the abstract DAO.

To achieve this effect, generics must be used. Because in an abstract DAO, it is impossible to know which DAO will inherit itself, so we do not know its specific type. A generic type specifies its specific type only when it is created.

Abstract DAO

public abstract class BaseDao<T> {

    //Simulate hibernate
    private Session session;
    private Class clazz;


    //Which subclass calls this method, and the class obtained is the type handled by the subclass (very important)
    public BaseDao() {
        Class clazz = this.getClass();  //You get subclasses
        ParameterizedType  pt = (ParameterizedType) clazz.getGenericSuperclass();  //BaseDao<Category>
        clazz = (Class) pt.getActualTypeArguments()[0];
        System.out.println(clazz);

    }


    public void add(T t){
        session.save(t);
    }

    public T find(String id){
        return (T) session.get(clazz, id);
    }

    public void update(T t){
        session.update(t);
    }

    public void delete(String id){
        T t = (T) session.get(clazz, id);
        session.delete(t);
    }

}

By inheriting the abstract DAO, the implementation class has the corresponding methods of adding, deleting, modifying and querying.

CategoryDao

public class CategoryDao extends BaseDao<Category> {

}

BookDao

public class BookDao extends BaseDao<Book> {

}

Topics: Java