JAVA Basic Knowledge | Generics

First, what is generics?

Generics are "parameterized types".

For example, by defining a variable A, we can define this variable as a string type or an integer in the following way.

String A;
Integer A;

Of course, when the type of variable is known, if there is one case, we do not know what type we will need when defining the variable, or when we need to be compatible with various types, how to define it?

In view of the above, we can introduce "generics" to parameterize String and Integer types. When used, specific parameter types are passed in.

The essence of generics is to parameterize the type (by passing in different types to determine the specific type of parameter). That is to say, in the process of using generics, the data type is designated as a parameter. This parameter type can be used in classes, interfaces and methods, which are called generic classes, generic interfaces and generic methods respectively.

2. Generic classes

Computer class, which contains an attribute t of type String.

public class Computer {
   private String t;

   public void set(String t) {
      this.t = t;
   }

   public String get() {
      return this.t;
   }
}

Generic Computer Class

//The "T" here is not a fixed way of writing, but can also be written with characters such as V or M.
public class Computer<T> {

	private T t;

	public void set(T t) {
		this.t = t;
	}

	public T get() {
		return this.t;
	}
}

//Various generic parameters can be passed in
public class Computer<T, V> {

	private T t;
	private V v;

	public void set(T t, V v) {
		this.t = t;
		this.v = v;
	}

	public T getT() {
		return this.t;
	}

	public V getV() {
		return this.v;
	}
}

The advantage of defining generic classes is that we can specify the type of attribute t when needed, which enhances generality.

Computer<String> computer1= new Computer<String>();
Computer<Integer> computer2= new Computer<Integer>();
Computer<Double> computer3= new Computer<Double>();

 

III. Generic Interface

//Define interface Calculatable
public interface Calculatable<T> {
    T execute();
}

//Arguments passed into String type
public class Computer implements Calculatable<String> {

    @Override
    public String execute() {
        return "ok";
    }
}

//Arguments passed into Integer type
public class Calculator implements Calculatable<Integer> {

    @Override
    public Integer execute() {
        return 100;
    }
}

//No specific arguments are passed in, continue to be thrown out, and pass in from the lower level.
public class Phone<V> implements Calculatable<V> {

    private V v;

    @Override
    public V execute() {
        return this.v;
    }
}

 

IV. GENERALIZED METHODS

public class Computer<T> {

    private T t;

    //Not a generic method
    public void set(T t) {
        this.t = t;
    }

    //Not a generic method
    public T get() {
        return this.t;
    }

    //generic method
    //Firstly, <V> between public and return value type is essential. Only methods declaring <V> are generic methods.
    //You can declare multiple generics, such as public < V, M > generic method (V, M)
    public <V> V genericMethod(V v){
        return v;
    }
}

 

Computer<String> computer = new Computer<String>();
Integer v= 100;
System.out.println(computer.genericMethod(v).getClass().toString());

Double v2= 100d;
System.out.println(computer.genericMethod(v2).getClass().toString());

Output results:

class java.lang.Integer
class java.lang.Double

 

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.

The generic method can determine the specific type of V by passing in the arguments.

 

V. Static Methods and Generics

If static methods cannot use generics defined in classes, they need to declare themselves generic if they want to use generics.

public class Computer<T> {    
    public static <V> void get(V v){
        //T; Error reporting, cannot use generics defined in classes
    }
}

 

6. Wildcards and upper and lower boundaries

Take a simple example.

//Fruits
public class Fruit {
    private String name;

    public Fruit(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

fsadfdsfd

//Apple
public class Apple extends Fruit {
    public Apple(String name) {
        super(name);
    }
}
//A bag for fruit
public class GenericHolder<T> {
    private T obj;

    public GenericHolder() {
    }

    public GenericHolder(T obj) {
        this.obj = obj;
    }

    public T getObj() {
        return obj;
    }

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

Test class:

public class AppTest extends TestCase {

    @Test
    public void test() {

        //This is a bag labeled with fruit.
        GenericHolder<Fruit> fruitHolder = new GenericHolder<Fruit>();
        //This is a bag with an apple label.
        GenericHolder<Apple> appHolder = new GenericHolder<Apple>();
        //This is a fruit.
        Fruit fruit = new Fruit("Fruits");
        //This is an apple.
        Apple apple = new Apple("Apple");

        //Now let's put the fruit in.
        fruitHolder.setObj(fruit);
        //Call the way to eat fruit
        eatFruit(fruitHolder);
      
       
        //There's certainly no problem putting fruit in bags labeled with fruit.
        //Now let's put the apple, a fruit subgroup, in this bag.
        fruitHolder.setObj(apple);
        //It's also possible. In fact, there will be an automatic upward transition. apple will be upgraded to Fruit type and then introduced into fruitHolder.
        //But it is no longer possible to assign the extracted object to redApple because the label of the bag is fruit, so the extracted object can only be assigned to the variables of the fruit class.
        //Red Apple = fruitHolder. getObj () cannot be detected by compilation.
        //Call the way to eat fruit
        eatFruit(fruitHolder);

        //If you put an apple label, you can only put an apple.
        appHolder.setObj(apple);
        //At this time, appHolder can not be passed into eatFruit, because GenericHolder < Fruit > and GenericHolder < Apple > are two different types, GenericHolder < Fruit > only allows the introduction of fruit bags.
        // eatFruit(appHolder);
    }

    public static void eatFruit(GenericHolder<Fruit> fruitHolder){
        System.out.println("I am eating. " + fruitHolder.getObj().getName());
    }
}

Implementation results:

I'm eating fruit.
I'm eating apples.

GenericHolder < Fruit > and GenericHolder < Apple > are two different types, so they cannot be compiled.

From the perspective of Java inheritance, we can analyze:

Apple IS-A Fruit

Apple bags NOT-IS-A fruit bags

So the question arises. What if I want the eatFruit method to handle both GenericHolder < Fruit > and GenericHolder < Apple >? And that's a reasonable demand. After all, Apple is a subcategory of Fruit. It can eat fruit. Why can't it eat apples??? If this method is to be overloaded once, there will be some fuss.

At this point, generic boundary symbols have their place of use. Let's see the effect first:

public class AppTest extends TestCase {

    @Test
    public void test() {

        //This is a bag labeled with fruit.
	        GenericHolder<Fruit> fruitHolder = new GenericHolder<Fruit>();
	        //This is a bag with an apple label.
	        GenericHolder<Apple> appHolder = new GenericHolder<Apple>();
	        //This is a fruit.
	        Fruit fruit = new Fruit("Fruits");
	        //This is an apple.
	        Apple apple = new Apple("Apple");

	        //Now let's put the fruit in.
	        fruitHolder.setObj(fruit);
	        //Call the way to eat fruit
	        eatFruit(fruitHolder);

	        //If you put an apple label, you can only put an apple.
	        appHolder.setObj(apple);
	        // At this point, appHolder can be smoothly imported into eatFruit.
	        eatFruit(appHolder);
	        //This generic type? Extended Fruit can't store Fruit, but can be used? Extended Fruit's variable points to the object of GenericHolder<Fruit>.
	        GenericHolder<? extends Fruit> fruitHolder22 = fruitHolder;
//	        FruitHolder 22.setObj (fruit); //Error reporting, fruit cannot be stored
	        System.out.println(fruitHolder22.getObj().getName()+"----");
	        
	        //The extends Fruit variable points to the GenericHolder < apple > object.
	        fruitHolder22 = appHolder;
//	        FruitHolder 22.setObj (apple); //Error cannot store apple
	        System.out.println(fruitHolder22.getObj().getName()+"^^^^");
    }

    public static void eatFruit(GenericHolder<? extends Fruit> fruitHolder){
        System.out.println("I am eating. " + fruitHolder.getObj().getName());
        Fruit fruit1 = new Fruit("Fruit 1");
        //Errors can not be stored in fruit1. Because the introduced parameter fruitHolder is a subclass of Fruit.
        //Error:(35,28) java: Incompatible type: Fruit cannot be converted to capture#1, altogether?
        //fruitHolder.setObj(fruit1);
    }
}

Operation results:

I'm eating fruit.
I'm eating apples.
Fruits -
Apple ^ ^ ^ ^

This is the generic boundary character, expressed in the form of <? Extends Fruit>. Boundary symbol means that nature defines a boundary. Represents that the incoming generic type is not a fixed type, but all types that conform to the rule scope, and defines an upper boundary with the extends keyword.

There are upper boundaries and lower boundaries. In generics, lower boundaries are used in the form of <?Super Fruit>.

These two methods basically solve our previous problems, but at the same time, there are some limitations (PECS principle).

1. The upper bound <? Extends T> cannot be stored in, but can only be taken out.

Don't be too confused, it's easy to understand, because the compiler only knows that Fruit or Fruit's subclass is in the container, but it doesn't know what type it is, so when it saves, it can't judge whether the type of data to be stored is the same as the type of container, so it will reject the set operation. Note: Take out the actual type, such as GenericHolder < Fruit >, then take out Fruit, if point to GenericHolder < Apple >, then take out is Apple.

2. The lower bound <? Super T> can only be assigned to Object variables outward, without affecting inward storage.

Because the compiler only knows that it is Fruit or its parent class, it actually relaxes the type restriction. Fruit's parent class can be stored into objects of type Object, but when it is taken, it can only be used as Object objects.

3. Don't use any wildcards if you want to save and retrieve them.

So if you need to read out frequently, use <? Extends T> and if you need to store in frequently, use <? Super T>.

How to read the source code of some Java collection classes, we can find that we usually combine the two together, such as the following:

public class Collections {
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        for (int i=0; i<src.size(); i++)
            dest.set(i, src.get(i));
    }
}

In my opinion, it should be equivalent to:

public class Collections {
    public static <T> void copy(List<T> dest, List<T> src) {
        for (int i=0; i<src.size(); i++)
            dest.set(i, src.get(i));
    }
}

Keywords: Programming Java Attribute calculator

Added by catalin.1975 on Mon, 23 Sep 2019 17:49:43 +0300