JAVA泛型

本文内容基于jdk 1.8。
什么是泛型:
泛型,即“参数化类型”,处理的数据类型不是固定的。这么说肯定不明白,因为我一开始看到这个解释也完全不明白。
百度的解释:泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。
怎么理解?举个java例子,假设现在有一个集合,本来打算是用来存字符串的,结果一不小心存了个数字进去了,新建类Test1,如下:

import java.util.ArrayList;
import java.util.List;
public class Test1 {
    public static void main(String[] args) {
        List arrayList = new ArrayList();
        arrayList.add("Hello sir");
        arrayList.add(100);
        for(int i = 0; i< arrayList.size();i++){
            String item = (String)arrayList.get(i);
            System.out.println("item:"+item);
        }
    }
}

编译的时候不会报错,因为集合本身可以存放多种不同类型的元素,但运行的时候报错了,如下:

image.png

编译时期不能检测到,运行时会出现类转化异常。这个时候就要用到了泛型了。
可以创建集合时先声明,只能存放String类型的数据,如果不小心存了别的类型进去,编译期就会报错。如下:

import java.util.ArrayList;
import java.util.List;
public class Test1 {
    public static void main(String[] args) {
        List<String> arrayList = new ArrayList<>();
        arrayList.add("Hello sir");
        arrayList.add(100);  //此行在编译期便会报错
        for(int i = 0; i< arrayList.size();i++){
            String item = (String)arrayList.get(i);
            System.out.println("item:"+item);
        }
    }
}

将运行时期会发生的异常提前到编译时期了。这就是泛型的作用
语言和程序设计的一个重要目标是将bug尽量消灭在摇篮里,能消灭在写代码的时候,就不要等到代码写完程序运行的时候
所以泛型的作用是一种安全机制,是一种书写规范,它和接口的作用有着一定的类似,都是在制定规则。

泛型的好处
  • 更好的安全性。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中
  • 更好的可读性,消除强制类型转换。
  • 实现更好的代码可重用性,例如通用算法的实现,在面向对象编程及各种设计模式中有非常广泛的应用。

泛型只在编译阶段有效。

泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

泛型有三种使用方式,分别为:泛型方法、泛型类、泛型接口

泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。

泛型的使用:

泛型方法:

泛型方法,是在调用方法的时候指明泛型的具体类型 。
新建类Test2,创建泛型方法getClzInstance:

public class Test2 {

    /**
     * 泛型方法的基本介绍
     *
     * @param clz 传入的泛型实参
     * @return T 返回值为T类型
     * 说明:
     * 1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
     * 2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
     * 3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
     * 4)此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
     */
    public <T> T getClzInstance(Class<T> clz) {
        T instance = null;
        try {
            instance = clz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return instance;
    }

    //泛型方法showE
    public <E> void showE(E e) {
        System.out.println(e.toString());
    }

    //可变参数泛型方法printT
    public <T> void printT(T... args) {
        for (T t : args) {
            System.out.println("print T :" + t.toString());
        }
    }

    //泛型的静态方法,
    public static <T> void print(T t) {
        System.out.println(t.toString());
    }

    public static void main(String[] args) {
        Test2 t1 = new Test2().getClzInstance(Test2.class);
        t1.showE(452345);
        t1.printT("hello! come here",168168, 69.0);
        print("泛型的静态方法!!!");
    }
}

<T>是不能省略的,用来声明为泛型方法,也可以用<E>,<K>,<V>等,可以随便写为任意标识

Class<T>这是用的系统的一个泛型类,关于泛型类等下再讲
运行结果:

image.png

泛型方法能使方法独立于类而产生变化,相对于泛型接口和泛型类,尽量优先使用泛型方法去处理问题

泛型类:

泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
基本写法:

class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
  private 泛型标识 /*(成员变量类型)*/ var; 
  .....
  }
}

例子:

public class Person<T> {
    private T key;  //声明key这个成员变量的类型为T,T的具体类型在实例化泛型类时指定
    public Person(T key){
        this.key = key;
    }
    //泛型方法getKey的返回值类型为T,T的具体类型也是实例化泛型类时指定的类型
    public T getKey(){
        return key;
    }
}

实例化泛型类:

 public static void main(String[] args) {
        Person<Integer> p1 = new Person<>(8888);
        Person<String> p2 = new Person<>("Hello sir");
        System.out.println("p1 key:"+p1.getKey());
        System.out.println("p2 key:"+p2.getKey());
 }

运行结果:


image.png

泛型类可以有多个类型变量。其中第一个域和第二个域使用不同的类型,如下:

public class Person<T, U> { } 

泛型接口:

定义一个泛型接口:

public interface IGenerator<T> {
    public T first();
    public T end();
}

实现泛型接口的类,未传入泛型实参:

public class MailGenerator<T> implements  IGenerator<T> {
    @Override public T first() {
        return null;
    }
    @Override public T end() {
        return null;
    }
}

当实现泛型接口的类,传入泛型实参时:

//<Person>指定泛型实参,参数类型为Person
public class PersonGenerator implements  IGenerator<Person>{
    @Override public Person first() {
        return null;
    }
    @Override public Person end() {
        return null;
    }
}

泛型通配符:

在Person类中加一个showKey方法:

//尖括号中的T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
public class Person<T> {
    private T key;  //声明key这个成员变量的类型为T,T的具体类型在实例化泛型类时指定
    public Person(T key){
        this.key = key;
    }

    //泛型方法getKey的返回值类型为T,T的具体类型也是实例化泛型类时指定的类型
    public T getKey(){
        return key;
    }

    public static void main(String[] args) {
        Person<Integer> p1 = new Person<>(8888);
        Person<String> p2 = new Person<>("Hello sir");
        showKey(p1); //此处编译报错
        showKey(p2);
    }

    public static void showKey(Person<String> p){
        System.out.println("p key:"+p.getKey());
    }
}

showKey(p1); 这行会报错,Person<Integer>并非Person<String>的子类。
要让showKey支持两种类型都输出key值。我们可以使用通配符,将代码修改如下:

public class Person<T> {
    private T key;  //声明key这个成员变量的类型为T,T的具体类型在实例化泛型类时指定
    public Person(T key){
        this.key = key;
    }

    //泛型方法getKey的返回值类型为T,T的具体类型也是实例化泛型类时指定的类型
    public T getKey(){
        return key;
    }

    public static void main(String[] args) {
        Person<Integer> p1 = new Person<>(8888);
        Person<String> p2 = new Person<>("Hello sir");
        showKey(p1);
        showKey(p2);
    }

    public static void showKey(Person<?> p){
        System.out.println("p key:"+p.getKey());
    }
}

用类型通配符?代替具体的类型实参。注意此处?是类型实参。

泛型上下边界:

将Person修改如下:

//尖括号中的T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
public class Person<T> {
    private T key;  //声明key这个成员变量的类型为T,T的具体类型在实例化泛型类时指定
    public Person(T key){
        this.key = key;
    }

    //泛型方法getKey的返回值类型为T,T的具体类型也是实例化泛型类时指定的类型
    public T getKey(){
        return key;
    }

    public static void main(String[] args) {
        Person<Integer> p1 = new Person<>(8888);
        Person<String> p2 = new Person<>("Hello sir");
        Person<Double> p3 = new Person<>(88.9);
        showKey(p1);
        showKey(p2);//此处报错
        showKey(p3);
    }

    public static void showKey(Person<? extends Number> p){
        System.out.println("p key:"+p.getKey());
    }
}

通过<? extends Number> 方式限制泛型实参类型必须是Number的子类。
showKey(p2);这一行代码会报错,因为String不是Number的子类

同样也可以用<? super Integer>方式限制泛型实参类型必须是Integer或Integer的父类。

类型变量的限定

有些情况,类或方法需要对类型变量加以约束:
比如,我们有如下一个方法用来求最小元素:

public static <T> T min(T[] array){}

假设我们要求T所属的类都有compareTo方法,方便比较元素大小。可以将代码改成如下:

public static <T extends Comparable> T min(T[] array){}

注意这里用的extends并不是继承的意思,而是表示T应该是绑定类型(Comparable)的子类型。限定T所属的类实现了Comparable接口。T和绑定类型可以是类,也可以是接口

一个类型变量或通配符可以有多个限定,例:

<T extends Comparable & Serializable> 

在有些情况下,比如遵循接口隔离原则,有些类可能实现了很多个接口,用在泛型类上会非常方便。

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

推荐阅读更多精彩内容