前言:被温水煮惯了,梦想的东西总是不敢于尝试,失败了又怎样,最多从头来过。
上一篇给大家初步讲解了泛型变量的各种应用环境,这篇将更深入的讲解一下有关类型绑定,通配符方面的知识。
class Point<T> { private T x; // 表示X坐标 private T y; // 表示Y坐标 public void setX(T x) { this.x = x; } public void setY(T y) { this.y = y; } public T getX() { return this.x; } public T getY() { return this.y; } } //使用 Point<Integer> p1 = new Point<Integer>(); p1.setX(new Integer(100)); System.out.println(p1.getX());首先,我们要知道一点,任何的泛型变量(比如这里的T)都是派生自Object,所以我们在填充泛型变量时,只能使用派生自Object的类,比如String,Integer,Double,等而不能使用原始的变量类型,比如int,double,float等。
private T x;当然只能调用Object所具有的函数,因为编译器根本不知道T具体是什么类型,只有在运行时,用户给什么类型,他才知道是什么类型。编译器唯一能确定的是,无论什么类型,都是派生自Object的,所以T肯定是Object的子类,所以T是可以调用Object的方法的。
public interface Comparable<T>{ public boolean compareTo(T i); }但如果我们直接利用T的实例来调用compareTo()函数的话,会报错,编译器截图如下:
这是因为,编译器根本无法得知T是继承自Comparable接口的函数。那怎么样才能让编译器知道,T是继承了Comparable接口的类型呢?
这就是类型绑定的作用了。
<T extends BoundingType>此定义表示T应该是BoundingType的子类型(subtype)。T和BoundingType可以是类,也可以是接口。另外注意的是,此处的”extends“表示的子类型,不等同于继承。
public interface Comparable<T> { public boolean compareTo(T i); } //添加上extends Comparable之后,就可以Comparable里的函数了 public static <T extends Comparable> T min(T...a){ T smallest = a[0]; for(T item:a){ if (smallest.compareTo(item)){ smallest = item; } } return smallest; }这段代码的意思就是根据传进去的T类型数组a,然后调用其中item的compareTo()函数,跟每一项做对比,最终找到最小值。
public class StringCompare implements Comparable<StringCompare> { private String mStr; public StringCompare(String string){ this.mStr = string; } @Override public boolean compareTo(StringCompare str) { if (mStr.length() > str.mStr.length()){ return true; } return false; } }在这段代码,大家可能会疑惑为什么把T也填充为StringCompare类型,记得我们上面说的吗:smallest.compareTo(item),smallest和item是同一类型!!所以compareTo的参数必须是与调用者自身是同一类型,所以要把T填充为StringCompare;
StringCompare result = min(new StringCompare("123"),new StringCompare("234"),new StringCompare("59897")); Log.d(TAG,"min:"+result.mStr);结果如下:
这里有extends接口,我们开篇说过,extends表示绑定,后面的BindingType即可以是接口,也可以是类,下面我们就再举个绑定类的例子。
(3)、实例:绑定类
我们假设,我们有很多种类的水果,需要写一个函数,打印出填充进去水果的名字:
为此,我们先建一个基类来设置和提取名字:
class Fruit { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }然后写个泛型函数来提取名字:
public static <T extends Fruit> String getFruitName(T t){ return t.getName(); }这里泛型函数的用法就出来了,由于我们已知水果都会继承Fruit基类,所以我们利用<T extends Fruit>就可以限定填充的变量必须派生自Fruit的子类。一来,在T中,我们就可以利用Fruit类中方法和函数;二来,如果用户填充进去的类没有派生自Fruit,那编译器就会报错。
class Banana extends Fruit{ public Banana(){ setName("bababa"); } } class Apple extends Fruit{ public Apple(){ setName("apple"); } }最后调用:
String name_1 = getFruitName(new Banana()); String name_2 = getFruitName(new Apple()); Log.d(TAG,name_1); Log.d(TAG,name_2);结果如下:
public static <T extends Fruit&Serializable> String getFruitName(T t){ return t.getName(); }再加深下难度,如果我们有多个泛型,每个泛型都带绑定,那应该是什么样子的呢:
public static <T extends Comparable & Serializable, U extends Runnable> T foo(T a, U b){ ………… }大家应该看得懂,稍微讲一下:这里有两个泛型变量T和U,将T与Comparable & Serializable绑定,将U与Runnable绑定。
class Point<T> { private T x; private T y; public Point(){ } public Point(T x,T y){ this.x = x; this.y = y; } public void setX(T x) { this.x = x; } public void setY(T y) { this.y = y; } public T getX() { return this.x; } public T getY() { return this.y; } }这段代码很简单,引入了一个泛型变量T,然后是有两个构造函数,最后分别是利用set和get方法来设置和获取x,y的值。这段代码没什么难度,不再细讲。
Point<Integer> integerPoint = new Point<Integer>(3,3); ………… Point<Float> floatPoint = new Point<Float>(4.3f,4.3f); ………… Point<Double> doublePoint = new Point<Double>(4.3d,4.90d); ………… Point<Long> longPoint = new Point<Long>(12l,23l); …………在这段代码中,我们使用Point<T>生成了四个实例:integerPoint,floatPoint,doublePoint和longPoint;
Point<?> point; point = new Point<Integer>(3,3); point = new Point<Float>(4.3f,4.3f); point = new Point<Double>(4.3d,4.90d); point = new Point<Long>(12l,23l);在这里,我们首先,利用下面的代码生成一个point实例,注意到,在填充泛型时,用的是?
Point<?> point;然后,各种类型的Point实例,都可以赋值给point了:
point = new Point<Integer>(3,3); point = new Point<Float>(4.3f,4.3f); point = new Point<Double>(4.3d,4.90d); point = new Point<Long>(12l,23l);这里的?就是无边界通配符。通配符的意义就是它是一个未知的符号,可以是代表任意的类。
(2)、?与T的区别
大家可能会有疑问,那无边界通配符?与泛型变量T有什么区别呢?
答案是:他们俩没有任何联系!!!!!
泛型变量T不能在代码用于创建变量,只能在类,接口,函数中声明以后,才能使用。
比如:
public class Box<T> { public T get(){ ………… }; public void put(T element){ ………… }; }而无边界通配符?则只能用于填充泛型变量T,表示通配任何类型!!!!再重复一遍:?只能用于填充泛型变量T。它是用来填充T的!!!!只是填充方式的一种!!!
//无边界通配符填充 Box<?> box; //其它类型填充 Box<String> stringBox;(3)、通配符只能用于填充泛型变量T,不能用于定义变量
Box<?> box; box = new Box<String>();即填充泛型变量T的位置,不能出现在后面String的位置!!!!
再次强调,?只能出现在Box<?> box;中,其它位置都是不对的。
我们给通配符加上限定: Point<? extends Number> point;
此时,最后两行,当将T填充为String和Object时,赋值给point就会报错!
这里虽然是指派生自Number的任意类型,但大家注意到了没: new Point<Number>();也是可以成功赋值的,这说明包括边界自身。
再重复一遍:无边界通配符只是泛型T的填充方式,给他加上限定,只是限定了赋值给它(比如这里的point)的实例类型。
如果想从根本上解决乱填充Point的问题,需要从Point泛型类定义时加上<T extends Number>:
class Point<T extends Number> { private T x; // 表示X坐标 private T y; // 表示Y坐标 ………… }(2)注意:利用<? extends Number>定义的变量,只可取其中的值,不可修改
明显在point.setX(Integer(122));时报编译错误。但point.getX()却不报错。
这是为什么呢?
首先,point的类型是由Point<? extends Number>决定的,并不会因为point = new Point<Integer>(3,3);而改变类型。
即便point = new Point<Integer>(3,3);之后,point的类型依然是Point<? extends Number>,即派生自Number类的未知类型!!!这一点很好理解,如果在point = new Point<Integer>(3,3);之后,point就变成了Point<Integer>类型,那后面point = new Point<Long>(12l,23l);操作时,肯定会因为类型不匹配而报编译错误了,正因为,point的类型始终是Point<? extends Number>,因此能继续被各种类型实例赋值。
回到正题,现在说说为什么不能赋值
正因为point的类型为 Point<? extends Number> point,那也就是说,填充Point的泛型变量T的为<? extends Number>,这是一个什么类型?未知类型!!!怎么可能能用一个未知类型来设置内部值!这完全是不合理的。
但取值时,正由于泛型变量T被填充为<? extends Number>,所以编译器能确定的是T肯定是Number的子类,编译器就会用Number来填充T
也就是说,编译器,只要能确定通配符类型,就会允许,如果无法确定通配符的类型,就会报错。
class CEO extends Manager { } class Manager extends Employee { } class Employee { }然后,如果我这样生成一个变量:
List<? super Manager> list;它表示的意思是将泛型T填充为<? super Manager>,即任意Manager的父类;也就是说任意将List<T>中的泛型变量T填充为Manager父类的List变量,都可以赋值给list;
从上面的代码中可以看出new ArrayList<Employee>(),new ArrayList<Manager>()都是正确的,而new ArrayList<CEO>()却报错,当然是因为CEO类已经不再是Manager的父类了。所以会报编译错误。
这里还要注意一个地方,从代码中可以看出new ArrayList<Manager>()是可以成功赋值给 List<? super Manager> list的,可见,super关键字也是包括边界的。即边界类型(这里是Manager)组装的实例依然可以成功赋值。
(2)、super通配符实例内容:能存不能取
上面我们讲了,extends通配符,能取不能存,那super通配符情况又怎样呢?我们试试看:
先看存的部分:
List<? super Manager> list; list = new ArrayList<Employee>(); //存 list.add(new Employee()); //编译错误 list.add(new Manager()); list.add(new CEO());首先,需要声明的是,与Point<? extends Number> point中point的类型是由Point<? extends Number>确定的,相同的是list的类型是也是由List<? super Manager> ;list的item的类型始终是<? super Manager>,即Manager类的任意父类,即可能是Employee或者Object.
list.add(new Manager()); list.add(new CEO());因为list里item的类型是<? super Manager>,即Manager的任意父类,我们假如是Employee,那下面这段代码大家能理解了吧:
List<Employee> list = new ArrayList<Employee>(); list.add(new Manager()); list.add(new CEO());在这里,正因为Manager和CEO都是Employee的父类,在传进去list.add()后,会被强制转换为Employee!
List<? super Manager> list; list = new ArrayList<Employee>(); //存 list.add(new Employee()); //编译错误 list.add(new Manager()); list.add(new CEO());编译器无法确定<? super Manager>的具体类型,但唯一可以确定的是Manager()、CEO()肯定是<? super Manager>的子类,所以肯定是可以add进去的。但Employee不一定是<? super Manager>的子类,所以不能确定,不能确定的,肯定是不允许的,所以会报编译错误。
在这段代码中,Object object = list.get(0);是不报错的,而Employee employee = list.get(0);是报错的;
我们知道list中item的类型为<? super Manager>,那编译器能肯定的是<? super Manager>肯定是Manger的父类;但不能确定,它是Object还是Employee类型。但无论是填充为Object还是Employee,它必然是Object的子类!
所以Object object = list.get(0);是不报错的。因为 list.get(0);肯定是Object的子类;
而编译器无法判断list.get(0)是不是Employee类型的,所以Employee employee = list.get(0);是报错的。
这里虽然看起来是能取的,但取出来一个Object类型,是毫无意义的。所以我们认为super通配符:能存不能取;
总结 ? extends 和 the ? super 通配符的特征,我们可以得出以下结论:
◆ 如果你想从一个数据类型里获取数据,使用 ? extends 通配符(能取不能存)
◆ 如果你想把对象写入一个数据结构里,使用 ? super 通配符(能存不能取)
◆ 如果你既想存,又想取,那就别用通配符。
好了,快累死了,这部分真是太难讲了,有关通配符捕获和编译器类型擦除的知识,就不讲了,在实际项目中基本用不到,有兴趣的同学可以自行去补充下。
下篇给大家讲下反射。
如果本文有帮到你,记得加关注哦
本文涉及源码下载地址:http://download.csdn.net/detail/harvic880925/9275551
请大家尊重原创者版权,转载请标明出处:http://blog.csdn.net/harvic880925/article/details/49883589 谢谢
参考文章:
1、《 java 泛型编程(一)》
2、《Java泛型--泛型应用--泛型接口、泛型方法、泛型数组、泛型嵌套》
3、《Java泛型编程最全总结》
4、《java 通配符解惑》
5、《《Java编程思想》学习笔记8——泛型编程高级》
6、《步步理解 JAVA 泛型编程(三)》
7、《Java-泛型编程-类型擦除(Type Erasure)》
8、《Java泛型--泛型入门》
9、《Java泛型--通配符》
10、《在 Java 的泛型类型中使用通配符》
11、《Java 理论与实践: 使用通配符简化泛型使用》
12、《Java 泛型学习三 通配符》
13、《Java泛型-- 通配符(转载)》
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文:http://blog.csdn.net/harvic880925/article/details/49883589