一文读懂java泛型机制

1. 什么是泛型

Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。

2. 泛型的作用

在泛型出现以前,我们考虑这么一个场景,当我们使用List只是存储字符串数据的时候,在泛型没有出现之前,则对应的代码如下:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
List data = new ArrayList();
data.add("233");
data.add(123); // 加入整型数据
String s = (String) data.get(1);
List data = new ArrayList(); data.add("233"); data.add(123); // 加入整型数据 String s = (String) data.get(1);
List data = new ArrayList();
data.add("233");
data.add(123); // 加入整型数据

String s = (String) data.get(1);

在上面中,通过ArrayList创建了一个集合,这种声明方式主要有以下缺点:

  1. 存储数据的时候无法做数据类型校验
  2. 取数据的时候需要判断数据类型,以及需要做数据类型的转换
  3. 在实际使用过程中,易出错

基于以上的问题,当我们需要共用代码的时候,并且对输入数据类型做校验时,就引入了泛型

3. 泛型的使用方式

泛型的基本使用主要包含了三种方式:泛型类,泛型接口,泛型方法

3.1 泛型类

下面通过一个简单的实例查看泛型类的使用:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class GenericType<T>{
private T value;
public GenericType(T value) {
this.value = value;
}
public T getValue() {
return this.value;
}
public void setValue(T t) {
this.value = t;
}
}
public class GenericType<T>{ private T value; public GenericType(T value) { this.value = value; } public T getValue() { return this.value; } public void setValue(T t) { this.value = t; } }
public class GenericType<T>{
    private T value;

    public GenericType(T value) {
        this.value = value;
    }

    public T getValue() {
        return this.value;
    }

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

在这个实例中,主要包含了以下几个部分:

  • T只是标识,代表了具体的类型
  • 类中包含了T的成员变量,该成员变量的类型是在泛型类定义的时候确定
  • 在方法中可以根据泛型类型设置值和取值,也是根据泛型类定义的时候确认

通过以上的类型定义,我们具体的使用方式为:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public static void main(String[] args) {
GenericType<Long> type = new GenericType<Long>(2L);
type.setValue(123L);
type.setValue("2323"); // 类型校验异常,报错
System.out.println(type.getValue()); // 123
}
public static void main(String[] args) { GenericType<Long> type = new GenericType<Long>(2L); type.setValue(123L); type.setValue("2323"); // 类型校验异常,报错 System.out.println(type.getValue()); // 123 }
public static void main(String[] args) {
    GenericType<Long> type = new GenericType<Long>(2L);
    type.setValue(123L);

    type.setValue("2323"); // 类型校验异常,报错
    System.out.println(type.getValue()); // 123
}

通过泛型的定义,我们通过<>的方式为泛型指定具体的类型,因此我们在使用setValue()方法可以帮助我们对类型进行校验,当我们设置setValue("2323")时,将会导致编译错误。

在泛型类型的时候,我们也可以同时为设置多个泛型标记,例如:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class MultiGenericType<K, V, T> {
private K key;
private V val;
private T t;
public MultiGenericType(K key, V val, T t) {
this.key = key;
this.val = val;
this.t = t;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getVal() {
return val;
}
public void setVal(V val) {
this.val = val;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
public class MultiGenericType<K, V, T> { private K key; private V val; private T t; public MultiGenericType(K key, V val, T t) { this.key = key; this.val = val; this.t = t; } public K getKey() { return key; } public void setKey(K key) { this.key = key; } public V getVal() { return val; } public void setVal(V val) { this.val = val; } public T getT() { return t; } public void setT(T t) { this.t = t; } }
public class MultiGenericType<K, V, T> {
    private K key;
    private V val;
    private T t;

    public MultiGenericType(K key, V val, T t) {
        this.key = key;
        this.val = val;
        this.t = t;
    }

    public K getKey() {
        return key;
    }

    public void setKey(K key) {
        this.key = key;
    }

    public V getVal() {
        return val;
    }

    public void setVal(V val) {
        this.val = val;
    }

    public T getT() {
        return t;
    }

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

则使用方式和单个泛型的使用方式一样,对应的使用方式为:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public static void main(String[] args) {
MultiGenericType<String, String, Integer> type = new MultiGenericType<>("2", "6", 3);
type.setKey("234");
type.setT(23);
type.setVal("34");
}
public static void main(String[] args) { MultiGenericType<String, String, Integer> type = new MultiGenericType<>("2", "6", 3); type.setKey("234"); type.setT(23); type.setVal("34"); }
public static void main(String[] args) {
        MultiGenericType<String, String, Integer> type = new MultiGenericType<>("2", "6", 3);
        type.setKey("234");
        type.setT(23);
        type.setVal("34");
    }

3.2 泛型接口

其实接口也是一个类型,所以泛型接口的使用方式和类型的使用方式是一样的,使用实例如下:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public interface GenericInterface<T> {
T getVal();
}
public interface GenericInterface<T> { T getVal(); }
public interface GenericInterface<T> {

    T getVal();
}

则在使用该泛型接口的时候,可以传递具体类型或者也可以传递泛型标识,例如:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class GenericInterfaceImpl implements GenericInterface<Integer> {
@Override
public Integer getVal() {
return null;
}
}
class GenericInterfaceImpl2<T> implements GenericInterface<T> {
@Override
public T getVal() {
return null;
}
}
public class GenericInterfaceImpl implements GenericInterface<Integer> { @Override public Integer getVal() { return null; } } class GenericInterfaceImpl2<T> implements GenericInterface<T> { @Override public T getVal() { return null; } }
public class GenericInterfaceImpl implements GenericInterface<Integer> {
    @Override
    public Integer getVal() {
        return null;
    }
}

class GenericInterfaceImpl2<T> implements GenericInterface<T> {

    @Override
    public T getVal() {
        return null;
    }
}

3.3 泛型方法

泛型方法与类中的泛型声明是独立的体系,在实体方法或者静态方法上都可以声明泛型,例如:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class GenericType<T>{
private T value;
public GenericType(T value) {
this.value = value;
}
public GenericType() {}
public T getValue() {
return this.value;
}
public void setValue(T t) {
this.value = t;
}
/**
*
* @param clazz 泛型T的class对象
* @return T 确定了返回值为泛型类型
* @param <T> 声明泛型类型
*/
public static <T> T getObject(Class<T> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/**
*
* @param clazz 泛型T的class对象
* @return T 确定了返回值为泛型类型
* @param <T> 声明泛型类型
*/
public <T> T get(Class<T> clazz) {
return getObject(clazz);
}
}
public class GenericType<T>{ private T value; public GenericType(T value) { this.value = value; } public GenericType() {} public T getValue() { return this.value; } public void setValue(T t) { this.value = t; } /** * * @param clazz 泛型T的class对象 * @return T 确定了返回值为泛型类型 * @param <T> 声明泛型类型 */ public static <T> T getObject(Class<T> clazz) { try { return clazz.newInstance(); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } /** * * @param clazz 泛型T的class对象 * @return T 确定了返回值为泛型类型 * @param <T> 声明泛型类型 */ public <T> T get(Class<T> clazz) { return getObject(clazz); } }
public class GenericType<T>{
    private T value;

    public GenericType(T value) {
        this.value = value;
    }

    public GenericType() {}

    public T getValue() {
        return this.value;
    }

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

    /**
     *
     * @param clazz 泛型T的class对象
     * @return T 确定了返回值为泛型类型
     * @param <T> 声明泛型类型
     */
    public static <T> T getObject(Class<T> clazz) {
        try {
            return clazz.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     *
     * @param clazz 泛型T的class对象
     * @return T 确定了返回值为泛型类型
     * @param <T> 声明泛型类型
     */
    public <T> T get(Class<T> clazz) {
        return getObject(clazz);
    }
}

为了证明泛型方法和泛型类的关系,在上面的实例中,在泛型类中定义了两个泛型方法:

  • <T>表明了对应的方法为泛型方法,当在方法上声明就是泛型方法,在类型上声明就是泛型类
  • 方法返回值T, 表明了方法的返回值为T,并且根据定义的实际调用的类型返回
  • 方法参数Class<T>: 这里定义了T的具体的class对象,泛型T是无法被实例化的,因此需要具体的class对象创建T的具体实例

4. 泛型擦除

在泛型被编译之后,实际在编译后的字节码中是看不到泛型泛型信息,例如我们有如下泛型类声明:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class Reference<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class Reference<T> { private T value; public void setValue(T value) { this.value = value; } public T getValue() { return value; } }
public class Reference<T> {
    private T value;

    public void setValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

我们通过查看字节码信息“javap -c Reference.class“, 有如下信息:

以上字节码编译过后可以看到,最终在字节码中存储的是Object的类型。

在上面泛型方法中,也可以通过javap -c GenericType.class的命令查看编译后的字节码信息:

通过以上的两个字节码的查看,证实了泛型在编译后被擦除的事实,实际上存储的是Object对象。但是在具有上边界和下边界上,却有些不同的地方,将在下面中介绍。

泛型擦除本身具有写局限性:

  1. <T>不能是基本类型,因为实际类型是Object, int无法转换为Object类型,只能使用Integer
  2. 无法获取带有泛型的Class对象
  3. 无法判断带泛型的类型。例如:t instanceof Pair<String>. 这种写法是不被允许的
  4. 不能实例化泛型标识T
  5. 不恰当的覆写方法。例如:public boolean equals(T t); 这个方法是不被允许的,因为T最终会被编译成为Object对象,而equals方法来自于Object对象,因此这种覆写不会被允许。

5. 泛型的继承和子类型

在Java中,只要类型存在继承或者实现关系,则可以将子类分配给父类使用。这也是多态使用的一种方式,在泛型中,这种多态也是支持。例如定义一下泛型类:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
package com.java.demo.generic;
/**
* @author xianglujun
* @date 2023/2/27 16:39
*/
public class GenericSubTypeDemo<T> {
private T val;
public GenericSubTypeDemo() {}
public GenericSubTypeDemo(T val) {
this.val = val;
}
public T getVal() {
return val;
}
public void setVal(T val) {
this.val = val;
}
public static void main(String[] args) {
GenericSubTypeDemo<Number> demo = new GenericSubTypeDemo<>();
demo.setVal(12); // 设置Integer
demo.setVal(12L); // 设置Long
demo.setVal(123.3D); // 设置Double
GenericSubTypeDemo<Integer> intDemo = new GenericSubTypeDemo<>();
demo = intDemo; // 编译错误
}
}
package com.java.demo.generic; /** * @author xianglujun * @date 2023/2/27 16:39 */ public class GenericSubTypeDemo<T> { private T val; public GenericSubTypeDemo() {} public GenericSubTypeDemo(T val) { this.val = val; } public T getVal() { return val; } public void setVal(T val) { this.val = val; } public static void main(String[] args) { GenericSubTypeDemo<Number> demo = new GenericSubTypeDemo<>(); demo.setVal(12); // 设置Integer demo.setVal(12L); // 设置Long demo.setVal(123.3D); // 设置Double GenericSubTypeDemo<Integer> intDemo = new GenericSubTypeDemo<>(); demo = intDemo; // 编译错误 } }
package com.java.demo.generic;

/**
 * @author xianglujun
 * @date 2023/2/27 16:39
 */
public class GenericSubTypeDemo<T> {
    private T val;

    public  GenericSubTypeDemo() {}
    public GenericSubTypeDemo(T val) {
        this.val = val;
    }

    public T getVal() {
        return val;
    }

    public void setVal(T val) {
        this.val = val;
    }

    public static void main(String[] args) {
        GenericSubTypeDemo<Number> demo = new GenericSubTypeDemo<>();
        demo.setVal(12); // 设置Integer
        demo.setVal(12L); // 设置Long
        demo.setVal(123.3D); // 设置Double
       GenericSubTypeDemo<Integer> intDemo = new GenericSubTypeDemo<>();
       demo = intDemo; // 编译错误
    }
}

因为Integer, Double, Long都是Number的子类,因为向Number中设置值都是正常的。但是GenericSubTypeDemo<Integer>与GenericSubTypeDemo<Number>之间并不存在继承关系,因此不能直接赋值。

6. 泛型类及其子类

在上面的实例中,泛型类或者泛型接口是可以被继承或者实现的,我们以JDK框架中的Collection为例:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
List<String> list = arrayList;
Collection<String> collection = list;
}
public static void main(String[] args) { ArrayList<String> arrayList = new ArrayList<>(); List<String> list = arrayList; Collection<String> collection = list; }
public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        List<String> list = arrayList;
        Collection<String> collection = list;
    }

在泛型类继承和实现上,只要他们的泛型类型一致,则本身的继承关系没有发生改变。则对应关系为:

7. 泛型上下边界

考虑一下类型,当我们实现两个数相加时,具体实现如下:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class NumberCountUtil {
public static double add(Pair<Number> pair) {
return pair.getFirst().doubleValue() + pair.getLast().doubleValue();
}
public static class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
}
public class NumberCountUtil { public static double add(Pair<Number> pair) { return pair.getFirst().doubleValue() + pair.getLast().doubleValue(); } public static class Pair<T> { private T first; private T last; public Pair(T first, T last) { this.first = first; this.last = last; } public T getFirst() { return first; } public T getLast() { return last; } } }
public class NumberCountUtil {

    public static double add(Pair<Number> pair) {
        return pair.getFirst().doubleValue() + pair.getLast().doubleValue();
    }

    public static class Pair<T> {
        private T first;
        private T last;

        public Pair(T first, T last) {
            this.first = first;
            this.last = last;
        }

        public T getFirst() {
            return first;
        }

        public T getLast() {
            return last;
        }
    }
}

在实际调用的时候,通过Pair<Number>是可以正常编译和运行的:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
double sum = add(new Pair<Number>(1,3));
double sum = add(new Pair<Number>(1,3));
double sum = add(new Pair<Number>(1,3));

但是当将参数替换为Pair<Integer>时,则会编译出错:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
double sum = add(new Pair<Integer>(1,3));
double sum = add(new Pair<Integer>(1,3));
double sum = add(new Pair<Integer>(1,3));

编译时提示:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
java: 不兼容的类型: com.java.demo.generic.NumberCountUtil.Pair<java.lang.Integer>无法转换为com.java.demo.generic.NumberCountUtil.Pair<java.lang.Number>
java: 不兼容的类型: com.java.demo.generic.NumberCountUtil.Pair<java.lang.Integer>无法转换为com.java.demo.generic.NumberCountUtil.Pair<java.lang.Number>
java: 不兼容的类型: com.java.demo.generic.NumberCountUtil.Pair<java.lang.Integer>无法转换为com.java.demo.generic.NumberCountUtil.Pair<java.lang.Number>

这是因为,我们在上面讨论继承关系时,Pair<Integer>并不是Pair<Number>的子类,因此不能够直接传入参数。

7.1 泛型上边界extends

为了解决以上的问题,我们可以通过<? extends Number>方式来确定泛型的上边界,该边界使得接收的参数类型变得更加广泛,可以接收:

  • Number本身作为参数
  • Number的子类作为参数

我们将上面add方法进行改造,让add方法能够接收所有数字类型的参数,并进行计算:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public static double add(Pair<? extends Number> pair) {
return pair.getFirst().doubleValue() + pair.getLast().doubleValue();
}
public static double add(Pair<? extends Number> pair) { return pair.getFirst().doubleValue() + pair.getLast().doubleValue(); }
public static double add(Pair<? extends Number> pair) {
        return pair.getFirst().doubleValue() + pair.getLast().doubleValue();
    }

则我们使用时,则Pair<Integer>也可以作为参数传递:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
double sum = add(new Pair<Integer>(1,3));
double sum = add(new Pair<Integer>(1,3));
double sum = add(new Pair<Integer>(1,3));

我们再将add方法做些改变,修改对应的声明如下:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public static double add(Pair<? extends Number> pair) {
pair.setFirst(new Integer(pair.getFirst().intValue() + 100)); // 编译报错
return pair.getFirst().doubleValue() + pair.getLast().doubleValue();
}
public static double add(Pair<? extends Number> pair) { pair.setFirst(new Integer(pair.getFirst().intValue() + 100)); // 编译报错 return pair.getFirst().doubleValue() + pair.getLast().doubleValue(); }
public static double add(Pair<? extends Number> pair) {
        pair.setFirst(new Integer(pair.getFirst().intValue() + 100)); // 编译报错
        return pair.getFirst().doubleValue() + pair.getLast().doubleValue();
    }

这里会导致在编译的时候无法通过,主要原因:

  • 当在调用add方法的时候传入Pair<Double>时,这时是满足上限的约定的
  • 但是在方法中继续调用setFirst方法的时候,Pair<Double>和Pair<Integer>两者类型不匹配会导致异常

这里就引出了泛型上边界的一个限制

方法参数签名setFirst(? extends Number)无法传递任何Number的子类型给setFirst(? extends Number)

通过上面的分析,我们的到泛型边界的一个特别重要的作用:

在传递的方法参数的时候,可以防止方法向集合中新增元素,或者修改同样具有泛型边界定义的元素的值,这是对对象的数据具有一定的保护作用。

当我们对上面的代码进行反编译如下:

可以看出,泛型上边界<? extends Number>使用的时候,在编译时,使用的是Number类型,因此和不指定上边界时,使用Object存在一定的差别

7.2 泛型下边界super

在泛型上边界中,我们知道Pair<Integer>并不是Pair<Number>的子类,因此在如下方法中:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public static void set(Pair<Integer> pair, Integer first, Integer last) {
pair.setFirst(first);
pair.setLast(last);
}
public static void set(Pair<Integer> pair, Integer first, Integer last) { pair.setFirst(first); pair.setLast(last); }
public static void set(Pair<Integer> pair, Integer first, Integer last) {
       pair.setFirst(first);
       pair.setLast(last);
   }

在使用set方法的时候,我们不能传入Pair<Number>的定义,然后正确执行该方法。

在java中可以使用泛型下边界super来实现, 这样我们就可以传入Pair<Number>, Pair<Object>, Pair<Integer>这样的参数。我们使用super改造上面的方法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public static void set(Pair<? super Integer> pair, Integer first, Integer last) {
pair.setFirst(first);
pair.setLast(last);
}
public static void set(Pair<? super Integer> pair, Integer first, Integer last) { pair.setFirst(first); pair.setLast(last); }
public static void set(Pair<? super Integer> pair, Integer first, Integer last) {
    pair.setFirst(first);
    pair.setLast(last);
}

通过super改造后的方法,则表示了可以接收Integer以及Integer的父类声明的Pair类型

则我们可以正常使用一下代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public static void main(String[] args) {
Pair<Integer> pair = new Pair<Integer>(1,3);
double sum = add(pair);
set(pair, 1, 3);
Pair<Number> numberPair = new Pair<>(23, 14);
set(numberPair, 12, 16);
}
public static void main(String[] args) { Pair<Integer> pair = new Pair<Integer>(1,3); double sum = add(pair); set(pair, 1, 3); Pair<Number> numberPair = new Pair<>(23, 14); set(numberPair, 12, 16); }
public static void main(String[] args) {
    Pair<Integer> pair = new Pair<Integer>(1,3);
    double sum = add(pair);
    set(pair, 1, 3);

    Pair<Number> numberPair = new Pair<>(23, 14);
    set(numberPair, 12, 16);
}

这里重点关注下Pair中的方法,在上面的声明中,实际上对应的setFirst的方法为:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public void setFirst(? super Integer)
public void setFirst(? super Integer)
public void setFirst(? super Integer)

因此,这个时候我们传入Integer类型的参数进入是没有问题,当我们尝试在执行中加入一下代码时:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Integer l = pair.getLast();
Integer l = pair.getLast();
Integer l = pair.getLast();

将会造成编译报错,报错信息为:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
java: 不兼容的类型: capture#1, 共 ? super java.lang.Integer无法转换为java.lang.Integer
java: 不兼容的类型: capture#1, 共 ? super java.lang.Integer无法转换为java.lang.Integer
java: 不兼容的类型: capture#1, 共 ? super java.lang.Integer无法转换为java.lang.Integer

这里主要原因在于super的用法,我们考虑在向setFirst设置参数时,是可以设置Number, Integer, Object类型的参数,我们设置Number的参数的时候,然后getFirst用Integer接收,将导致类型的转换异常,因此这里不能直接使用Integer来接收结果值。但是可以通过Object来接收结果。

因此,以上报错的地方,可以修改为:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Object l = pair.getLast();
Object l = pair.getLast();
Object l = pair.getLast();

因此,这里将不会产生编译异常。这主要是考虑到了类型的转换带来的隐藏的问题。

因此,我们可以得出结论,在<? super Integer>声明的泛型,在方法内部只能够写,不能够读。当然这里要除开Object读的情况。

我们还是将字节码文件进行反编译,看下编译器如何处理super这种下边界的限制:

下边界编译的处理,最终是处理成为了Object类型,这里也可以解释为什么getLast方法不能直接使用Integer来接收了。

7.3 对比extends和super通配符

<? extends T>类型和<? super T>类型的区别在于:

  • <? extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);
  • <? super T>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)。

一个是允许读不允许写,另一个是允许写不允许读。

7.4 PECS原则

在具体使用场景中,extends和super两者该如何选择呢?主要遵循:Producer Extends Consumer Super:

  • 如果需要返回泛型标识T,则为生产者,这个时候就需要使用extends进行声明
  • 如果需要写入标识T,则为消费者,这时就需要使用super声明

可以查看下Collections#copy方法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
..
}
public static <T> void copy(List<? super T> dest, List<? extends T> src) { .. }
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
   ..
}

在这个实例中:

  • 需要返回T的src是生产者,因此使用extends声明
  • 需要写入T的dest是消费者,因此使用super声明

7.4 无限通配符

无限通配符是对泛型没有做限定,例如声明List<?>, Pair<?> 这种就是没有限定的通配符。这种通配符在没有指定extends或者super的时候,同事具有两者的缺点:

  • 不允许调用set(T)方法并传入引用(null除外);
  • 不允许调用T get()方法并获取T引用(只能获取Object引用)。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public static void set(Pair<?> pair, Integer obj) {
pair.setLast(obj); // 编译异常
pair.setLast(null); // 编译通过
Object r = pair.getLast(); // 正常
Integer i = pair.getLast(); // 编译异常
}
public static void set(Pair<?> pair, Integer obj) { pair.setLast(obj); // 编译异常 pair.setLast(null); // 编译通过 Object r = pair.getLast(); // 正常 Integer i = pair.getLast(); // 编译异常 }
public static void set(Pair<?> pair, Integer obj) {
    pair.setLast(obj); // 编译异常
    pair.setLast(null); // 编译通过
    Object r = pair.getLast(); // 正常
    Integer i = pair.getLast(); // 编译异常
}

因此这种实现,既不能实现写入,也不能读。这可以通过这种方式判断值是否为null。

在大多数情况下,<?>可以使用<T>来进行替换。

但是<?>有个最大的特点,Pair<?>是所有Pair<T>的超类:也就是说,所有的Pair泛型都可以赋值给Pair<?>

8. 泛型的多态

多态为java的特性,泛型的多态说的就是在类中声明了泛型,然后子类继承或者实现泛型类。我们定义泛型类如下:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class ObjReference<T> {
private T val;
public void setVal(T val) {
this.val = val;
}
public T getVal() {
return this.val;
}
}
public class ObjReference<T> { private T val; public void setVal(T val) { this.val = val; } public T getVal() { return this.val; } }
public class ObjReference<T> {

    private T val;

    public void setVal(T val) {
        this.val = val;
    }

    public T getVal() {
        return this.val;
    }
}

定义一个子类:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class IntegerReference extends ObjReference<Integer> {
@Override
public void setVal(Integer val) {
super.setVal(val);
}
@Override
public Integer getVal() {
return super.getVal();
}
}
public class IntegerReference extends ObjReference<Integer> { @Override public void setVal(Integer val) { super.setVal(val); } @Override public Integer getVal() { return super.getVal(); } }
public class IntegerReference extends ObjReference<Integer> {
    @Override
    public void setVal(Integer val) {
        super.setVal(val);
    }

    @Override
    public Integer getVal() {
        return super.getVal();
    }
}

从上面可以看出,在泛型多态上面,其实是方法的重载,而不是重写。如果是方法的重新,那么根据泛型的擦除,那么父类中的方法定义为:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public void setVal(Object obj) {}
public void setVal(Object obj) {}
public void setVal(Object obj) {}

那么,我们可以写一个类,看能否调用到父类方法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public static void main(String[] args) {
IntegerReference reference = new IntegerReference();
reference.setVal(1);
reference.setVal(new Object()); // 编译报错
}
public static void main(String[] args) { IntegerReference reference = new IntegerReference(); reference.setVal(1); reference.setVal(new Object()); // 编译报错 }
public static void main(String[] args) {
    IntegerReference reference = new IntegerReference();
    reference.setVal(1);
    reference.setVal(new Object()); // 编译报错
}

从调用方法可以知道,直接调用父类的Object方法会导致编译报错,那么可以确定确实是方法重载。那么jvm是如何解决这样的事情的呢?答案就是桥接方法

我们通过javap的命令,IntegerReference类型的字节码反编译,得到以下的信息:

在上面的字节码反编译后,我们可以看到setVal和getVal方法分别有两个。首先我们来看setVal方法

setVal(Integer)和setVal(Object)两个方法,而setVal(Object)方法是由编译器生成,在指令中,可以看到做了这么几件事情:

  • 类型的检查,判断传入的类型是否为Integer类型
  • 类型检查通过后,调用setVal(Integer)方法执行

这就是桥接方法的意义,主要作用就在于最终调用实现类的重载方法,这也是JVM为了解决泛型方法重载所采用的策略。

这里我们主要关注下getVal方法,可以看到getVal方法的定义其实很像,唯一不同在于其返回值:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public Integer getVal() {}
public Object getVal() {}
public Integer getVal() {} public Object getVal() {}
public Integer getVal() {}

public Object getVal() {}

在我们平常的开发中,这样的方法定义其实会导致编译不通过的,但是JVM在为了解决这种重载策略时,却能够使用这种定义,这主要是因为JVM中对方法唯一性定义是方法名称+参数+返回值,因此这种编译器加入的代码,在JVM也是能够通过的。

以上就是关于泛型知识的内容,后面将主要介绍泛型和反射的知识,主要讲解集中Type的使用和如何获取到反正真正的类型。

 

参考文章

  1. https://www.liaoxuefeng.com/wiki/1252599548343744/1265105920586976
  2. https://waylau.gitbooks.io/essential-java/content/docs/generics.html
  3. https://pdai.tech/md/java/basic/java-basic-x-generic.html
Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注