泛型:边界和通配符

1. 关系

Java 中,可以给一个对象赋值另一个兼容的对象,例如,我们可以把 Integer 赋值给 Object,因为 ObjectInteger 的超类。

Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger;   // OK

在面向对象语言中,这种关系称为 "is a",即 Integer is a Object。 但是, Integer 同时也是 Number 的一个子类,所以以下的代码也是允许的。

public void someMethod(Number n) { /* ... */ }

someMethod(new Integer(10));   // OK
someMethod(new Double(10.1));   // OK

同样的,在泛型中,如果类型是和 Number 兼容的,则可以调用 add 方法添加。

public class Box<T>{
    public void add(T t) {
    }
}


Box<Number> box = new Box<Number>();
box.add(new Integer(10));   // OK
box.add(new Double(10.1));  // OK

接下来思考一个问题

public void boxTest(Box<Number> n) { /* ... */ }

这个函数会接收一个什么类型的参数?
我们可以直接看到它需要的是 Box<Number>,但是,当我们传入一个 Box<Integer> 或者 Box<Double> 时,这是否允许?
答案是否定的,原因是 Box<Integer>Box<Double> 都不是 Box<Number> 的子类。

这是一个关于泛型通?;嵯萑氲奈笄?/p>

关系图

从上面的关系图中可以看出,尽管 IntegerNumber 的子类,但是 Box<Integer> 并不是 Box<Number> 的子类,它们是没有关系的。

2. 边界

有时我们可能需要限制参数的类型,比如在一个只操作数字的方法中,可能只需要 Number 类型或者其子类的参数,这就是边界参数的作用。

public class Box<T extends Number> {

    private T t;

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

    public static void main(String[] args) {
        Box<Integer> box = new Box<>();
        box.set(new Integer(10));
        
        Box<String> box1 = new Box<String>(); //报错
    }
}

在上面的例子中,通过 extends 关键词,约束了 Box 的上边界为 Number。
因此,在实例化 Box 时,指定的类型必须是 number 的子类。IntegerNumber 的子类,所以能通过编译,String 不是,会编译报错。

多边界约束

<T extends B1 & B2 & B3>

具有多个边界的类型变量是该边界中列出的所有类型的子类型。如果其中一个边界是类,则必须首先指定它。例如:

Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }

class D <T extends A & B & C> { /* ... */ }

下边这个会编译报错

class D <T extends B & A & C> { /* ... */ }

3. 通配符

在代码中,通配符使用 ? 字符,用来表示未知的类型。

通配符可以用于多种情况:作为参数、字段或局部变量的类型甚至有时作为返回类型。

上界通配符

声明一个上界通配符,只需要在 ? 后面跟上 extends 关键词,比如 <? extends A>

如果我们想定义一个函数,其接收一个 Numberlist 或者是 Number 的子类比如 Integer,Double,Float等的,我们看看下面的写法:

public class Box {

    public double sumOfList1(List<Number> list) {
        double s = 0.0;
        for (Number n : list)
            s += n.doubleValue();
        return s;
    }

    public double sumOfList2(List<? extends Number> list) {
        double s = 0.0;
        for (Number n : list)
            s += n.doubleValue();
        return s;
    }
}

这里定义了两个函数,分别是 sumOfList1(List<Number> list) 、 sumOfList2(List<? extends Number> list) ,不同之处是后者使用了通配符,那他们的使用有什么区别呢?

public static void main(String[] args) {
        Box box = new Box();
        List<Number> listNumber = Arrays.asList(1, 2, 3);
        List<Integer> listInteger = Arrays.asList(1, 2, 3);
        //调用使用了通配符的函数
        double result = box.sumOfList2(listNumber);
        double result1 = box.sumOfList2(listInteger);
        
        //调用未使用通配符的函数  
        double result2 = box.sumOfList1(listNumber);
        double result3 = box.sumOfList1(listInteger); //编译报错
    }

上面调用的代码可以看出,使用了通配符的函数, list 包裹的类型可以是 Number 及其子类的,而未使用通配符的,只能使用 Number 类型的。上界通配符放松对变量的限制。

无限通配符

无限通配符是对通配符 (?) 的一种特殊使用,用法如, List<?>,这个 list 的类型是未知的。

无限通配符的两个引用场景:

  • 编写的方法能直接使用 Object 提供的方法实现的。
  • 当使用泛型类的方法不依赖于泛型参数时。比如,List.size, List.clear。当类 Class<T> 的大多数方法都不依赖于 T 时,更常使用 CLass<?>。

比如:

public static void printList(List<Object> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}

这个函数的目的是打印 list 的所有的类型,但是现在它只能打印 Object 实例的列表,它是不能打印 List<Integer>, List<String>, List<Double> 等类型的列表的,因为它们都不是 List<Object> 的子类,无法传入。
要想实现此目的,我们可以使用无限通配符。

public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + " ");
    System.out.println();
}

List<Integer> li = Arrays.asList(1, 2, 3);
List<String>  ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);

因为多余任一实现类型的 A,List<A> 都是 List<?> 的子类。

下界通配符

上界通配符通过 <? extends A> 的形式,将未知类型限制其为 A 的子类。相似的,下界通配符是将未知类型限制为某个类型的父类。

声明一个下界通配符,只需要在 ? 后面跟上 super 关键词,比如 <? super A>。

public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }

上面的函数中,List<? super Integer> 限制了只能传入 List<Integer>, List<Number>, 或者 List<Object>,因为 Integer 的父类只有 Number, Object。

需要注意的,可以使用通配符指定上界,也可以指定下界,但不能两者同时指定。







参考:

Wildcards


相关文章:

泛型:为什么使用泛型与泛型的基本使用
泛型:边界和通配符
泛型:类型擦除

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