Android基础-Java泛型

泛型的定义

泛型:参数化的类型。很简单的一句话,那么什么叫做“参数化的类型”呢?。。。。。

为什么需要泛型?

假设现在有这样一个需求:把两个整数进行相加并返回计算结果。我们很轻松的就可以写出如下代码来完成此功能:

    public int addInt(int x, int y) {

        return x + y;
    }

后来有一天业务拓展,需要支持浮点数进行相加并返回计算结果。于是我们可以新增一个新的如下方法来完成新需求:

 public float addFloat(float x, float y) {

        return x + y;
    }

日子一天天的过去,又有了新的需求,需要支持double类型的相加,那么我们依然可以依葫芦画瓢的再增加一个新的方法来完成double类型的数据相加。诶?等等。。。观察上面的两个方法除了参数的类型和返回值不同之外,其他的都相同(方法名是可以相同的,称之为重载,此处为了区分类型,故写作不同名称)。于是为了解决这个问题,java就引入了泛型机制,可以很好的解决重复代码

我们平常用的最多的数据结构恐怕就是List了。例如:

        List list = new ArrayList();
        list.add("element0");
        list.add("element1");
        list.add(100);

这段代码无论在编写阶段还是运行阶段都是不会报错的。

        for (int i = 0; i < list.size(); i++) {
            String value = (String) list.get(i);
            System.out.println("第" + (i + 1) + "个元素的值是: " + value);
        }

可是当我们需要用上面的代码遍历输出集合中元素的时候,却会报出ClassCastException。这时因为最后添加的一个元素是 int 类型,强转为 String 类型肯定是会报错的。于是这里就体现出了泛型的第二个好处:安全。于是我们经常如下去创建一个List:

        List<String> list = new ArrayList<>();
        list.add("element0");
        list.add("element1");
        list.add(100); // 编译器会提示此行代码报错

并且加了泛型之后在取值的时候也不需要强制类型转换了。
综上所述:1.适用于多种数据类型执行相同的代码。2.在编译期间就发现数据类型安全问题。这也就是我们为什么要使用泛型的原因。

泛型的使用
  • 泛型类
public class GenericClass<T> {
    
    private T data;

    public GenericClass(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
  • 泛型接口
public interface GenericInterface<T> {

    T next();
}

关于泛型接口的实现有两种:

public class GenericInterfaceImpl1 implements GenericInterface<String>{
    @Override
    public String next() {
        return null;
    }
}
public class GenericInterfaceImpl2<T> implements GenericInterface<T>{

    @Override
    public T next() {
        return null;
    }
}

这两种方式的区别就是一个是在使用的时候才确定具体类型,一个在声明类的时候就确定了类型。

  • 泛型方法
    public <T> T genericMethod(T... t) {
        return t[t.length / 2];
    }

其中<T>是定义泛型方法所必须的,如果没有的话,即使这个方法带有 T 、或者 E 这种常见的泛型定义,那么它依然不是一个泛型方法。例如上面泛型类中的 getData() 或者 setData(T data),前面没有<T>,它们依旧是普通的方法,只不过他们是定义在了泛型类中罢了。

类型变量的限定-用于方法上
    public static <T extends Comparable> T min(T a, T b) {
        if (a.compareTo(b) > 0) return a;else return b;
    }

extends 关键字从面相对象上严格来说是叫派生。那么 T extends Comparable 就可以理解为派生自 Comparable 这个接口的子类。故下面可以使用Comparable 接口中的 compareTo() 方法。假如尖括号中只有一个 T,那么参数a是无法调用compareTo()方法的。这就是所谓的类型变量的限定
关于限定类型的使用有如下规则:

  • 可以有多个限定类型,中间用 & 符号隔开
  • 可以是类,也可以是接口。如果是类的话需要放在第一个的位置
  • 有且只能有一个类,接口则没有限制。因为java是单继承,多实现。
    下面是简单示例:
    public static <T extends View & Comparable & Serializable> T min(T a, T b) {
        if (a.compareTo(b) > 0) return a;else return b;
    }

此时传入的参数需要满足是 View 的子类,且同时实现了Comparable 和 Serializable 接口才能使用。否则无法通过编译。

类型变量的限定-用于类上
public class GenericClass<T extends Comparable> {

    private T data;

    public GenericClass(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public T min(T t) {
        if (this.data.compareTo(t) > 0) {
            return t;
        } else {
            return this.data;
        }
    }
}

这里是把上面泛型类进行了一个改造,这时泛型的具体类型只能是 Comparable 的实现类。

泛型的约束和局限性
  • 不能实例化类型变量
    还是以上面的泛型类为例。这时我们给它添加一个无参的构造方法。
public class GenericClass<T> {

    private T data;

    public GenericClass() {

    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

}

假如我们想要在构造方法中初始化 T 的实例是不允许的。编译器会直接报错:


  • 静态域或静态方法不能引用类型变量



    编译器会直接报错。这个问题的答案其实很简单:泛型的具体类型是在创建出具体的泛型类的对象的时候才确定的,而静态代码的执行是早于对象的创建的。那么虚拟机根本就不知道静态的 T 是什么。但如果是静态泛型方法则是可以的:

    public static <E> void print(E e) {
        System.out.println(e.toString());
    }
  • 基本数据类型不能作为泛型
        GenericClass<double> genericClass1 = new GenericClass<>(); // 报错 基本类型不可以
        
        GenericClass<Double> genericClass2 = new GenericClass<>(); // 需要用其包装类
  • 不能使用 instanceof 关键字
    通常我们需要判断一个对象是不是某种类型的时候,都会用 instanceof 关键字来判断。可是判断某个对象是不是某个泛型类的类型时却不可以。例如:
        GenericClass<Float> genericFloat = new GenericClass<>();

        if (genericFloat instanceof GenericClass<Float>){ // 这一行编译器会报错

        }
  • 不能实例化泛型数组



    可以声明泛型数组,但是却不能实例化。这是 java 语法规定。是不是很奇葩?

  • 泛型类不能继承 Exception 或 Throwable



    非常简单粗暴,编译器直接提示泛型类不能派生自 Throwable。

  • 不能捕获泛型类对象



    但是下面这种写法确实允许的:


通配符

假设现在有两个如下类,且存在继承关系:

public class Animal {
    
}

public class Dog extends Animal{
        
}

Dog 是 Animal 的子类,那么问题来了:

        GenericClass<Animal> animalGeneric = new GenericClass<>();
        GenericClass<Dog> dogGeneric = new GenericClass<>();

请问GenericClass<Animal> 和 GenericClass<Dog> 之间存在继承关系吗?答案显然是否定的。但是泛型类可以继承或扩展其他泛型类,例如 List 和 ArrayList 之间的关系:

public class GenericClassChild<T> extends GenericClass<T>{
    
}

再假设现在有如下一个方法:

    public static <T> void set(GenericClass<Animal> genericClass) {

    }

那么下面两种调用可以吗?

        set(animalGeneric); // 1
        set(dogGeneric); // 2

很显然,第一种是肯定可以的。第二种就不可以了。于是为了解决这种问题就有了通配符的概念。

  • ? extends
    现在有如下4个类,且有如下继承关系:



    假设现在有如下4个对象和对应的打印方法:

        GenericClass<Fruit> fruit = new GenericClass<>();
        GenericClass<Apple> apple = new GenericClass<>();
        GenericClass<RedApple> redApple = new GenericClass<>();
        GenericClass<Orange> orange = new GenericClass<>();

        public static <T> void printExtends(GenericClass<? extends Apple> genericClass) {
      
        }

下面我们来看一下调用结果:


从上图中我们可以得知 ?extends 限定了传入参数的上边界。那这样使用有没有什么限制呢?

可以看到当我们尝试着设置数据的时候是不允许的,当我们要取数据的时候却只可以用基类 Fruit 去接收。其实这也很好解释。上面我们说了?extends 限定了上边界,也就是说当我们取数据的时候,不管这个时候这个对象里有什么,但是一定是 Fruit 的子类,根据多态的性质,可以用父类来接收子类。至于设置数据的时候为什么不可以也就很好解释了。因为编译器只知道传入的是 Fruit 的子类,而至于传入的具体是哪一个子类,编译器是不知道的。至此我们可以总结一下:?extends 限定了传入参数类型的上界,用于安全的访问数据

  • ?super
        GenericClass<Fruit> fruit = new GenericClass<>();
        GenericClass<Apple> apple = new GenericClass<>();
        GenericClass<RedApple> redApple = new GenericClass<>();
        GenericClass<Orange> orange = new GenericClass<>();

        public static <T> void printSuper(GenericClass<? super Apple> genericClass) {

        }

下面我们来看一下调用结果:


可以看到?super正好是限定了传入参数的下边界。下面我们来看一下有什么限制:


不是说 super 是限定了下边界吗?怎么在设置数据的时候父类 Fruit 反而不行了呢?而子类 RedApple 却可以呢?这是因为所有类的父类都是Object,而编译器无法确定传入的是什么类型,但是 Apple 和 Apple 的子类是可以安全的转型为Apple的,可以满足最下边界,所以可以安全传入。这也就是为什么最后获取的时候直接得到的是 Object ,而不是 Fruit 。至此我们可以总结一下:?super 限定了传入参数类型的下界,用于安全的设置数据

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

推荐阅读更多精彩内容