1 类型绑定extends
为什么要适用类型绑定?
public interface Comparable<T>{
public boolean compareTo(T i);
}
但如果我们直接通过函数来比较类型的大小,肯定会报错,因为编译器根本不知道什么类型?又怎么比较呢?
所以需要让编译器知道是什么类型,这就是类型绑定的用处了。
定义形式:
<T extends BoundingType>
此定义表示T应该是BoundingType的子类型(subtype)。T和BoundingType可以是类,也可以是接口。另外注意的是,此处的”extends“表示的子类型,不等同于继承。
类型绑定有两个作用:1、对填充的泛型加以限定 2、使用泛型变量T时,可以使用BoundingType内部的函数。
(1)绑定接口
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;
}
这里的泛型直接绑定Comparable接口,通过传进去的类型数组a,然后比较每一项的大小,最后得到最小值。
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;
}
}
以上是非泛型类实现泛型接口,所以要填充类型,很多人可能会发现接口填充为什么是StringCompare,这是由于StringCompare绑定了Comparable接口,那么填充的时候也要用StringSompare类型来填充,最后是调用函数:
StringCompare result = min(new StringCompare("123"),new StringCompare("234"),new StringCompare("59897"));
Log.d(TAG,"min:"+result.mStr);
(2)绑定类型
假设我们有一个水果摊,有许多水果,需要打印出填充进去的水果
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类
class Banana extends Fruit{
public Banana(){
setName("bababa");
}
}
class Apple extends Fruit{
public Apple(){
setName("apple");
}
}
同过<T extends Fruit>就可以限定填充进去必定是Fruit的子类了。
最后调用
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 Comparable & Serializable, U extends Runnable> T foo(T a, U b){
…………
}
2 通配符
什么是通配符?首先还是引入类型Point<T>
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;
}
}
然后生成四个实例
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);
…………
生成四个实例需要4个不同的变量,有没有一种方法可以只生成一个变量,然后将不同类型的实例赋值给它。
看下实现方式
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);
这里的?就是无边通配符。它代指任意符号,可代表任意的类。
无边通配符只能用于填充泛型T,表示统配任何类型。不能用于定义变量。
Box<?> box;
box = new Box<String>();
3 通配符的绑定
和泛型绑定类似,如果不加以限定,在后期的使用中编译器可能不会报错。所以我们同样,要对?加以限定。
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坐标
…………
}
注意:
Point<? extends Number> point;
point = new Point<Integer>(3.33);
Number number = point.getX();
point.setX(new Integer(222));
第四行会报错,为什么呢?因为填充Point的泛型变量T是<? extends Number>,这是一个未知类型,不能用来设置內部值。
但是取值的时候,由于泛型变量T被填充为<? extends Number>,所以编译器能确定T是Number的子类,编译器就回用Number来填充。
4 通配符?的super绑定
<? extends Number> 指的是被填充为Number的子类,那么<? super Number> 就是指填充T为Number的父类。
class CEO extends Manager {
}
class Manager extends Employee {
}
class Employee {
}
List<? super Manager> list;
list = new ArrayList<Employee>();
//存
list.add(new Employee()); //编译错误
list.add(new Manager());
list.add(new CEO());
为啥添加Employee实例时会报编译错误呢?首先Manager和CEO都为<? super Manager>的子类,当他们被填充进去时就回被强制转为Manager的任意父类,而Employee不一定是<? super Manager>的子类, 所以不能会报错。
5 通配符总结
总结 ? extends 和 the ? super 通配符的特征,我们可以得出以下结论:
◆ 如果你想从一个数据类型里获取数据,使用 ? extends 通配符(能取不能存)
◆ 如果你想把对象写入一个数据结构里,使用 ? super 通配符(能存不能取)
◆ 如果你既想存,又想取,那就别用通配符。