Java基础 泛型

问题1. B继承A 为什么 List<B> 不能赋值给List<A>

假设有如下代码

class A{}  class B extends A{}

List<B> b = new ArrayList<>();
List<A> a = b; // 编译报错

List<A> List<B> 在运行时泛型被抹除, 都是List类型,所以在运行时并无继承关系, A和B是声明List中存放的元素类型, List<> 尖括号中的泛型并不在类型继承体系中

编译报错的原因是,a指向b集合,a是List<A> 类型,意味这a.add(new A()) 这种完全是可以的, 因为LIst<A>可以接受 A类型, 但是a实际类型是 List<B> b.add(A()) 这样的操作是不可接受的,所以这样的操作编译不通过

问题2. 既然List<B>不能赋值给List<A> 那如何解决重复代码

假设有如下代码

class A {
    public void func() {
    }
}

class B extends A{
}

List<B> b = new ArrayList<>()
List<A> a = new ArrayList<>()

public void funcA(List<A> al) {
    for (A a : al) {
		a.func()
    }
}

public void funcB(List<B> bl) {
    for (B b : bl) {
		b.func()
    }
}

funcA(a)
funcB(b)

public void funcD(List<A> al) {
     al.add(new B())
}

public void funcE(List<B> bl) {
    bl.add(new B())
}

funcA和funcB都是List类型 都是调用A类中的func方法, 但是因为List<B> 不能赋值给List<A> 相同的逻辑却要写两遍,解决方案可以是用不带泛型的List接然后get元素之后强转成A,但是这个操作要被推迟到运行时

public void func(List l) {
    for (Object o : l) {
		((A)o).func();
	}
}

同样 List<A>和List<B> 都可以进行add(new B())的操作,但是List<A> 不能向下强转成List<B> 相同的逻辑要写两边,解决方案可以是不带泛型的List接,然后调用add操作, 这样完全没问题,但同样也会被推迟到运行时

public void func(List l) {
	l.add(new B());
}

这样的操作导致 为了解决泛型问题而忽略泛型!不优雅, 太不优雅了!

3. 怎么解决上面的问题

针对问题2中的相同代码问题,

funcA和funcB, List<A>中存储的都是A类型,List<B>中存储的都是B类型,而B类型一定是A类型, 所以List<B> 可以认为存储的都是A类型, 如果我们把List<B>中的B向上抽象一下, 表示成 List<? extends A> List<A>中的A也抽象一下,表示成List<? extends A>, 这样类型就一样了, 那么是不是就可以解决上面的问题

public void func(List<? extends A> l) {
      	for (A a : l) {
			a.func()
		}
}

funcC和funcD List<B>中可以存储B类型 List<A> 可以存储A类型, 可以存储A类型,那一定可以存储B类型,因为B继承于A, 既然funcC和funcD的代码都是要王List<A>和List<B>中放入B类型, B类型比A类型更具体, 那么是不是可以把 List<A> 向下具化成 List<? super B> 同样List<B>也具化成List<? super B> 这样类型就一样了,是不是就可以解决上面的问题

public viod func(List<? super B> l) {
		l.add(new B())
}

相比之下,优雅,太优雅了!

4.把泛型向上抽象或者向下具化又带来了什么问题

把 List<B> 向上抽象 变成了 List<? extends A>
List<B> b = new ArrayList<>()
b.add(new B())
List<? extends A> a = b;
a.add(new B())  // 编译报错,向上抽象丢失了具体类型,所以当放一个具体类型时报错

原先List<B> b 在这个b对象上进行add(new B()) 的操作是完全没问题的,但是现在List<? extends A> 执行add(new B()) 编译不通过

原因也比较简单 泛型的B信息被替换成了? extends A 之前的B泛型丢了, List<? extends A> 唯一能确定的是我指向的集合里的元素都是A类型,但是具体是那个具体类型不知道,可能是List<C> 向上抽象转来的,也可能是List<B> 向上抽象来的, 那往List<? extends A> 这个集合里add(new B()) 我就没法确定他的正确性,万一是List<C>来的呢, 这不把B加到了List<C>里的吗

回过头我们看一下Collection中add 方法定义

boolean add(E e);

如果我们的集合是List<? extend A> 那么这里的E, IDE的提示是capture of ?extends A 类型, capture of ? extends A 这个表示编译器在计算表达式时,从代码中推断出来具体类型, 这就有意思了, 熟悉的java方法通常参数类型是给定的,比如A类型 B类型 但是这里的? 需要一个A或者A的子类型,具体是A的哪种子类型,编译器不知道, 可能是C,可能是D,也可能是A,所以除了null可以被传递外,其他值类型无法匹配,所以add方法基本不可用

再看看get方法定义

E get(int i);

如果我们的集合是List<? extend A> 那么这里的E, IDE的提示是返回一个capture of ?extends A 类型, 我们需要一个变量去接收这种类型, 由于都是A的子类, 所以用 A去接 A a = list.get(0) 没有问题,符合类型推导

把 List<A> 向下具化 变成了 List<? super B>
List<A> a = new ArrayList<>()
A a1 = a.get(0);
List<? super B> b = a;
A a1 = b.get(0);  // 编译报错, 向下具化, 丢失了父类型, b的父类型时什么,编译器不知道,那肯定不能用A接, 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值