Java基础——泛型 宽泛与约束

一、泛型

什么是泛型

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

为什么用泛型

提高的代码的复用性,减少了数据的类型转换(泛型提供了类型检查),同时保证了类型安全。

减少类型转换?如,使用Comparable比较时每次都需要类型强转

泛型参数类型

规则

  1. 使用简练的名字作为类型形参的名字,最好为单个的大写字母,比如 T ;
  2. 如果一个泛型类有泛型方法,对于它们的类型形参来说,应避免使用相同的名字;
  3. 泛型的类型实参只能是类类型,不能是基本数据类型。

常见的参数类型起名

  • K 键,比如映射的键 key的类型
  • V 值,比如Map的值 value类型
  • E 元素,比如Set<E> Element表示元素,元素的类型
  • T 泛型,Type的意思

注意:泛型的类型名字是可以随便写的,上面的K,V,E,T只是我们常用的用法,有一定含义,我们对应的把T换成HAHAHA,也算是可以的。

如何了解泛型

我们可以大概从下面几点来开展文章。

  • 泛型方法
  • 泛型类
  • 泛型接口
  • 泛型的通配符

二、泛型方法

泛型方法定义格式

 修饰符 <泛型参数列表> 返回值类型 方法名 (参数列表) {
    ……
}

.
.
我们先来看一个简单的泛型方法

    private static <T> void inputContent(T t){
        System.out.println("打印传入的数据:"+t);
    }

简单规则

  • 泛型返回返回值(void也需要)之前需要有 泛型类型参数的声明,由尖括号包括,比如<T>
    (也可以理解为带有返回值前带有泛型类型的都是参数方法 )
  • 形参参数可以不是泛型参数

code1 泛型方法用法参考

public class TestClass {
    public static void main(String[] args) {
        inputContent(666);
        inputContent("哈哈哈");
        new Num("张三").say();
        
        Num num = doClass(new Num("李四"));
        num.say();
    }    
    
    private static <T> void inputContent(T t){
        System.out.println("打印传入的数据:"+t);
    }
    
    private static <T> T doClass(T t){
        return t;
    }
    
}

class Num{
    private String name;
    public Num(String str){
        this.name = str;
    }
    public void say(){
        System.out.println(name +"  调用了say方法");
    }
}

.
.
输出

打印传入的数据:666
打印传入的数据:哈哈哈
张三  调用了say方法
李四  调用了say方法

.
.
code2 泛型方法,多参数类型以类型名

public class TestClass {
    public static void main(String[] args) {
        Map map = getAMap("张三", 18);        
        Set<String> set = map.keySet();
        for(String key : set){
            System.out.println("key "+key);
            System.out.println("value "+map.get(key));
        }
    }    
    private static <HAHA extends String,XIXI> Map<String,XIXI> getAMap(HAHA haha,XIXI xixi){
        Map map = new HashMap<HAHA,XIXI>();
        map.put(haha, xixi);
        return map;
    }
}

输出

key 张三
value 18

从这个例子中,我们看到,泛型方法返回值前的<>里面的参数可以有多个,而且,参数类型名称我们基本可以随便起,不局限于一个字母,比如起名为HAHA,需要注意的是,

  • 我们最好起有默认含义的,比如T,K,E,V
  • <>里面的泛型参数列表已经限定了参数参数的类型,方法后面的形参的泛型参数类型只能在前面声明的类型中选择。

至于extends是什么怎么用,后面会涉及。

三、泛型类

格式

class 类名<泛型类型1,泛型类型2……>{
   ……
}

简单规则

  • 在类名后面带上<>,在<>里面声明泛型参数列表

code3 泛型方法示例

public class TestClass {
    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<Integer>();
        Box<String> stringBox = new Box<String>();
     
        integerBox.add(new Integer(10));
        stringBox.add(new String("张三"));

        System.out.println("整型值为 :"+integerBox.get());
        System.out.println("字符串为 :"+ stringBox.get());
    }    
}

class Box<T> {
      private T t;
     
      public void add(T t) {
        this.t = t;
      }
     
      public T get() {
        return t;
      }
}

.
.
输出

整型值为 :10
字符串为 :张三

如上 Box类就是泛型类

.
.
.

泛型接口

泛型接口,就是在接口后面跟着一对<>,在<>里面放上泛型参数列表。
在接口里面的抽象方法,我们就可以结合接口定义上的泛型参数列表做一些事情。
当然,方法可以使用泛型参数类型必须是接口的声明的泛型参数列表以内的。

code4 泛型接口的使用

public class TestClass {
    public static void main(String[] args) {
        SayClass class1 = new SayClass();
        System.out.println(class1.doSomeThing());     
    }    
}

class SayClass implements Generator<String>{
    @Override
    public String doSomeThing() {
        String  str = "被指定的类型为String";
        return str;
    }    
}

interface Generator<T> {
    // 接口的里的抽象方法繁殖可以不是T
    // 但是一般这里我们指定为T才有一定意义,不然不就白泛型了
    public T doSomeThing();
}

输出

被指定的类型为String

.
.
.

四、泛型的限定符/通配符

四.1、泛型限定符的种类

  • <? extends Type> 子类限定/上界通配符
  • <? super Type> 父类限定 / 下界通配符
  • <?> 无限定
    Person<?> 和 Person<? extends Object> 等同.

为了演示,我们弄几个类,一个父类Water,Water下有SofdDrink(汽水)和Juice(果汁),Juice下有分为OrangeJuice和AppleJuice类。

image.png

四.2、子类限定 extends

<? extends Type>
子类限定,有上界,代表传入实参必须是指定类型的子类

  • 1、只读,不可写
  • 2、对于<? extends T>,那么限定传入类型只能 T类或者T的子类
  • 3、<T extends Runnable & Serializable>如果给T限定多个类型,则需要使用符号&,比如<T extends Runnable & Serializable>

code5 泛型的子类限定示例

import java.util.ArrayList;
import java.util.List;

public class TestClass{
    public static void main(String[] args) {
        
        Water water = new Water("纯净水");
        SodaDrink sodaDrink = new SodaDrink("可乐");
        Juice juice = new Juice("果汁");
        OrangeJuice orangeJuice = new OrangeJuice("果粒橙");
        AppleJuice appleJuice =new AppleJuice("牛顿牌果汁");
        
        ArrayList<OrangeJuice> tempList = new  ArrayList<OrangeJuice>();
        tempList.add(orangeJuice);
        
        // ======  
        // 证明1、<? extends T>,那么限定传入类型只能 T类或者T的子类
        ArrayList<? extends Juice> arrayList1 = new  ArrayList<Juice>();
        //ArrayList<? extends Juice> arrayList2 = new  ArrayList<Water>(); // 编译报错 Water不是Juice也不是Juice的子类
        ArrayList<? extends Juice> arrayList3 = new  ArrayList<OrangeJuice>();
        
        // 证明2、ArrayList<? extends Juice> arrayList1,是只读的,不可写入
        // 我们无法插入任何数据到arrayList1中,因为我们无法确定插入的到底是哪一种类型,是Juice? 还是OrangeJuice? 还是AppleJuice?
        // 泛型extends是不可写的,非要写也只能是写入null
        //arrayList1.add(orangeJuice); // 
        
        // 泛型extends是可读的
        ArrayList<? extends Juice> arrayList4 = tempList;
        Juice juice23 =arrayList4.get(0);
        juice23.sayName();    
    }
}

// 商店
class Shop<T>{
    private T t;
    
    public Shop(T t){
        this.t= t;
    }

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}


class Water {
    private String name;

    public Water(String name) {
        this.name = name;
    }
    public void sayName() {
        System.out.println("name===: "+name);
    }
    
    public String getName(){
        return name;
    }
}

class SodaDrink extends Water{

    public SodaDrink(String name) {
        super(name);
    }
    public void sayName() {
        System.out.println("汽水/苏打水===: "+getName());
    }    
}


class Juice extends Water{

    public Juice(String name) {
        super(name);
    }
    public void sayName() {
        System.out.println("果汁===: "+getName());
    }    
}

class OrangeJuice extends Juice{

    public OrangeJuice(String name) {
        super(name);
    }
    public void sayName() {
        System.out.println("橙汁===: "+getName());
    }
}

class AppleJuice extends Juice{

    public AppleJuice(String name) {
        super(name);
    }
    public void sayName() {
        System.out.println("苹果汁===: "+getName());
    }    
}

.
打印

橙汁===: 果粒橙

如上,我们知道,对于作了子类限定的泛型
1、实参只能传入限定类型的子类或者其子类
2、是只读的,不能调用set,只能set(null)


? extends T 限定了类型为T和T的子类型

    ArrayList<? extends Juice> arrayList1 = new  ArrayList<Juice>();
    ArrayList<? extends Juice> arrayList2 = new  ArrayList<Water>(); // 编译报错 Water不是Juice也不是Juice的子类
    ArrayList<? extends Juice> arrayList3 = new  ArrayList<OrangeJuice>();

对于上面的代码,arrayList1和arrayList3是合法。arrayList2会编译报错,因为我们限定了传入的是Juice或者Juice的子类,但是arrayList2传入的是Juice的父类Water。

泛型extends的读和写


可读并且只能读取到限定的类型(无法读取到子类型)
对于ArrayList<? extends Juice> arrayList1

  • 可以从 arrayList1 中读取到 Juice 对象, 因为 arrayList1 中包含的元素是 Juice 类型或 Juice 的子类型.
  • 无法从 arrayList1 中读取到 OrangeJuice 类型, 因为 arrayList1 中可能保存的是 AppleJuice 类型.

.
.


不可写,无法写入除了null外任何元素,包括Juice类型 或者 Juice类型的子类
对于ArrayList<? extends Juice> arrayList1

  • 不能添加 Juice 到 arrayList1 中, 因为 numberArray 有可能是List<AppleJuice> 类型
  • 不能添加 OrangeJuice 到 numberArray 中, 因为 numberArray 有可能是 List<AppleJuice> 类型
  • 不能添加 AppleJuice 到 numberArray 中, 因为 numberArray 有可能是 List<OrangeJuice> 类型

四.2、父类限定 super

  • 可写,不可读。
  • 对于<? super T>,那么限定传入类型只能 T类或者T的父类

code6 泛型的父类限定示例

public class TestClass{
    public static void main(String[] args) {
        
        Water water = new Water("纯净水");
        SodaDrink sodaDrink = new SodaDrink("可乐");
        Juice juice = new Juice("果汁");
        OrangeJuice orangeJuice = new OrangeJuice("果粒橙");
        AppleJuice appleJuice =new AppleJuice("牛顿牌果汁");
        
        // ======  
        // 证明1、<? super T>,那么限定传入类型只能 T类或者T的父类
        ArrayList<? super Juice> arrayList1 = new  ArrayList<Juice>();
        ArrayList<? super Juice> arrayList2 = new  ArrayList<Water>(); 
        //ArrayList<? super Juice> arrayList3 = new  ArrayList<OrangeJuice>();// 编译报错 OrangeJuice不是Juice也不是Juice的子类
        
        // 证明2、ArrayList<? extends Juice> arrayList1,是可写,不可读。
        // 关于写,我们只能写入 Juice或者Juice的子类,而不能写入Juice的父类
        arrayList1.add(juice); 
        arrayList1.add(orangeJuice); 
        //arrayList1.add(water);  // 编译报错
        
        arrayList2.add(juice); 
        //arrayList2.add(water);  // 编译报错
        
        //关于读,我们无法读取到类型为Juice的数据,或者Juice的值类型,因为存入的可能是Juice,也可能是Juice的子类
        // 唯一可以确定的是,读取的出来的肯定是个Object,但是如果你非常强转也是可以的
        Object obj =  arrayList1.get(0); // 只能确定读出出来的是Object
        Juice jui =  (Juice) arrayList1.get(1);
        jui.sayName();

    }
}


// 商店
class Shop<T>{
    private T t;
    
    public Shop(T t){
        this.t= t;
    }

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}


class Water {
    private String name;

    public Water(String name) {
        this.name = name;
    }
    public void sayName() {
        System.out.println("name===: "+name);
    }
    
    public String getName(){
        return name;
    }
}

class SodaDrink extends Water{

    public SodaDrink(String name) {
        super(name);
    }
    public void sayName() {
        System.out.println("汽水/苏打水===: "+getName());
    }    
}


class Juice extends Water{

    public Juice(String name) {
        super(name);
    }
    public void sayName() {
        System.out.println("果汁===: "+getName());
    }    
}

class OrangeJuice extends Juice{

    public OrangeJuice(String name) {
        super(name);
    }
    public void sayName() {
        System.out.println("橙汁===: "+getName());
    }
}

class AppleJuice extends Juice{

    public AppleJuice(String name) {
        super(name);
    }
    public void sayName() {
        System.out.println("苹果汁===: "+getName());
    }    
}

打印输出

橙汁===: 果粒橙

? super T 限定了类型为T和T的父类型

        ArrayList<? super Juice> arrayList1 = new  ArrayList<Juice>();
        ArrayList<? super Juice> arrayList2 = new  ArrayList<Water>(); 
        //ArrayList<? super Juice> arrayList3 = new  ArrayList<OrangeJuice>();// 编译报错 OrangeJuice不是Juice也不是Juice的子类
        

这里很清楚的说明,我们限定了传入类型只能是限定类型的或者其父类。

泛型super的读和写

对于 ArrayList<? super Juice> arrayList1

  • 无法确定读出来的是Juice或者OrangeJuice或者AppleJuice。
  • 可以确定的是,读出来肯定是一个Object(如果你非要强转也行)。

.
.

  • 写入的必须是T类型的或者T的子类型
    .
    .
    .

四.3、泛型限定extends和super的使用原则 PECS

PECE 原则: Producer Extends, Consumer Super

因为extends,可读不可写;super可写不可读

所以:
1、如果我们的操作基本上是只读的,那么用extends
2、如果我们的操作基本上是只写的,那么用super


** 小结:extends和super **

  • 阿敏说:extends

比如 ArrayList<? extends Juice> arrayList1,肯定不可以写入,我们都不知道你放进来的是苹果汁还是橙汁,然后别人到会来取得时候,已经限定了要一杯果汁,那我机器人这么笨怎么知道给一杯什么,所以你不要给我放进来了,因为我也无法给出去啊,多浪费啊,少年你自己喝了吧。
你这么笨机器人,你可以取,但是不不能存,没有存哪有取,要你何用?
少年你这么说就不对了,你可以不要一个个添加嘛,你先把所有的符合我要求的数据批量准备好,比如 ArrayList<Juice> temp1 或者 ArrayList<Juice> temp2 ,数据你自己填充好,然后直接给我,这样我给别人的时候也好给啊,我这么聪明的机器人一定不会弄错啦。

ArrayList<Juice> tempList1 = new  ArrayList<Juice>(); 
        tempList1.add(new Juice("果汁1"));
        tempList1.add(new Juice("果汁2"));
        
        ArrayList<? extends Juice> juiceList1 = tempList1; 
        juiceList1.get(0).sayName();
        juiceList1.get(1).sayName();
        
        // =======
        ArrayList<OrangeJuice> tempList2 = new  ArrayList<OrangeJuice>();
        tempList2.add(new OrangeJuice("果粒橙1"));
        tempList2.add(new OrangeJuice("果粒橙2"));
        
        ArrayList<? extends Juice> juiceList2 = tempList2; 
        juiceList2.get(0).sayName();
        juiceList2.get(1).sayName();
        
输出

果汁===: 果汁1
果汁===: 果汁2
橙汁===: 果粒橙1
橙汁===: 果粒橙2

.

  • 阿敏说:super存取疑惑
    .
    比如 ArrayList<? super Juice> arrayList2,可以写入,为什么,你放进来的可以是果汁,可以是苹果汁,可以是橙汁,都没问题。但是取是万万不能的,里面存放辣么多不同的饮料,或者可能不同的饮料,你说要果汁,我是机器人那么笨,我拿什么给你。

参考:
JAVA泛型•通配符限定

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

推荐阅读更多精彩内容

  • 我们知道,使用变量之前要定义,定义一个变量时必须要指明它的数据类型,什么样的数据类型赋给什么样的值。 假如我们现在...
    今晚打肉山阅读 929评论 0 1
  • 前言 泛型(Generics)的型变是Java中比较难以理解和使用的部分,“神秘”的通配符,让我看了几遍《Java...
    珞泽珈群阅读 7,574评论 12 51
  • 泛型的好处 使用泛型的好处我觉得有两点:1:类型安全 2:减少类型强转 下面通过一个例子说明: 假设有一个Tes...
    德彪阅读 1,067评论 0 0
  • 引言:泛型一直是困扰自己的一个难题,但是泛型有时一个面试时老生常谈的问题;今天作者就通过查阅相关资料简单谈谈自己对...
    cp_insist阅读 1,804评论 0 4
  • 哎,昨夜又失眠了,静谧的夜晚,一个人,在阴暗角落里蜷着身子,静静的,痴痴的,痴痴的呆着。像这样的夜晚不知经...
    芋头西米露阅读 340评论 0 1