Those things about Java generics

1. Overview of Generics

1.1. Why use generics

Without generics, you can only use specific types or Object types when writing code, and you ca n't do what the user wants to use . For example, when creating a method, the formal parameters need to specify the data type to be used. At the beginning of the method creation , the data type that the method can handle has been determined, which greatly limits the flexibility of programming. Because of this, there is generic programming where you decide what a specific type is when you use it .

1.2. What are generics

Generic: Broad, common, non-specific things. Generics is the use of symbols to represent unspecific types at the beginning of the definition, and the specific types are dynamically specified when they are used. You should understand this kind of generic programming design idea. The benefits of using generics are that the code is more concise, more flexible, and the program is more robust (there is no warning at compile time, and there will be no class cast exception - ClassCastException at runtime).

2. Generic interface, class, method

Generics allow use when defining interfaces, classes, and methods , and will be dynamically specified when declaring variables, creating objects, and calling methods.

2.1. Generic interface

Define a generic interface: such as the List interface in a collection

// 定义接口时指定泛型:E,E类型在接口中就可以作为类型使用
public interface List<E> extends Collection<E>{
    ……
    boolean add(E e);
    Iterator<E> iterator();
    ……
}
// 定义接口时指定泛型:K 和 V,K和V类型在接口中就可以作为类型使用
public interface Map<K,V>{
    ……
    Set<K> keySet();
    Collection<V> values();
    Set<Map.Entry<K, V>> entrySet();
    ……
}

Use generic interface: The generic type E of the List interface is specified as the concrete type String when used

public static void main(String[] args) {
    List<String> list = new ArrayList<>();// 指定泛型类型E=String
    list.add("我只认识字符串");//boolean add(E e); 等价于boolean add(String e);
    Iterator<String> iterator = list.iterator();//Iterator<E> iterator();

    while (iterator.hasNext()){
        String next = iterator.next();//不需要强转为String
        System.out.println(next);
    }
}

As for how to use the generic interface Map<K,V> collection, you can write it yourself and feel it.

2.2. Generic classes

plain generic class

define generic class

public class DemoFx<D> {
    private D dd;
    public D getDd(){
        return this.dd;
    }
    public void setDd(D dd){
        this.dd = dd;
    }
}

Use generic classes

public static void main(String[] args) {
    DemoFx<String> stringDemoFx = new DemoFx<>();
    stringDemoFx.setDd("我是字符串类型");
    System.out.println(stringDemoFx.getDd());
}

Inheritance and implementation of generic classes

Define a generic class: Take the ArrayList class as an example, inherit the generic abstract class and implement the generic interface:

public class ArrayList<E> extends AbstractList<E> 
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    ……
    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }
    ……
}

Use generic classes

public static void main(String[] args) {
    List<String> list = new ArrayList<String>();// 指定泛型类型E=String
    list.add("我只认识字符串");
    String s = list.get(0);// 返回值为String
}

2.3. Generic methods

Defining generic methods: or the ArrayList case

public class ArrayList<E> extends AbstractList<E> 
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    ……
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of as runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }
    ……
}

Use generic methods: public <T> T[] toArray(T[] a)

public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("s1");
    list.add("s2");
    list.add("sn");
    // public <T> T[] toArray(T[] a) 
    String[] strings = list.toArray(new String[list.size()]);
    System.out.println(Arrays.asList(strings));
}

3. Type wildcards

3.1. Using type wildcards

The wildcard representation symbol is the question mark < ? >, which is an unknown type and can match any type, also known as an unbounded wildcard .

Comparing the methods created by "wildcard" and "generic"

// 通配符定义
public void foreach(List<?> list){
    for (int i =0 ;i<list.size();i++) {
        Object o = list.get(i);
        System.out.println(o.toString());
    }
}
// 泛型定义
public <T> void foreach2(List<T> list){
    for(T t : list){
        System.out.println(t.toString());
    }
}
// 使用
public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("s1");
    list.add("s2");
    list.add("sn");
    Demo demo = new Demo();
    demo.foreach(list); // 通配符
    demo.foreach2(list); // 泛型
}

Both wildcards and generics can achieve the same effect, and the generic method can also use the generic type defined by itself, while the "?" of the wildcard cannot be used as a data type, so only Object can be used in the wildcard method case. Receives the elements of a list, which is also the disadvantage of wildcards: there is no way to determine what the unknown type is.

So what is the use of wildcards?

Wildcards are a special case of generics that can be used in formal parameters without defining an unknown type.

Difference between generics and wildcards

  • The Java compiler infers the generic type [T] as a T type, and allows T type variables in the code block; while the wildcard [?] is inferred as an unknown type, and there is no ? type variable;

  • Class<T> needs to depend on T, and <T> needs to be specified when the method is declared, while Class<?> does not need;

This may be a better understanding of generics and wildcards: generics emphasize types, and wildcards emphasize symbols .

Class<?> represents any type, but it is not equivalent to Class<Object>. The former can only insert null if the type does not match , but the latter can insert Object or any subclass of Object object.

For example: You cannot add objects of any type to List<?> list, except null

3.2. Type upper limit

Wildcard upper limit: <? extends Demo>, the upper limit of wildcard [?] is Demo type, that is, the scope of <? extends Demo> is Demo or its subclass type.

Generics upper limit: <T extends Demo>, the same as wildcard understanding. The upper limit of the type is shown in the figure

 

Case :

Create three classes DemoFather, Demo, DemoChildren, the relationship is as shown above

public class DemoTest {

    public static void main(String[] args) {
        List<DemoChildren> demoChildrens = new ArrayList<>();
        demoChildrens.add(new DemoChildren());
        demoChildrens.add(new DemoChildren());

        DemoTest test = new DemoTest();
        test.testDemo(demoChildrens); // 通配符
        test.testDemo2(demoChildrens);// 泛型

    }

    // 通配符上限:控制list 集合允许的类型范围为Demo或其子类
    public void testDemo(List<? extends Demo> list){
        // 若无上限,这里只能用Object类型代替Demo类型
        for (Demo demo : list){
            System.out.println(demo.toString());
        }
    }

    // 泛型上限:控制list 集合允许的类型范围为Demo或其子类
    public <T extends Demo> void testDemo2(List<T> list){
        for (T t : list){
            System.out.println(t.toString());
        }
        // or
        for(Demo demo:list){
            System.out.println(demo.toString());
        }
    }

}

The upper limit of generics is determined at the time of definition <T extends Demo>; wildcards directly determine the upper limit on formal parameters <? extends Demo>. In fact, it is well understood. The upper limit of the type is to add a range "upper limit" based on the general writing method, which is both extends xxx.

Some examples of source code

// 接口泛型上限
public interface ObservableArray<T extends ObservableArray<T>> extends Observable {……}
// 抽象类泛型上限
public abstract class ArrayListenerHelper<T extends ObservableArray<T>> extends ExpressionHelperBase {……}
public abstract class CellBehaviorBase<T extends Cell> extends BehaviorBase<T> {……}
// 方法泛型上限
public static <T extends Number> ReadOnlyLongProperty readOnlyLongProperty(final ReadOnlyProperty<T> property) {……}
// 通配符上限
void putAll(Map<? extends K, ? extends V> m);

3.3. Type lower limit

The lower limit of wildcard: <? super Demo>, the lower limit of wildcard [?] is the Demo type, that is, the scope of <? super Demo> is the parent type of Demo.

Generic lower bound: None . Mainly because type lower bounds can be confusing and not particularly useful. Some explanation of why there is no lower bound for type parameters: http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ107

public static void main(String[] args) {
    Demo demo = new Demo();
    List<Demo> demos = new ArrayList<>();
    demos.add(demo);
    DemoTest test = new DemoTest();
    test.testSuper(demos);

    DemoFather demoFather = new DemoFather();
    List<DemoFather> demoFathers = new ArrayList<>();
    demoFathers.add(demoFather);
    DemoTest test2 = new DemoTest();
    test2.testSuper(demoFathers);
}

public void testSuper(List<? super Demo> list){
    // 虽然有下限,但无法直接使用Demo类型接收参数
    for (Object obj : list){
        System.out.println(obj.toString());
    }
}

Although there is a lower limit, it is not possible to directly use the Demo type to receive parameters. It's like "upcasting" and "downcasting", upcasting is automatic, downcasting requires forced conversion; type upper bounds can use the largest type (parent class) to receive a smaller type (subclass) than it is, and the type lower bound It is not possible to use the smallest type (subclass) to accept a potentially larger type (superclass).

Some examples of source code

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    ……
    public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }
}

public class Arrays {
    public static <T> void sort(T[] a, int fromIndex, int toIndex,
                                Comparator<? super T> c) {
        if (c == null) {
            sort(a, fromIndex, toIndex);
        } else {
            rangeCheck(a.length, fromIndex, toIndex);
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, fromIndex, toIndex, c);
            else
                TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
        }
    }
}

What about the generic letters E, K, V, T?

  • E stands for Element,

  • K represents Key,

  • V represents Value,

  • T represents Type,

  • N represents Number,

  • ? for unknown type

Except [?], you can write other generic symbols as strings, but pay attention to readability. Generally, they are represented by a single capital letter, otherwise the code will not be concise and difficult to read.

4. Generics implementation principle - type erasure

Assigning an object with generic information to another variable reference without generic information will erase the type information .

Case 1: The upper limit of generics is not specified, and it is Object after type erasure

public static void main(String[] args) {
    // 定义集合泛型E = String
    List<String> stringArrayList = new ArrayList<>();
    stringArrayList.add("test1");
    // 获取到的类型为:String
    String s = stringArrayList.get(0);
    // 把带有泛型信息的stringArrayList 对象赋给不确定泛型的List
    List listObject = stringArrayList;
    // listObject 只知道get的类型为Object,而不是String
    Object obj = listObject.get(0);
}

Case 2: Specify a generic upper limit, and type erasure is the upper limit type

public class DemoFather {}
public class Demo extends DemoFather{}
public class DemoChildren<T extends DemoFather> {
    private T t;
    public T getT(){
        return this.t;
    }
    public void setT(T t){
        this.t= t;
    }
}
// 测试public class DemoTest {
 public static void main(String[] args) {
        //class DemoChildren<T extends DemoFather>,指定泛型T=Demo类型
        DemoChildren<Demo> demoChildren = new DemoChildren<Demo>();
        // 拿到的方法类型确实是T=Demo类型
        Demo demo = demoChildren.getT();
        // 把带有泛型信息的 demoChildren 对象赋给不确定泛型的demoChildren2
        DemoChildren demoChildren2 =demoChildren;
        // 再来获取方法的类型时,变为了上限的DemoFather类型
        DemoFather demoFather = demoChildren2.getT();
    }
}

in conclusion:

When the generic upper limit is specified, the type of the upper limit is the type after type erasure; otherwise, the Object type, because all classes in Java inherit the Object class by default.

So the generic class of case 2 looks like this in the compilation phase

public class DemoChildren {
    private DemoFather t;
    public DemoFather getT(){
        return this.t;
    }
    public void setT(DemoFather t){
        this.t= t;
    }
}
// 原来的泛型类,对比一下
public class DemoChildren<T extends DemoFather> {
    private T t;
    public T getT(){
        return this.t;
    }
    public void setT(T t){
        this.t= t;
    }
}

Guess you like

Origin blog.csdn.net/m0_62396648/article/details/124394925
Recommended