之前对泛型这一块儿的内容学习有点疏漏,现在抽空补一下。
原文参考:https://blog.csdn.net/harvic880925/article/details/49883589
类型绑定
1.定义
有时候,你会希望泛型类型只能是某一部分类型,比如操作数据的时候,你会希望是Number或其子类类型。这个想法其实就是给泛型参数添加一个界限。其定义形式为:
此定义表示 T 是 BoundingType 的子类型。 T 和 BoundingType 可以是类 也可以是接口,需要注意的是此处的 extends 表示的子类型,不同于继承
2.实例:绑定接口
类型绑定有两个作用:
- 对填充的类型加以限定
- 使用泛型变量 T 时,可以使用 BoundingType 内部的函数。
我们用泛型来实现根据传进去的T类型数组a,找到最小值的功能。
package com.gildata;
/**
* Created by liaock on 2019/1/15
**/
public interface Comparable<T> {
public boolean compareTo(T o);
}
//添加上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接口的类:
package com.gildata;/**
* Created by liaock on 2019/1/16
**/
/**
*
**/
public class StringCompare implements Comparable<StringCompare>{
private String mStr;
public StringCompare(String string){
this.mStr = string;
}
public String getmStr() {
return mStr;
}
public void setmStr(String mStr) {
this.mStr = mStr;
}
public boolean compareTo(StringCompare str) {
if(mStr.length() > str.mStr.length()){
return true;
}
return false;
}
}
测试:
package com.gildata;
import java.util.Arrays;
/**
* Created by liaock on 2019/1/15
**/
public class Main {
//添加上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;
}
public static void main(String []args){
Point<Integer> p1 = new Point<Integer>();
p1.setX(new Integer(100));
System.out.println(p1.getX());
// 類型綁定
StringCompare result = min(new StringCompare("123"),new StringCompare("234"),new StringCompare("59897"));
System.out.println(result.getmStr());
//無邊界通配符: ?
Point<?> point = new Point<Integer>(3,3);
Point<?> point2 = new Point<Float>(4.3f,4.3f);
}
}
二、通配符
通配符是一个比较难以理解的问题
1.引入
我们来看这样一个例子:
package com.gildata;
/**
* Created by liaock on 2019/1/15
**/
class Point <T> {
private T x; //表示X坐标
private T y; //表示Y坐标
public Point(){}
public Point(T x, T y){
this.x = x;
this.y = y;
}
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
这段代码很简单,引入一个泛型变量 T,然后两个构造函数,set、get方法。
我们看看下面的这段代码:
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生成了四个实例:integerPoint,floatPoint,doublePoint和longPoint;
在这里,我们生成四个实例,就得想四个名字。如果我们想生成十个不同类型的实例呢?那不得想十个名字。
光想名字就是个事,(其实我并不觉得想名字是个什么大事…… T _ T ,没办法,想不出更好的例子了…… )
那有没有一种办法,生成一个变量,可以将不同类型的实例赋值给他呢?
2、无边界通配符
<1>.概述
我们这样来实现上面的例子:
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<?> 这里的?就是无边界通配符。通配符的意义就是它是一个未知的符号,可以是代表任意的类。
<2>. ? 与 T 的区别
? 与 T 没有任何联系 !
泛型变量 T 不能在代码用于创建变量,只能在类、函数、接口声明以后,才能使用。
而无边界通配符?则只能用于填充泛型变量T,表示通配任何类型!
三、通配符 ? 的extends绑定
1.概述
从上面我们可以知道通配符?可以代表任意类型,但跟泛型一样,如果不加以限定,在后期的使用中编译器可能不会报错。所以我们同样,要对?加以限定。绑定的形式,同样是 extends 关键字,意义和使用方法都和泛型变量一致。
Point<? extends Number> point3;
point3 = new Point<Number>();
point3 = new Point<Float>(4.3f,4.3f);
point3 = new Point<String>("","");
例如上面的代码,最后一行代码编译就会报错。
无边界通配符只是泛型T的填充方式,给他加上限定,只是限定了赋值给它(比如这里的point)的实例类型。
如果想从根本上解决乱填充Point的问题,需要从Point泛型类定义时加上:
class Point<T extends Number> {
private T x; // 表示X坐标
private T y; // 表示Y坐标
…………
}
2.注意:利用 <? extends Number> 定义的变量,只可取其中的值,不可以修改
看下面的代码
point3.getX();
point3.setX(3.3f);
发现 明显在point3.setX(3.3f);;时报编译错误。但point3.getX()却不报错。
这是为什么呢?
首先,point的类型是由Point<? extends Number>决定的,并不会因为point = new Point(3,3);而改变类型。
即便point = new Point(3,3);之后,point的类型依然是Point<? extends Number>,即派生自Number类的未知类型!!!这一点很好理解,如果在point = new Point(3,3);之后,point就变成了Point类型,那后面point = new Point(12l,23l);操作时,肯定会因为类型不匹配而报编译错误了,正因为,point的类型始终是Point<? extends Number>,因此能继续被各种类型实例赋值。
回到正题,现在说说为什么不能赋值
正因为point的类型为 Point<? extends Number> point,那也就是说,填充Point的泛型变量T的为<? extends Number>,这是一个什么类型?未知类型!!!怎么可能能用一个未知类型来设置内部值!这完全是不合理的。
但取值时,正由于泛型变量T被填充为<? extends Number>,所以编译器能确定的是T肯定是Number的子类,编译器就会用Number来填充T
也就是说,编译器,只要能确定通配符类型,就会允许,如果无法确定通配符的类型,就会报错。
四、通配符? 的super绑定
1.概述
如果说 <? extends XXX>指填充为派生于XXX的任意子类的话,那么<? super XXX>则表示填充为任意XXX的父类!
五、通配符? 总结
总结 ? extends 和 the ? super 通配符的特征,我们可以得出以下结论:
- 如果你想从一个数据类型里获取数据,使用 ? extends 通配符(能取不能存)
- 如果你想把对象写入一个数据结构里,使用 ? super 通配符(能存不能取)
- 如果你既想存,又想取,那就别用通配符。
最后重复一遍:构造泛型实例时,如果省略了填充类型,则默认填充为无边界通配符!