Java之泛型边界

1.泛型通配符

?:可以通配任意类型

观察下面代码:

public void test1(Collection<Integer> c) {

}

public void test2(Collection<String> c) {

}

public void test3(Collection<Double> c) {

}

test1方法【只能】接收泛型是Integer类型的集合对象

test2方法【只能】接收泛型是String类型的集合对象

test3方法【只能】接收泛型是Double类型的集合对象

原因:由于泛型的类型之间没有多态,所以=号俩边的泛型类型必须一致

在这种情况下,就可以使用通配符(?)来表示泛型的父类型:

public void test(Collection<?> c) {

}

注意,这时候test方法中的参数类型,使用了泛型,并且使用问号来表示这个泛型的类型,这个问号就是通配符,可以匹配所有的泛型类型

test方法可以接收 泛型是任意引用类型的 Collection集合对象

public static void main(String[] args) {
    Test t = new Test();
    t.test(new ArrayList<String>());
    t.test(new ArrayList<Integer>());
    t.test(new ArrayList<Double>());
    t.test(new ArrayList<任意引用类型>());
}

使用通配符(?)所带来的问题:

Collection<?> c;     
c = new ArrayList<String>();

//编译报错
//因为变量c所声明的类型是Collection,同时泛型类型是通配符(?)
//那么编译器也不知道这个?将来会是什么类型,因为这个?只是一个通配符
//所以,编译器不允许使用变量c来向集合中添加新数据。
c.add("hello");

//编译通过
//但是有一个值是可以添加到集合中的,null
//集合中一定存的是引用类型,null是所有引用类型共同的一个值,所以一定可以添加进去。
c.add(null);

虽然使用通配符(?)的集合,不能再往其中添加数据了,但是可以遍历集合取出数据:

public static void main(String[] args) {

    ArrayList<String> list = new ArrayList<>();
    list.add("hello1");
    list.add("hello2");
    list.add("hello3");
    list.add("hello4");

    Collection<?> c = list;
    
    //编译报错
    //c.add("hello5");

    for(Object obj : c) {
        System.out.println(obj);
    }
}

2. 泛型边界

在默认情况下,泛型的类型是可以任意设置的,只要是引用类型就可以。

如果在泛型中使用extendssuper关键字,就可以对泛型的类型进行限制。即:规定泛型的上限下限

泛型的上限

  • 格式类型名<? extends 类型 > 对象名称
  • 意义只能接收该类型及其子类型

泛型的下限

  • 格式类型名<? super 类型 > 对象名称
  • 意义只能接收该类型及其父类型
1)泛型上限
  • 例如:List<? extends Number> list

  • 将来引用list就可以接收泛型是Number或者Number子类型的List集合对象

public static void main(String[] args) {

    List<? extends Number> list;
    //list可以指向泛型是Number或者Number【子】类型的集合对象
    list = new ArrayList<Number>();
    list = new ArrayList<Integer>();
    list = new ArrayList<Double>();

    //编译报错,因为String不是Number类型,也不是Number的子类型
    //list = new ArrayList<String>();
}

能表示数字的类型都是Number类型的子类型,例如Byte Short Integer Long等

2)泛型下限
  • 例如:List<? super Number> list

  • 将来引用list就可以接收泛型是Number或者Number父类型的List集合对象

public static void main(String[] args) {

    List<? super Number> list;
    //list可以指向泛型是Number或者Number【父】类型的集合对象
    list = new ArrayList<Number>();
    list = new ArrayList<Serializable>();
    list = new ArrayList<Object>();

    //编译报错,因为String不是Number类型,也不是Number的父类型
    //list = new ArrayList<String>();
    
    //编译报错,因为Integer不是Number类型,也不是Number的父类型
    //list = new ArrayList<Integer>();
}

泛型中extendssuper对比:

  • 使用extends可以定义泛型的【上限】,这个就表示将来泛型所接收的类型【最大】是什么类型。可以是这个最大类型或者它的【子类型】。
  • 使用super可以定义泛型的【下限】,这个就表示将来泛型所接收的类型【最小】是什么类型。可以是这个【最小类型】或者它的【父类型】。

3. 类型擦除

泛型类型仅存在于编译期间,编译后的字节码和运行时不包含泛型信息,所有的泛型类型映射到同一份字节码。

由于泛型是JDK1.5才加入到Java语言特性的,Java让编译器擦除掉关于泛型类型的信息,这样使得Java可以向后兼容之前没有使用泛型的类库和代码,因为在字节码(class)层面是没有泛型概念的。

例如,定义一个泛型类Generic是这样的:

class Generic<T> {
    private T obj;

    public Generic(T o) {
        obj = o;
    }

    public T getObj() {
        return obj;
    }
}

那么,Java编译后的字节码中Generic相当于这样的:(类型擦除,变为原始类型Object)

class Generic {
    private Object obj;

    public Generic(Object o) {
        obj = o;
    }

    public Object getObj() {
        return obj;
    }
}

例如,

public static void main(String[] args) {
    //编译报错
    //ArrayList<Integer>和new ArrayList<Long>在编译期间是不同的类型
    //ArrayList<Integer> list = new ArrayList<Long>();
	
    //但是编译完成后,它们对应的是同一份class文件:ArrayList.class
    ArrayList<Integer> list1 = new ArrayList<Integer>();
	ArrayList<Long> list2 = new ArrayList<Long>();
    // 大家大致能看懂即可,后续反射章节会补充
	System.out.println(list1.getClass() == list2.getClass()); //true
}

注意,泛型信息被擦除后,所有的泛型类型都会统一变为原始类型:Object

例如,

//编译报错
//因为在编译后,泛型信息会被擦除
//所以下面两个run方法不会构成重载,本质上都是 public void run(List list)
public class Test {
    public void run(List<String> list){

    }

    public void run(List<Integer> list){

    }
}

可以看出,Java的泛型只存在于编译时期,泛型使编译器可以在编译期间对类型进行检查以提高类型安全,减少运行时由于对象类型不匹配引发的异常。

但是在编译成功后,所有泛型信息会被擦除,变为原始类型Object。

4. 泛型小结

概念描述示例
泛型类(Generic Class)使用泛型参数化的类,可以在实例化时指定具体类型。class MyClass<T> { ... }
泛型接口(Generic Interface)使用泛型参数化的接口,可以在实现时指定具体类型。interface MyInterface<T> { ... }
泛型方法(Generic Method)使用泛型参数化的方法,可以在调用时指定具体类型,与所属类的泛型参数可以不同。public <T> void myMethod(T data) { ... }
类型参数(Type Parameter)在泛型中使用的占位符类型,用于指定参数化类型。<T><K, V>
通配符(Wildcard)用于表示未知类型的通配符,用于灵活处理不确定类型的泛型。List<?>List<? extends Number>List<? super Integer>
类型限定(Type Bounds)限定泛型的类型范围,可以是具体类型、接口或类。<T extends Number><T extends Comparable<T>>
类型擦除(Type Erasure)泛型在编译时会进行类型擦除,生成原始类型的字节码,运行时无法获取泛型类型的具体信息。-
  • 27
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值