一.泛型是什么
避免强转、在编译时检查出来传进去的类型对不对、利于程序扩展。
二.各种泛型定义及使用
1.泛型类型定义及使用
//定义
class Point<T>{// 此处可以随便写标识符号
private T x ;
private T 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 ;
}
};
//IntegerPoint使用
Point<Integer> p = new Point<Integer>() ;
p.setX(new Integer(100)) ;
System.out.println(p.getX());
//FloatPoint使用
Point<Float> p = new Point<Float>() ;
p.setX(new Float(100.12f)) ;
System.out.println(p.getX());
(1)定义泛型:Point<T>
首先,大家可以看到Point<T>,即在类名后面加一个尖括号,括号里是一个大写字母。这里写的是T,其实这个字母可以是任何大写字母,大家这里先记着,可以是任何大写字母,意义是相同的。
(2)类中使用泛型
这个T表示派生自Object类的任何类,比如String,Integer,Double等等。这里要注意的是,T一定是派生于Object类的。为方便起见,大家可以在这里把T当成String,即String在类中怎么用,那T在类中就可以怎么用!
(3)使用泛型类
在泛型类构造的时候,在类名后面加上<想传入的类型>
2、多泛型变量定义及字母规范
(1)多泛型变量定义
<T,U,A,B,C....>相加几个就加几个
(2)、字母规范
任意一个大写字母都可以。他们的意义是完全相同的,但为了提高可读性,大家还是用有意义的字母比较好,一般来讲,在不同的情境下使用的字母意义如下:
- E — Element,常用在java Collection里,如:List<E>,Iterator<E>,Set<E>
- K,V — Key,Value,代表Map的键值对
- N — Number,数字
- T — Type,类型,如String,Integer等等
3、泛型接口定义及使用
泛型接口和泛型类一样:
public interface Infor<T> {
T getVar();
}
但是在使用时候有以下两种情况;
- 使用方法一:非泛型类
class InforImpl implements Infor<String>{
@Override
public String getVar() {
return null;
}
}
先要认清楚一点InforImpl 不是一个泛型类,因为它后面没有<T>!!!这里我们将Infor<>填充了String类型,getVar()的返回值也变成了String。
- 使用方法二:泛型类
class InforImpl<T> implements Infor<T>{
@Override
public T getVar() {
return null;
}
}
我们构造了一个泛型类InforImpl<T>,然后把泛型变量T 传给了Infor<T>,说明接口和泛型类使用的是同一个泛型变量。在使用的过程中:
这里传入想要的类型<String>
InforImpl<String> infor = new InforImpl();
String var = infor.getVar();
4、泛型函数定义及使用
上面提到了类和接口的泛型使用,下面看看怎么单独在一个函数中使用泛型:
public class StaticFans {
public static <T> void staticMethod(T t) {
System.out.println(t.toString());
}
public <T> void method(T t) {
System.out.println(t.toString());
}
}
上面的写法和平时的方法差不多,唯一的区别就是在返回值(Void)前面加上<T>来表示泛型变量.
使用方法如下:
   可以像普通方法一样,直接传值,任何值都可以(但必须是派生自Object类的类型,比如String,Integer等),函数会在内部根据传进去的参数来识别当前T的类别
StaticFans.staticMethod("String类型");
StaticFans.staticMethod(666666);//整型
先创建类的实例,然后调用泛型函数。
new StaticFans().method("String类型");
new StaticFans().method(666666);
- 进阶:返回值中存在泛型
public static <T> List<T> parseArray(String response,Class<T> object){
List<T> modelList = JSON.parseArray(response, object);
return modelList;
}
函数的返回值是List<T>类型
5、其它用法:Class<T>类传递
有时,我们在Gson解析对象时,代码一般这样写
public static List<SuccessModel> parseArray(String response){
List<SuccessModel> modelList = JSON.parseArray(response, SuccessModel.class);
return modelList;
}
如果只有一两个类还行,如果需要很多,这么写岂不是很麻烦,parseArray里面我们需要一个SuccessModel.class,这时候就要用上Class<T>,使用Class<T>传递泛型类Class对象。
public static <T> List<T> parseArray(String response,Class<T> object){
List<T> modelList = JSON.parseArray(response, object);
return modelList;
}
注意到,我们用的Class<T> object来传递类的class对象,即我们上面提到的SuccessModel.class。
--------------------- 下面是进阶部分-------------------
-------被温水煮惯了,梦想的东西总是不敢于尝试,失败了又怎样,最多从头来过。--------
一 、类型绑定:extends
有时候,你会希望泛型类只能是某一部分,,比如操作数据的时候,你希望是Number或者是它的子类,这个想法就是给泛型参数增加一个范围
<T extends BoundingType>
T 表示BoundingType的子类型,T和BoundingType都可以是类,也可以是接口。extends表示子类型,不等同于继承。注意:::这里的extends不是继承里的extends,两者根本没有任何关联。
先设置一个基类
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,那编译器就会报错。
然后,我们新建两个类,派生自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);
二、通配符
1.引入
public class Point<T> {
private T x;
private T 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;
}
}
使用情况:
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;
在这里,我们生成四个实例,就得想四个名字。如果我们想生成十个不同类型的实例呢?那不得想十个名字。
光想名字就是个事,(其实我并不觉得想名字是个什么大事…… T _ T ,没办法,想不出更好的例子了…… )
那有没有一种方法,生成一个变量,可以将不同类型的实例赋值给它吗?---?来了
2.无边界通配符: ?
如果我们用?,可以这样实现
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的区别
?和T 没有任何的联系!!!
?和T 没有任何的联系!!!
?和T 没有任何的联系!!!
泛型T不能在代码用于创建变量,只能在类、接口、函数中声明以后,才能使用。
而无界通配符?只能用于填充泛型变量T,它是用来填充T的!!!只是填充方式的一种。
3、通配符?的extends绑定
从上面我们可以知道通配符?可以代表任意类型,但跟泛型一样,如果不加以限定,在后期的使用中编译器可能不会报错。所以我们同样,要对?加以限定。
绑定的形式,同样是通过extends关键字,意义和使用方法都用泛型变量一致。
同样,以我们上面的Point<T>泛型类为例,因为Point在实例意义中,其中的值是数值才有意义,所以将泛型变量T填充为Object类型、String类型等都是不正确的。
所以我们要对Point<?> point加以限定:只有数值类型才能赋值给point;
我们把代码改成下面的方式:
Point<? extends Number> point;
point=new Point<Number>();
point=new Point<Integer>();
point=new Point<String>();//有错误
point=new Point<Object>();//有错误
我们给通配符加上限定:Point<? extends Number> point;此时,最后两行,当T填充为String和Object,point就会报错。虽然指派的是派生自Number的任意类型, new Point<Number>();也是可以成功赋值的,说明包括边界本身。
再强调一遍:无边界通配符只是泛型T的填充方式,给它加上限定,只是限定了赋值给它的实体类。
注意:利用<? extends Number>定义的变量,只可取其中的值,不可修改
Point<? extends Number> point;
point = new Point<Integer>(3, 3);
Number i = point.getX();
point.setX(1);//这里会报错
为什么getX()可以,而setX()不行?
首先,point类型是由Point<? extends Number>决定的,并不会因为 point = new Point<Integer>(3, 3)而改变类型,假设如果point类型随着 point = new Point<Integer>(3, 3)改变,那么point = new Point<Long>(12l,23l)就会报错,正因为point类型始终是<? extends Number>,因为能被各种实例赋值。打个比方:Point类就好比学生,point是学生1,此时point类型还是学生,而不是学生1.
然后,正因为point类型<? extends Number>,这是一个什么类型?这其实是一个未知类型Integer、long、double。。。怎么可能给一个未知类型去赋值?显然是不合理的。
最后,当我们去getX()的时候,取得类型可能是Integer、long。。虽然类型不确定,但它一定是Number的子类,也就是说,编译器只要能确定通配符的类型,就会允许,如果无法确定通配符的类型,就会报错。
4、通配符?的super绑定
如果说< ? extends XXX> 指的是填充派生于XXX的子类,那么<? super XXX> 指的是任意XXX的父类。
class CEO extends Manager {
}
class Manager extends Employee {
}
class Employee {
}
List<? super Manager> list;
list = new ArrayList<Employee>();
list = new ArrayList<Manager>();
list = new ArrayList<CEO>();//这句话报错
因为CEO已经不是Manager的父类了,所以编译会报错。super的关键字也是包括边界的。
super通配符实例内容:能存不能取
List<? super Manager> list=new ArrayList<>();
list.add(new CEO());
list.add(new Manager());
list.add(new Employee());//这句话报错
为什么前两个行,最后一个不行。这里可以这么理解:父类的引用可以指向子类对象。假设?是Manager,CEO是Manager的子类,最后都可以强转成Manager。但是Manager是Employee 的子类,所以Employee 不行。这就好比我声明的是一个Dog对象,我们在add的时候不能new 一个Animal进去。
我们再来看看取:
Object object=list.get(0);
Employee employee=list.get(0); //报错
Manager manager=list.get(0);//报错
CEO ceo=list.get(0);//报错
我们填充的是Manager的父类,假设是Employee 或者是Object,取得的值肯定是Object的子类,所以第一个肯定没有问题。我们无法判断我们取得值是什么类型,所以都是报错的。但取出一个Object也没有什么意思,所以,我们认为super能存不能取。
5、通配符?总结
- 如果你想从一个数据类型里获取数据,使用 ? extends 通配符(能取不能存)
- 如果你想把对象写入一个数据结构里,使用 ? super 通配符(能存不能取)
- 如果你既想存,又想取,那就别用通配符。