泛型

作者: 王小贱_ww | 来源:发表于2017-11-10 22:27 被阅读44次

一.泛型是什么

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

二.各种泛型定义及使用

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 通配符(能存不能取)
  • 如果你既想存,又想取,那就别用通配符。

相关文章

  • 泛型 & 注解 & Log4J日志组件

    掌握的知识 : 基本用法、泛型擦除、泛型类/泛型方法/泛型接口、泛型关键字、反射泛型(案例) 泛型 概述 : 泛型...

  • 【泛型】通配符与嵌套

    上一篇 【泛型】泛型的作用与定义 1 泛型分类 泛型可以分成泛型类、泛型方法和泛型接口 1.1 泛型类 一个泛型类...

  • 泛型的使用

    泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法 泛型类 泛型接口 泛型通配符 泛型方法 静态方法与...

  • Java 泛型

    泛型类 例如 泛型接口 例如 泛型通配符 泛型方法 类中的泛型方法 泛型方法与可变参数 静态方法与泛型 泛型上下边...

  • 探秘 Java 中的泛型(Generic)

    本文包括:JDK5之前集合对象使用问题泛型的出现泛型应用泛型典型应用自定义泛型——泛型方法自定义泛型——泛型类泛型...

  • Web笔记-基础加强

    泛型高级应用 自定义泛型方法 自定义泛型类 泛型通配符? 泛型的上下限 泛型的定义者和泛型的使用者 泛型的定义者:...

  • 重走安卓进阶路——泛型

    ps.原来的标题 为什么我们需要泛型? 泛型类、泛型接口和泛型方法(泛型类和泛型接口的定义与泛型方法辨析); 如何...

  • Kotlin泛型的高级特性(六)

    泛型的高级特性1、泛型实化2、泛型协变3、泛型逆变 泛型实化 在Java中(JDK1.5之后),泛型功能是通过泛型...

  • Java 19-5.1泛型

    泛型类定义泛型类可以规定传入对象 泛型类 和泛型方法 泛型接口 如果实现类也无法确定泛型 可以在继承类中确定泛型:

  • 【Swift】泛型常见使用

    1、Swift泛型4种 泛型函数泛型类型泛型协议泛型约束 2、泛型约束3种 继承约束:泛型类型 必须 是某个类的子...

网友评论

    本文标题:泛型

    本文链接:https://www.haomeiwen.com/subject/pmqmmxtx.html