Java中的泛型

Java中的泛型存在一些编译器特性, 是在编译期间就将泛型代码转换成具体的类型.

泛型的概念

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

  • 泛型参数 泛型形参都称为泛型参数
  • 泛型形参 泛型形参可以类比于对象方法中的具体的形参。
  • 泛型实参 泛型实参就是实际要绑定的具体类型。

泛型表示

Java中泛型都用大写符号表示.但一般有比较规范的表示意思

  • K 表示Key
  • E 表示Element
  • V 表示Value
  • T 表示Type

泛型的三种应用

java中有泛型类、泛型接口、泛型方法三种泛型。

使用步骤

  • 声明泛型名称(类和接口相识、泛型方声明在 访问修饰符号和返回类型之间)
  • 如果是类, 可以使用泛型名称修饰非静态成员
  • 如果是方法, 可以使用泛型名称修饰返回类型, 参数列表

泛型类和接口(以类为例)

泛型类

语法:

[修饰符] class 类名<泛型名称> {
  
}

示例代码:

public class TestGenericTest {


    public static void main(String[] args) {

        Student<Integer> student = new Student<Integer>();
        student.setScore(100);
        student.setName("小明");
        System.out.println(student);

    }

}

class Student<T> {

    private String name;
    private T score;

    public Student() {

    }

    public Student(String name, T score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public T getScore() {
        return score;
    }

    public void setScore(T score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}

定义的泛型类,就一定要传入泛型类型实参么?并不是这样,在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。
也就是说泛型类型并不一定要参数实参,但是如果不传入实参就起不到本来应该起的限制作用,此时泛型类型可以为任意类型

示例代码

        Student student1 = new Student("小王", 100);
        Student student2 = new Student("小陈", "100");
        Student student3 = new Student("小李", false);
        System.out.println(student1.getScore());
        System.out.println(student2.getScore());
        System.out.println(student3.getScore());

输出

100
100
false

泛型类总结

  • 泛型的类型参数只能是类类型,不能是简单类型。
  • 泛型类型并不一定要参数实参,但是如果不传入实参就起不到本来应该起的限制作用,此时泛型类型可以为任意类型。
  • 泛型的类型不能用在静态成员上。

泛型方法

为什么会有泛型方法, 主要是为了应用于以下两种场景

  • 某个类不是泛型类, 而某个方法需要使用泛型
  • 某个类是泛型类, 但是需要再静态方法上使用泛型

语法:

[修饰符] <泛型名称> 返回类型 方法名(形参类型  形参列表) {

}

其中返回类型、形参类型也可以用声明的泛型类型进行修饰

示例代码:


public class TestGenericMethod {


    public static void main(String[] args) {

        Integer[] nums = new Integer[]{1,2,3,4,5,6};
        String numsStr = MyArray.toString(nums);
        System.out.println(numsStr);
    }


}

// 类不是泛型类。又想声明泛型方法
class MyArray {

    public static <T> String toString(T[] arr) {

        String str = "[";
        int i = 0;

        for (T t:arr) {

            str += t;

            if (i != arr.length - 1)
                str+= ",";
            i++;
        }

        str +="]";
        return str;


    }
}

泛型通配符

  • ? 问号泛型通配符, 表示任意匹配
  • extends: 泛型中, 表示上限,不管是类还是接口,都用extends表示匹配的类型必须是这个类或者是这个类的子类,对于接口则表示必须是实现了该接口的类
  • super: 泛型中, 表示下限。表示类必须是这个类或者是起父类

通配符上限设置

<? extends className>
假设有如下继承体系, 则Box <? extends Fruit>表示图中蓝色部分

上限设置

通配符下限设置

<? super className>
假设有如下继承体系, Box<? super Fruit> 表示的如下黄色部分, Fruit就是下限.支持的只能是Fruit或是其父类.

下限设置

以上两张图均是取自网络

具体例子

现在有四个类,分别是 Food,Fruit,Apple 和 Plate、rice。其中 Apple 继承自 Fruit,Fruit 继承自 Food,Plate 是用来盛放这些东西的容器.Rice是谷物,继承自Food

定一个泛型类Plate

class Plate<T> {
    T x;
    public Plate(){}  
    public Plate(T x){this.x = x;}
    public void set(T x){this.x=x;}
    public T get(){return x;}
}

我们能实例化这个泛型类盘子, 让它为装载水果的盘子.因为是水果盘子,所以苹果也能放进去,但是取的时候,我们只知道这个果盘装的是水果,但不知道是什么水果。因为泛型的实参是Fruit,绑定的是Fruit

        Plate<Fruit> plate = new Plate<Fruit>();
        plate.set(new Apple());
        Fruit apple = plate.get();

其实我们可以进一步限制这个盘子能装什么类型的水果, 比如限制这个盘子只能装苹果或者水果


public class Test {

    public static void main(String[] args) {
        
        Plate<? extends Fruit> plate = new Plate<Fruit>();
        Plate<? extends Fruit> plate2 = new Plate<Apple>();
        // 上限不能往外取, 编译器是禁止的.因为编译器
        // 只知道plate和plate2是 能存水果类的盘子,但是并不知道这个盘子
        // 到底是哪一种盘子,如上代码, 可能是放苹果类的盘子,也可能是水果类盘子
        // 如果是苹果类盘子,那只能放苹果类的苹果
//        plate.set(new Apple());   编译器报错
//        plate.set(new Fruit());   编译器报错
        
        Fruit f1 = plate.get();
        Fruit f2 = plate2.get();
//        Apple a1 = plate2.get();  编译器报错
        
    }

}

上限不能往外取, 编译器是禁止的.因为编译器
上述代码只知道plate和plate2是 能存水果类的盘子,但是并不知道这个盘子到底是哪一种盘子,如上代码, 可能是放苹果类的盘子,也可能是水果类盘子, 如果是苹果类盘子,那只能放苹果类的苹果。因为苹果类型是小类型, 不能往大类型,水果类型自动转,而JVM不保证强转的成功,自然就不能调用set.

总结一句话上限通配符,只能往外取,不能往里存。

下界通配符规律恰好想法, 下界通配符,能往外取,也能往里存,但是取出来会丢失类型,存进去只能存子类或者本类,不能存父类.

泛型通配符使用总结

  • 上限通配符,只能往外取,不能往里存。
  • 下限通配符,既能往外取,有能往里存。只是外取丢失类型信息,里存只能存子类或本类对象。

泛型通配符使用场景

  • 在代码中避免泛型类和原始类型的混用。比如List
  • 在使用带通配符的泛型类的时候,需要明确通配符所代表的一组类型的概念。由于具体的类型是未知的,很多操作是不允许的。
  • 泛型类最好不要同数组一块使用。你只能创建new List<?>[10]这样的数组,无法创建new List[10]这样的。这限制了数组的使用能力,而且会带来很多费解的问题。因此,当需要类似数组的功能时候,使用集合类即可。
  • 不要忽视编译器给出的警告信息。

PECS 原则

  • 如果要从集合中读取类型T的数据, 并且不能写入,可以使用 上界通配符(<?extends>)—Producer Extends。
  • 如果要从集合中写入类型T 的数据, 并且不需要读取,可以使用下界通配符(<? super>)—Consumer Super。

如果既要存又要取, 那么就要使用任何通配符。


引用的参考文章

java 泛型详解

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

推荐阅读更多精彩内容

  • 引言:泛型一直是困扰自己的一个难题,但是泛型有时一个面试时老生常谈的问题;今天作者就通过查阅相关资料简单谈谈自己对...
    cp_insist阅读 1,835评论 0 4
  • 本文包括:JDK5之前集合对象使用问题泛型的出现泛型应用泛型典型应用自定义泛型——泛型方法自定义泛型——泛型类泛型...
    廖少少阅读 1,886评论 5 16
  • 一 泛型是什么泛型最精准的定义:参数化类型。具体点说就是处理的数据类型不是固定的,而是可以作为参数传入。定义泛型类...
    Diffey阅读 2,703评论 0 9
  • 文章作者:Tyan博客:noahsnail.com 1. 什么是泛型 Java泛型(Generics)是JDK 5...
    SnailTyan阅读 771评论 0 3
  • 泛型 (Generic) 泛型 (Generic),即“参数化类型”,就是允许在定义类、接口、方法时使用类型形参,...
    AshengTan阅读 3,262评论 0 3