泛型

一.泛型是什么

   避免强转、在编译时检查出来传进去的类型对不对、利于程序扩展。

二.各种泛型定义及使用

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>来表示泛型变量.
使用方法如下:

   &ensp;&ensp;     可以像普通方法一样,直接传值,任何值都可以(但必须是派生自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 通配符(能存不能取)
  • 如果你既想存,又想取,那就别用通配符。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • 泛型的定义及使用 1. 定义泛型: 2. 类中使用泛型 3. 使用泛型类 4. 使用泛型的优势? 多泛型变量的定义...
    xue57233阅读 428评论 0 1
  • 我们知道,使用变量之前要定义,定义一个变量时必须要指明它的数据类型,什么样的数据类型赋给什么样的值。 假如我们现在...
    今晚打肉山阅读 976评论 0 1
  • 开发人员在使用泛型的时候,很容易根据自己的直觉而犯一些错误。比如一个方法如果接收List作为形式参数,那么如果尝试...
    时待吾阅读 1,045评论 0 3
  • 第8章 泛型 通常情况的类和函数,我们只需要使用具体的类型即可:要么是基本类型,要么是自定义的类。但是在集合类的场...
    光剑书架上的书阅读 2,145评论 6 10