泛型边界和通配符

一、边界

边界使得可以在用于泛型的参数类型上设置限制条件,这使得你可以强制规定泛型可以应用的类型,在潜在的一个更为重要的效果是你可以按照自己的边界类型来调用方法。


因为有擦除的存在,泛型类型在运行时,就会被擦除掉他的类型信息。普通类型一般会被擦除到Object类,此时你只能调用Object类型方法。但是如果你将参数类型限制为某个类型的子类型,那么擦除就会擦除到这个某个类,那你就可以调用这某个类型的方法。

interface Bfirst{
	public void f();
}
interface Bsecond{
	public void s();
}
class third{
	public int x,y,z;
}

interface fourth{
	public void four();
}
//This don't work:类型一定要现在前面,然后才是接口
//class BoundHolder<T extends first & second & third>{}

class BoundHolder<T extends third & Bfirst & Bsecond>{
	T item;
	BoundHolder(T item){
		this.item = item;
	}
	void f(){                                       //类型参数T有边界first,能调用first的方法f();
		item.f();
	}
	void s(){                                       //类型参数T有边界second,能调用second的方法s();  
		item.s();
	}
    int  getX(){                                    //类型参数有边界third,拥有私有域
    	return item.x;
    }
    int gerY(){
    	return item.y;
    }
    int getZ(){
    	return item.z;
    }
}

class SunBoundHolder<T extends third & Bfirst & Bsecond & fourth>
extends BoundHolder<T>{                            //可以通过继承添加边界限制
	public SunBoundHolder(T item){
		super(item);
	}
	public void fourth(){                          //类型参数T可以调用four()方法;
		super.item.four();
	}
}

class Bounds extends third implements Bfirst,Bsecond {
	public void f(){
		System.out.println("Bounds f()");
	}
	public void s(){
		System.out.println("Bounds s()");
	}
}

public class BasicBounds {
	public static void main(String[] args){
		BoundHolder<Bounds> newHolder =  new BoundHolder<Bounds>(new Bounds());
		newHolder.f();
		newHolder.s();
	}
}/*Output
Bounds f()
Bounds s()
*///~
二、通配符

通配符是在泛型表达式中使用问号?代表参数类型。

(1)数组的可协变性和泛型的不可协变性

使用数组时,我们可以向导出类型的数组赋予基类型的数组引用:

class Fruit{
}

class Apple extends Fruit{}
class Jonathan extends Apple{}
class Orange extends Fruit{}

public class CovarianArray {
	public static void main(String[] args){
		int SIZE = 100;
		Fruit[] fruit  = new Apple[SIZE];                     //你可以将导出类Apple数组赋予基类Fruit数组引用
		fruit[0] = new Jonathan();
		fruit[1] = new Apple();   
		// Runtime type is Apple[],not Fruit[] or Orange[]   
		try{
			//Compiler allows you to add fruit
			fruit[0] = new Fruit();			                   //因为这是Fruit数组引用,在编译期,你可以光明正大把Fruit和他的子类
			                                                   //放置进去,但是,在运行期时,这是Apple类型数组,会抛出异常。
		}catch(Exception e){
			e.printStackTrace();
		}
		try{
			fruit[0] = new Orange();//ArrayStrorException
		}catch(Exception e ){
			e.printStackTrace();
		}	
	}
}

但是在泛型中,就不存在这种可协变性:

//Compile Error:incompatible types:
		List<Fruit> flist = new List<Apple>();
我们不能将一个涉及Apple的泛型赋给一个涉及Fruit的泛型,这里不能理解为“不能把一个Apple容器赋值给Fruit容器”。

要明白一点,Apple的List在类型上不等价与Fruit的List,Apple是Fruit的子类,但是List<Apple>不是List<Fruit>的子类。

如果你想像数组中那样,在两个类型之间建立某种向上转型关系,可以使用通配符。

List<? extends Fruit> flist = new ArrayList<Apple>();
		//Compile Error:Can't add type of Object        现在我们不能往这个容器里添加除了null之外的任何类型,
		//flist.add(new Apple());                       因为我们不能知道这个容器到底持有什么类型,所以编译器干脆什么类型都不让我们放进去
		//flist.add(new Fruit());
		//flist.add(new OBject));
		flist.add(null);
		Fruit f = flist.get(0);                         //因为类型一定是Fruit,所以我们可以获得返回值
从上面这段代码中我们可以看出,对于使用了通配符的泛型,编译器将直接拒绝对参数列表中涉及通配符的方法的调用。那哪些是涉及通配符的方法呢?

在上面这个例子中,对于一个ArrayList<:? extends Fruit>,他的add方法的参数也会直接变成<? extends Fruti>,涉及通配符的方法就是哪些要对容器内部元素进行修改添加的方法。


(2)超类型通配符

可以声明统配通配符是某个特定类型的基类来界定的,方法是指定<? super MyClass>,甚至使用类型参数<? super T>,但是你不能对泛型参数给出一个超类型边界,即不能声明<T super MyClass>。

//超类型通配符  <? super T>
		List<? super Apple> apples = new ArrayList<Apple>();
		apples.add(new Apple());
		apples.add(new Jonathan());
		//apples.add(new Fruit());
参数是Apple的某种基类型,那么我能就能很放心的在里面放置Apple或者是它的子类型,但是,我们却没有办法知道参数具体是Apple的哪个基类型,因此向其中添加Fruit是不安全的。

extends 限定了返回类型,但是对参数类型无法限定;

super    限定了参数类型,但是无法限定返回类型。


(3)无界通配符

无界通配符<?>表示泛型参数可以是任何类型,但是它和原生类型是不同的。例如:List表示:“持有任何Object类型的原生类型”,但是List<?>表示:具有某种特定类型的非原生List,但是我们并不知道那种类型是什么。我们假设有一个Holder泛型类,他可以持有一个对象。对于原生的Holder,它将持有任何组合类型的组合,当你想他传递一个Object参数让他持有时,它会产生一个警告。但是Holder<?>将持有具有某种具体类型的同构集合,你向其中传入一Object对象让它持有,它就会产生错误的警告。

<?>可以被认为是一种装饰,事实上,它是在声明:“我是想用Java的泛型来编写这段代码,我在这里并不是要用原生类型,但是在当前这种情况下,泛型参数可以持有任何类型。”

无界通配符有很多方面的应用

(1)处理多个泛型类型参数

当你在处理多个泛型类型的参数时,可能会限定某一个参数是任何类型,而其他的参数类型都有特殊限定,这时候你就可以利用无界通配符进行处理。

//: generics/UnboundedWildcards2.java
import java.util.*;

public class UnboundedWildcards2 {
  static Map map1;
  static Map<?,?> map2;
  static Map<String,?> map3;
  static void assign1(Map map) { map1 = map; }
  static void assign2(Map<?,?> map) { map2 = map; }
  static void assign3(Map<String,?> map) { map3 = map; }
  public static void main(String[] args) {
    assign1(new HashMap());
    assign2(new HashMap());
    // assign3(new HashMap()); // Warning:
    // Unchecked conversion. Found: HashMap
    // Required: Map<String,?>
    assign1(new HashMap<String,Integer>());
    assign2(new HashMap<String,Integer>());
    assign3(new HashMap<String,Integer>());
  }
} ///:~

(2)捕获转换

有一种情况特别需要使用<?>而不是原生类型。如果向一个使用<?>的方法传递原生类型,那么对编译器来说,可能会推断出实际的类型参数,使得这个方法可以回转并调用另一个使用这个确切类型的方法。这种技术被称为捕获转换。

有关警告的注释只有在@SuppressWarnings注解被移除后才能起作用。

public class CaptureConversion {
	static <T> void f1(Holder<T> holder){
		T t = holder.get();
		System.out.println(t.getClass().getSimpleName());
	}
	static void f2(Holder<?> holder){
		f1(holder);
	}
	@SuppressWarnings("unchecked")  //Comment this will see the warning.
	public static void main(String[] args){
		Holder raw = new Holder<Integer>(1);
		//f1(raw);                              //Produces Warnings
		f2(raw);                              //No Warnings
		Holder rawBasic  = new Holder();      
		rawBasic.set(new Object());           //Warning
		f2(rawBasic);
		//Upcast to Holder<?,still figures it out.
		Holder<?> wildcarded = new Holder<Double>(1.0);
		f2(wildcarded);
	}
}/*Output:
Integer
Object
Double
*///~










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值