基础篇——泛型(Generics)

为什么要使用泛型?

Java设计之初并不知道会往容器中存放什么类型的元素,因此元素类型都设定为Object,这样就什么都能放了。但是这么设计有明显的缺点:①取出元素的时候必须进行强制类型转换异常;②如果不小心往集合里加了不相同类型的元素可能会导致类型异常,进行equals、compare比较的时候尤为明显;③在很多地方都需要强制类型转换,增加了变成的复杂度,影响了代码的美观和维护成本。
因此泛型应运而生。

泛型概述:

泛型(generics)是Java 1.5中引入的新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。只要编译时不出现问题,运行时就不会出现ClassCastException(类型转换异常)。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
Java集合都实现了泛型,允许程序在创建集合时就可以指定集合元素的类型,例如List<String>就表明这是一个只能存放String类型的List。List<String>就是参数化类型,也就是泛型,而String就是该List<String>泛型的类型参数。
泛型只在编译阶段有效,在编译之后会进行泛型擦除的操作,不会传递到运行阶段。也就是说编译后的class文件中是不包含任何泛型信息的。

泛型的规则:

1.泛型的类型参数只能是类类型(包括自定义类)和通配符,不能是简单类型;
2.同一种泛型可以对应多个版本(类型参数不同),不同版本的泛型类实例是不兼容的;
3.泛型的类型参数可以有多个,例如<K , V>;
4.泛型的类型参数可以使用extends语句,形如<T extends Number>称为有界的类型参数。
5.不能对确切的泛型类型使用 instanceof 操作;
注:A instanceof B:判断A是否是B的实例对象或者B子类的实例对象。
6.不能创建一个确切的泛型类型的数组;

泛型的好处:

1.在编译的时候检查类型安全,减少了运行时异常,降低crash率;保证了如果在编译时没有发出警告,则在运行时就一定不会产生ClassCastException(类型转换异常);
2.集合创建时就指定了集合元素的类型,取出元素的时候不需要强制类型装换了;
3.提高了代码的复用性、减少维护成本;

泛型类:

1.定义一个类,在该类名后面添加类型参数声明部分(由尖括号分隔)。
2.每一个类型参数声明部分可以包括一个或多个类型参数,参数间用逗号隔开。
3.使用<T>来声明一个类型持有者名称,然后就可以把T当作一个类型来声明成员、参数、返回值类型。T仅仅是个名字,可以自行定义。

/**
 * 泛型类
 */
 
public class GenericsBox<T> {
    private T t;
 
    public void add(T t) {
        this.t = t;
    }
 
    public T get() {
        return t;
    }
}
        //泛型类调用
        GenericsBox<Integer> integerGenericsBox = new GenericsBox<>();
        GenericsBox<String> stringGenericsBox = new GenericsBox<>();
 
        integerGenericsBox.add(new Integer(25));
        stringGenericsBox.add(new String("年龄"));
        LogUtil.e("泛型类调用", stringGenericsBox.get() + ":" + integerGenericsBox.get());

泛型接口:

1.定义一个接口,在该接口名后面添加类型参数声明部分(由尖括号分隔)。
2.每一个类型参数声明部分可以包括一个或多个类型参数,参数间用逗号隔开。

/**
 * 泛型接口
 */
 
public interface GenericsInterface<T> {
 
    public abstract void genericsInterface1(T element);
 
    public abstract <T> void genericsInterface2();
}

泛型方法:

定义一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当的处理每一个方法调用。
定义泛型方法的规则:
1.所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回值类型之前;
2.每一个类型参数声明部分包括一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符;
3.类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符;

    /**
     * 泛型方法
     * 打印各种类型的数组中的元素
     *
     * @param inputArray
     * @param <T>
     */
    public static <T> void printLog(T[] inputArray) {
        for (T element : inputArray) {
            LogUtil.e("打印数组中的元素", element + "");
        }
    }
        //创建各种类型的数组(Integer、Double、Character)
        Integer[] integerArray = {1, 2, 3, 4, 5};
        Double[] doubleArray = {1.1, 2.2, 3.3, 4.4, 5.5};
        Character[] characterArray = {'a', 'b', 'c', 'd', 'e'};
 
        printLog(integerArray);
        printLog(doubleArray);
        printLog(characterArray);

静态方法与泛型:

如果在类中定义使用泛型的静态方法,需要添加额外的泛型类型参数声明,即将该静态方法定义为泛型方法。即使静态方法要使用泛型类中已经声明过的类型参数也不可以。

/**
 * 静态方法与泛型
 */
 
public class GenericsStatic<T> {
 
    public static <T> void show(T t) {
    }
 
    //以下是错误的
//    public static void show(T t) {
//    }
}

有界的类型参数:

限定被允许传递到一个类型参数的类型种类范围。
1.要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,继承上界。例如:上界通过形如<T extends Comparable<T>>来定义,表示类型只能接受Comparable及其下层子类类型。这就是有界的类型参数的目的。
注意:<T extends Comparable<T>>这里的限定使用关键字extends,后面可以是类也可以是接口。extends统一的表示了原有的extends和implements的概念,应该理解为T类型是实现了Comparable接口的类型,或者是继承了XX类的类型。
2.泛型仍要遵循Java单继承、多实现的体系,所以当某个类型参数需要使用extends限定,且有多种类型的时候,只能存在一个类,并且类写在第一位,接口列在后面。形如<T extends Number & Comparable & Callback>

    /**
     * 泛型方法
     * 比较三个值并返回最大值
     *
     * @param x
     * @param y
     * @param z
     * @param <T>
     * @return
     */
    public static <T extends Comparable<T>> T maxValue(T x, T y, T z) {
        //假设x是最大值
        T max = x;
 
        //如果y比max大,则将y赋值给max
        if (y.compareTo(max) > 0) {
            max = y;
        }
        //如果z比max大,则将z赋值给max
        if (z.compareTo(max) > 0) {
            max = z;
        }
        //返回最大值
        return max;
    }
        //输出最大值
        LogUtil.e("最大值为:", maxValue(3, 7, 5) + "");
        LogUtil.e("最大值为:", maxValue(3.3, 3.7, 5.3) + "");
        LogUtil.e("最大值为:", maxValue("apple", "pear", "orange"));

类型通配符(?)

为了解决类型被限制死了不能动态根据实例来确定的缺点,引入了类型通配符。类型通配符一般使用 ? 代替具体的类型参数。例如:List<?>在逻辑上是List<String>、List<Integer>等所有List<具体类型实参>的父类。
1.如果只指定了<?>,而没有extends,则默认是允许Object及其下的任何Java类,也就是任意类。
2.通配符也可以向上限制,通过形如List<? extends Comparable>来定义,表示类型只能接受Comparable及其下层子类类型;
3.通配符还可以向下限制,通过形如List<? super Comparable>来定义,表示类型只能接受Comparable及其上层父类类型;
因为getData()的参数是List<?>类型,所以name、age、number都可以作为这个方法的实参,这就是通配符的作用。
因为getNumberData()的参数是有上限的List<? extends Number>类型的,如此定义就是类型通配符泛型值接受Number及其下层子类类型。

    /**
     * 类型通配符的使用
     */
    public static void getData(List<?> data) {
        LogUtil.e("类型通配符的使用", data.get(0) + "");
    }
    
    public static void getNumberData(List<? extends Number> data) {
        LogUtil.e("类型通配符的使用(Number)", data.get(0) + "");
    }
        //类型通配符
        List<String> name = new ArrayList<>();
        List<Integer> age = new ArrayList<>();
        List<Character> blood = new ArrayList<>();
 
        name.add("张三");
        age.add(25);
        blood.add('A');
 
        getData(name);
        getData(age);
        getData(blood);
 
//        getNumberData(name);
        getNumberData(age);
//        getNumberData(blood);

通配符的PECS原则:

1.Producer Extends:使用<? extends T>的集合类,只能作为Producer(生产者)向外提供(get)元素;而不能作为Consumer(消费者)对外获取(add)元素;
2.Consumer Super:使用<? super T>的集合类,只能作为Consumer(消费者)对外获取(add)元素;而不能作为Producer(生产者)向外提供(get)元素;
3.如果同时需要读取以及写入,那就不能使用通配符。

        //通配符的PECS原则
        List<? extends String> extendsList = new ArrayList<>();
        List<? super String> superList = new ArrayList<>();
 
//        extendsList.add("李四");
        String s = extendsList.get(0);
        superList.add("张三");
//        Object s1 = superList.get(0);

早计划,早准备,早完成。 欢迎关注!交流!Star!

GitHub:https://github.com/wangyang0313

微信公众号:一个灵活的胖子MrWang

CSDN:https://blog.csdn.net/qq941263013

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