java 泛型 super extend_Java泛型边界问题,super和extends关键字

本文深入探讨Java泛型的引入原因及工作原理,解释了泛型如何确保类型安全性,特别是通过extends和super限定符的应用,帮助读者理解泛型的高级用法。

背景

为什么JDK5要引入泛型,泛型保证参数类型一致性。什么叫类型一致?

假设有继承关系,A

ArrayList list;

list.add(new C());

list.add(new D());

list.add(new B());//compile ERROR

并没有破坏list的类型一致性,因为list被声明参数类型时C,最终list中所有引用对象都是按照C的类型取出。

有了泛型特性之后,Java就应该使用带参数的类型,而不是rawtype。但是为了兼容,仍然支持rawtype使用。

泛型有些看起来奇怪的特性,在假设不能使用rawtype只能使用泛型的情况下就很好理解了。

List 和List没有层次关系

Java给定一个具体的类型参数A之后的泛型List,与给定另一个具体的类型参数X的泛型List之间没有层次关系,不论Object和X类型的层次关系如何。为什么这样设计?

假设

ArrayList list1 = get();

ArrayList list2 = list1;

list2.add(new Object());//reasonable

在拿到list2的代码中,由于list2被声明为ArrayList,因此,可以自然向其中添加Object类型的item,这样,在拿到list1的代码中认为list1的item的类型时一致的,都是String,取出item的时候就可能得到实际为Object的item,这就会导致程序异常。这是很常见的错误发生场景。因此Java禁止他们之间有层次关系。

缺点:无法实现模板方法,接收多种参数类型的泛型。

void recvGen(ArrayList list){};

ArrayList param = get();

recvGen(param);//ERROR, 两者没有关系,所以不能转型,也不能强制转型

为此,Java引入了一种带有通配符的泛型,> super A> extends A>

extends和super和?

void recvGen(ArrayList extends Object> list){};

ArrayList param = get();

recvGen(param);//pass

/***********************/

void recvGen(ArrayList super String> list){};

ArrayList param = get();

recvGen(param);//pass

super或者extends可以定义一大类的泛型,作为给出具体类型参数的泛型的父类。

super或者extends定义的有边界泛型,根据参数类型的层次覆盖判定和具体参数类型泛型之间的层次关系。

不要望文生义

super关键字,用于类的方法中表示指向父类对象的引用。

在泛型边界语法 super X>中指出泛型下界,不表示可以存入X的父类对象,只能存入X及其子类对象,取出的对象一律按照Object。

extends在定义类或接口时表示继承父类,在泛型边界语法 extends X>中指出泛型下界,不表示可以存入具体类的子类对象(实际上不能存入任何指定类型的对象),只表示已经存在其中的所有对象可以按照X类型取出。

假设有继承关系,A

void f( List super C> param){...}

List super C> 是List,List,List,List或者List super B>,List super A>的父类,对于接受List super C>为参数的方法f中,传递上述类型时,可以隐式向上转型,安全。在方法中,取出的item都是Object类型,List super C>取出的类型都是Object,不是C或者其他的具体类。

如果需要cast取出的item为具体类型,程序员自己保证存在List中的对象都是可以安全强制转型的。最好的应用场景是不需要针对具体类型的item进行处理,其次是程序员自己保证所有item可以安全强制转型。

方法f无法接受List或List,cast强制转型也无法通过编译,Java不接受没有层次关系的两个类型的强制转型。

假设方法内

List super C> param;

param.add(new B());// ERROR

param.add(new Object());//ERROR

param.add(new D());//right

List super C>存入item的限制很大而且看起来有点奇怪:任何C的父类和C类型的对象都不能存入,C的子类可以存入。

PECS(producer extends,Consumer super)规则:extends的泛型是生产者,只能从中读取

super限制的泛型可以作为消费者,可以从存入对象。——但是这不是super的主要意义。

为什么List super C> param取出来的是Object,而不是具体类型?

因为在声明param的时候只要求这个param是List super C>,翻译成人话就是声明param是List,List,List,List或者是List super B>,List super A>中的一种,不能保证是具体的哪一种,所以编译器不知道取出的item是哪一种具体类型。如果程序员自己能够保证传入的是同一种具体类型的item,那么自行强制转型,但是编译器只能简单地约定从List super C>中取出的Object类型。

为什么不能向List super C>存入A B Object类型,而可以存入C和D E?

因为存入A B或者Object类型可能会破坏原来List的类型一致性,正是为了避免这个问题才在JDK5引入泛型特性的,因此不能允许。而存入C的子类时,通过隐式转换为C可以保证不因此破坏List中item类型的一致。

既然不能保证取出的item是同一种具体类型,要super有何用?

super的意义在于放大模板方法接受的泛型参数类型,同时提供向这个泛型写入的合法性。

extends的意义也是在于放大模板方法接受的泛型参数类型,牺牲向泛型写入的可能性,提供保证读取出的类型有具体类型的便利性。

extends

extends修饰的泛型是只读的,并且保证读出的类型都是具体类型。

List extends C> void f(){

List extends C> ret = getNewList();

for(int i =0; i<10; i++){

ret.add(new C());//ERROR

ret.add(new D());//ERROR

ret.add(new Object());//ERROR

ret.add(null);

}

List tmp = getNewList();

for(int i =0; i<10; i++){

tmp.add(new C());//ERROR

tmp.add(new D());//pass

tmp.add(new E());//pass

tmp.add(null);//pass

}

ret = tmp;

return ret;

}

一个extends通配的泛型List extends C>,不可以向其中添加任何具体类型,除了null。

道理和List不能接受List向上转型一样,add任何具体类型可能破坏泛型原有的类型一致性,禁止。

有没有使用super比使用extends来限定泛型边界的更好的场景?

大概就是当上界不需要限制的时候,这时如果使用 extends Object>,那么就相当于没有限制。

所以super不是完全可以被extends替代的,多一种选择更好。

总结

super和extends的意义更多是在于模板方法的定义中,模板方法希望放大参数类型从而可以接受更多中的泛型参数。extends是只读的,读出的类型具体。super是读写的,但是写入时只能是更具体的类型,读取时类型只能泛化为Object。

### 回答1: Java中的extendsuper是用于限制参数的关键字extend用于限制参数的上界,表示参数必须是指定类的子类或实现类。例如,List<? extends Number>表示参数必须是Number类或其子类。 super用于限制参数的下界,表示参数必须是指定类的父类或超类。例如,List<? super Integer>表示参数必须是Integer类或其父类。 使用extendsuper可以使参数更加灵活,可以适应不同的场景需求。但是需要注意的是,使用过多的extendsuper可能会导致代码可读性降低,因此需要根据实际情况进行选择使用。 ### 回答2: Java中可以通过实现更强大的类安全代码的复用性。在使用时,我们常常需要了解extendsuper关键字,以便更好地理解使用。 extend关键字 extend关键字用于限定参数的上限,表示参数必须是继承了某个类或实现了某个接口才能被使用。例如: ``` public class GenericClass<T extends Number> { private T data; public GenericClass(T data) { this.data = data; } public T getData() { return data; } } ``` 在上面的类中,使用了T extends Number语法,表示参数T必须是继承了Number类的某个子类,否则编译会报错。这样可以保证data属性的类一定是Number或其子类类super关键字 super关键字用于限定参数的下限,表示参数必须是某个类的父类或某个接口的实现类。例如: ``` public class GenericClass<T super Number> { private T data; public GenericClass(T data) { this.data = data; } public T getData() { return data; } } ``` 在上面的类中,使用了T super Number语法,表示参数T必须是Number类的父类,即可以是Number类本身或其父类类。这样可以保证data属性的类一定是Number类或其父类类。 需要注意的是,extendsuper关键字只能用于参数,而不能用于普通类。在实际使用中,我们需要根据具体的情况来灵活地使用extendsuper关键字,以便在代码中实现更好的类安全更高的代码复用性。 ### 回答3: Java中的是一种非常强大的语言特性,允许我们使用一种类来表示多种类,在编写类安全且可重用的代码时经常用到。在的使用中,我们有时需要约束所能使用的,使得代码更加类安全。在这种情况下,我们可以使用extend super 关键字。 在 Java 中,使用 extends 关键字可以为参数添加上限限制。通过 extends 关键字,我们可以指定参数必须是某个类或接口的子类。这样做的好处是能够防止代码中使用无效的类参数,从而保证代码的类安全性。值得注意的是,我们只能使用 extends 关键字来添加上限限制,而不能添加下限限制。 下面是一个例子,演示如何使用 extends 关键字: ``` public class Box<T extends Number> { private T t; public Box(T t) { this.t = t; } public T get() { return t; } public void set(T t) { this.t = t; } } ``` 可以看到,Box 类使用了 extends 关键字,将参数 T 限制为 Number 类或其子类。在此之后,我们就可以在 Box 类中安全地使用 Number 类或其子类,如 Integer、Double 等。 除了上限限制之外,Java 还提供了另一种参数的约束方式:super 关键字。使用 super 关键字,我们可以限制参数必须是某个类或接口的父类。这个约束通常用于需要消费对象的情况中,例如集合类的 add 方法。通过为参数添加 super 关键字,我们可以保证只能添加某个类的子类到集合中,从而防止集合中出现无效的元素。类似地,我们也只能使用 super 关键字添加下限限制,而不能添加上限限制。 这里是一个例子,演示如何使用 super 关键字: ``` public class Box<T> { private List<? super T> list; public Box() { list = new ArrayList<>(); } public void add(T t) { list.add(t); } public void addAll(List<? extends T> otherList) { list.addAll(otherList); } } ``` 可以看到,Box 类使用了 super 关键字来限定集合中存储的类,只能是 T 的父类或其本身。这样做的好处是,保证添加到集合中的元素都是类安全的,避免了程序运行时出现错误。 总之,参数的 extend super 关键字Java使用的重要工具,能够帮助我们实现类安全的代码。在实际使用中,根据具体场景灵活运用这两个关键字,能够提高代码的可读性可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值