Java泛型遇到多态与Java泛型边界

说明:
今天,继续学姐java编程思想-泛型这一章的时候,对于通配符的理解不够明白,因而又网上查了些资料,发现一篇写的很好的文章,在此记录学习一下.php

原做者传送门java

http://build.cthuwork.com:8081/wordpress/category/java%E6%95%99%E7%A8%8B/java%E5%86%8D%E8%B0%88%E6%B3%9B%E5%9E%8B/编程

首先本文假定读者对Java的泛型有基础的了解,若须要请参考其余资料配合阅读。设计模式

“当java有了泛态和多态重写两个特性。两件快乐的事情重合在一块儿。而这两份快乐,又能带来更多的自由。获得的,本该是像梦境通常的自由设计模式……可是,为何呢,为何会变成这样呢……” -白·雨轩数组

泛型做为Java1.5添加到Java语言中的一个重要特性,起着提升类型安全、增长程序可读性等等做用。可是泛型在Java中的实现方式和某些行为却饱受诟病,最多见的说辞莫过于Java的泛型是伪泛型,会被擦除了。但其实之因此会拿这个说法来给泛型差评,其实很大程度上只是由于这是最容易理解的问题罢了,因此经常被一些只知其一;不知其二的人拿来讲事。Java泛型不尽人意的地方远不止这一个问题,但其余的不尽人意的地方大多都由于其自己的复杂性被大多数人忽略了。由于大多数人宁愿只使用简单的泛型特性也不肯去研究如何作到最大化的类型安全。甚至只会使用Java的API中的泛型类,根本不清楚、没尝试如何去正确实现一个本身的泛型类。安全

因此今天咱们来聊聊其中一个不尽人意的地方:泛型函数重写。markdown

重写做为多态的一个重要特性。由于Java中全部对象方法在被JIT优化前都会被当成虚函数,因此重写起着相当重要的用途。使用多态的动态单分派和虚函数表来实现虚函数的特性。重写自己在Java中的实现是十分优雅的,要是没记错的话应该是在Java1.5的时候还增长了返回值类型协变的特性,使得在使用特化的泛型类子类的时候不须要强转返回值了。ide

可是,看成为多态的招牌:“重写”,赶上做为类型安全的招牌:“泛型”的时候有些事情就不那么快乐了。原本两件快乐的事情重合在一块儿,会获得更多的快乐……可是,为何呢,为何会变成这样呢……(白学家打死www)。wordpress

其实问题归根结底仍是由于泛型和多态绕在一块儿后造成的复杂度。咱们先来考虑一下,什么样的重写才是正确且不会出现类型问题的:函数

1.函数标识符应该彻底。
1.函数参数应该是彻底相同的,不能容许任何的协变或逆变。
2.函数返回值应该兼容。这意味着返回值能够容许协变。
当符合以上3条时,咱们就能够说重写是正确的。那么,若是咱们说泛型和重载 在!一!起! 在!一!起!(某MV中毒www)的时候会有啥问题,那么也只能出如今返回值上。好,那咱们就把重点放在返回值上!

首先咱们先要明白,什么叫协变。在没有泛型以前,协变指的是从为子类隐式转换父类,这很好理解。在有了泛型后,就有点不一样了。首先泛型自己是不支持协变的,除非你使用通配符(从数组吸收的教训)。当你使用通配符后,泛型就是能够协变的了,这时候就由要转换的类型是否和通配符兼容来决定。

那么也就是说,若是咱们尝试重写一个返回值包含有TypeVariable的函数,那么要么返回值如出一辙、要么协变到拥有一样父类泛型参数的子类、要么把其中的泛型参数协变为其通配符兼容的泛型参数、要么类型自己和泛型参数一块儿协变。

如今咱们来看看这段代码:

interface I1 {

}

interface I2 extends I1 {

}

interface I3 extends I2 {

}

class A {
    <T extends I1> List<T> foo() {
        return null;
    }
}

class B extends A {
    @Override
    <T extends I2> List<T> foo() {
        return null;
    }
}

这段代码的意图在于想让B的foo函数返回一个List

List<? extends I1> foo() {
        return null;
    }
}

class B extends A {
    @Override
    List<? extends I2> foo() {
        return null;
    }
}

如今呢?这段代码是能够经过编译的。那问题到底出在哪里?咱们再尝试将代码改成

class A {
    <T extends I1> List<? extends T> foo() {
        return null;
    }
}

class B extends A {
    @Override
    <T extends I2> List<? extends T> foo() {
        return null;
    }
}

这种状态下通配符的范围显而易见至少应该和第2组代码同样。可是这段代码仍然不能经过编译。这时候大多数人基本上都弃治了,要么认为遇到了BUG,要么各类病急乱投医。那么问题到底在哪?其实问题的根本在于若是想重载泛型函数的话那么要么不写任何TypeVariables,要么函数的TypeVariables必须如出一辙。What!?Excuse me???不是说重载是否正确只取决于那3条么?怎么还要要求TypeVariables如出一辙?

首先要声明的一点是所谓的TypeVariables必须如出一辙是指TypeVariables的Bounds必须相同,因此说标识符是能够不一样的,好比 T a()和 A a()的重载是合法的。其实之因此要设计成要求TypeVariables如出一辙是有缘由的。咱们考虑下面的代码:

// Abstract Stage

interface Shitagi {

}

interface Pants extends Shitagi {

}

interface BlueWhitePants extends Pants {

}

interface PinkPants extends Pants {

}

interface BearPants extends Pants {

}

// Real Stage

interface RealShitagi extends Shitagi {

}

interface RealPants extends Pants, RealShitagi {

}

interface RealBlueWhitePants extends BlueWhitePants, RealPants {

}

interface RealPinkPants extends PinkPants, Pants {

}

interface RealBearPants extends BearPants, RealPants {

}

class A<E extends Shitagi> {
    <T extends Pants> A<T> foo() {
        return null;
    }
}

class B<E extends RealShitagi> extends A<E> {
    @Override
    <T extends RealPants> B<T> foo() {
        return null;
    }
}

在看下面这一段前,先S!T!O!P。想一想到底这玩意到底有没有问题。而后若是有问题,问题在哪?

=======================EMPTY AREA=======================

首先这段代码仍然不能被编译(必定是由于太污,因此编译器拒绝与您说话并丢出了辣鸡BUG www),可是这段代码更能体现TypeVariables不相同所致使的问题。首先当咱们调用B.foo的时候返回的类型的通配符范围是B

class A<E extends Shitagi> {
    <T extends Pants> A<T> foo() {
        return null;
    }
}

class B<E extends RealShitagi> extends A<E> {
    @Override
    <T extends Pants> B<T> foo() {
        return null;
    }
}

这样编译器就能直接检测出B.foo的返回值类型B<>(这里的2个括号并非打多了,是表示返回值的类型并非一个Wildcard,而是由调用函数时提供的泛型参数T来决定的)和B自己的泛型参数B不兼容。就能避免出现类型安全问题。好比若是这里的T是PinkPants的话,那么返回值类型就必须为B,和B不兼容。

好那么问题来了,咱们怎么修复咱们的代码,使其能够工做?嗯,这个问题才是关键。首先咱们来看看刚刚那个例子,刚刚那例子的问题在于B.foo的返回值,要使B.foo的既和A.foo兼容,又和B的泛型参数兼容,就只能这么作:

class A<E extends Shitagi> {
    <T extends Pants> A<? extends T> foo() {
        return null;
    }
}

class B<E extends RealShitagi> extends A<E> {
    @Override
    <T extends Pants> B<? extends T> foo() {
        return null;
    }
}

这样改动后B.foo的返回值类型就是B

class A<E extends Shitagi> {
    <T extends Pants> A<? extends T> foo() {
        return null;
    }
}

class B<E extends RealShitagi> extends A<E> {
    @Override
    <T extends Pants> B<? extends RealPants> foo() {
        return null;
    }
}

这段代码行不行?固然不行,首先第一个问题是返回值类型和A.foo不兼容,其次问题是T被吃掉了。到这里大部分人都一脸懵逼了,没办法动T,怎么玩?一些会玩黑魔法的码农也许会建立一个class C extends B。可是这是异端,设计上毫无美感,而且之后扩展程序时也可能会遇到问题。既然咱们没办法动TypeVariables,那么能不能把T的上界设置为某个类的TypeVariable,而后对其动手?好想法,咱们来实际尝试一下:

class A<E extends Shitagi, BASE extends Pants> {
    <T extends BASE> A<? extends T, BASE> foo() {
        return null;
    }
}

class B<E extends RealShitagi, BASE extends RealPants> extends A<E, BASE> {
    @Override
    <T extends BASE> B<? extends T, BASE> foo() {
        return null;
    }
}

咱们给类加了一个名为BASE的泛型参数,用于传递T的返回值类型。这段代码可否经过编译?固然能够,这段代码没有任何问题。惟一缺点就是在建立B的对象时须要手工指定一个多余的泛型参数,固然咱们也能够直接写成:

class B<E extends RealShitagi> extends A<E, RealPants> {
    @Override
    <T extends RealPants> B<? extends T> foo() {
        return null;
    }
}

若是咱们不须要B还有子类,这是目前看来的最佳解决方案。等会,这样B.foo的TypeVariable是否是和A.foo的不同了?其实仍是同样的,毕竟在B里面来看A,BASE这个泛型参数就是RealPants,毕竟在继承的时候就已经被特化为RealPants了。因此没有任何问题,只是写法有些许区别。好,那么如何解决返回值类型的泛型参数是Wildcard的问题?那就更简单了,直接删掉Wildcard解决问题。代码能经过编译,完事OK?目前看来确实是只要把PAD塞进去就完事OK了(wwwww)。毕竟这种问题仍是能够经过一(塞)些(入)tr(P)ic(A)k(D)来解决的。

那么如今问题来了,有没有什么状况是用这种最终手段解决不了的。答案是有。假设咱们如今Shitagi系列的自己接口自己就自带泛型,咱们来看下面的代码:

// Base Ningen

interface Ningen {

}

interface Onnanoko extends Ningen {

}

interface Otoko extends Ningen {

}

// Real Ningen

interface KanameMadoka extends Onnanoko {

}

interface EnkanNoKotowari extends KanameMadoka {
    // 圆环之理指引着你www
}

interface ShiinaMashiro extends Onnanoko {

}

interface ShiinaMakuro extends ShiinaMashiro {
    // 椎名真黑:实行Plane Cwww
}

interface OgisoSetsuna extends Onnanoko {

}

interface Sprite extends OgisoSetsuna {
    // 老板来一箱82年的雪碧!
}

interface NoumiKudryavka extends Onnanoko {

}

interface NoumiKudryavkaAnatolyevnaStrugatskaya extends NoumiKudryavka {
    // Wafu,你可识得库特全名www
}

interface ItouMakoto extends Otoko {

}

interface KinoshitaHideyoshi extends Ningen {
    // 秀吉的性别就是秀吉www
}

// Abstract Stage

interface Shitagi<N extends Ningen> {

}

interface Pants<N extends Ningen> extends Shitagi<N> {

}

interface BlueWhitePants<N extends Ningen> extends Pants<N> {

}

interface PinkPants<N extends Ningen> extends Pants<N> {

}

interface BearPants<N extends Ningen> extends Pants<N> {

}

// Real Stage

interface RealShitagi<N extends Ningen> extends Shitagi<N> {

}

interface RealPants<N extends Ningen> extends Pants<N>, RealShitagi<N> {

}

interface RealBlueWhitePants<N extends Ningen> extends BlueWhitePants<N>, RealPants<N> {

}

interface RealPinkPants<N extends Ningen> extends PinkPants<N>, Pants<N> {

}

interface RealBearPants<N extends Ningen> extends BearPants<N>, RealPants<N> {

}

class A<E extends Shitagi<ORIGIN_N>, ORIGIN_N extends Ningen> {
    <T extends Pants<NEW_N>, NEW_N extends Ningen> A<T, NEW_N> gift(NEW_N ningen) {
        return null;
    }
}

class B<E extends RealShitagi<ORIGIN_N>, ORIGIN_N extends Ningen> extends A<E, ORIGIN_N> {
    @Override
    <T extends RealPants<NEW_N>, NEW_N extends Ningen> B<T, NEW_N> gift(NEW_N ningen) {
        return null;
    }
}

如今咱们把B.foo改为了B.gift用于gift胖次www。首先因为TypeVariables不一样,因此是这段代码没法经过编译。那么能不能像刚刚那样解决?若是你尝试使用上面的方法解决问题,你会发现行不通了,由于RealPants如今带了一个泛型参数NEW_N。这就尴尬了,而NEW_N这个泛型参数是在调用函数的时候才能使用的,在类里面是找不到的。

这里有一种很不美观且晦涩难懂的解决办法,将NEW_N放到类里面:

static class A<E extends Shitagi<ORIGIN_N>, ORIGIN_N extends Ningen, BASE extends Pants<NEW_N>, NEW_N extends Ningen> {
    <T extends BASE> A<T, NEW_N, RealPants<?>, ?> gift(NEW_N ningen) {
        return null;
    }

    @SuppressWarnings("unchecked")
    static <E extends Shitagi<ORIGIN_N>, ORIGIN_N extends Ningen, NEW_N extends Ningen> A<E, ORIGIN_N, Pants<NEW_N>, NEW_N> prepare(A<E, ORIGIN_N, ?, ?> a) {
        return (A<E, ORIGIN_N, Pants<NEW_N>, NEW_N>) a;
    }
}

static class B<E extends RealShitagi<ORIGIN_N>, ORIGIN_N extends Ningen, BASE extends RealPants<NEW_N>, NEW_N extends Ningen> extends A<E, ORIGIN_N, BASE, NEW_N> {
    @Override
    <T extends BASE> B<T, NEW_N, RealPants<?>, ?> gift(NEW_N ningen) {
        return null;
    }

    @SuppressWarnings("unchecked")
    static <E extends RealShitagi<ORIGIN_N>, ORIGIN_N extends Ningen, NEW_N extends Ningen> B<E, ORIGIN_N, RealPants<NEW_N>, NEW_N> prepare(B<E, ORIGIN_N, ?, ?> a) {
        return (B<E, ORIGIN_N, RealPants<NEW_N>, NEW_N>) a;
    }
}

这样代码就能经过编译了,使用时咱们用一种很别扭的方式:

B<RealBlueWhitePants<NoumiKudryavka>, NoumiKudryavka, RealPants<?>, ?> b = <Other Code>; ShiinaMashiro mashiro = <Other Code>; B<RealBlueWhitePants<ShiinaMashiro>, ShiinaMashiro, RealPants<?>, ?> new_b = B.<RealBlueWhitePants<NoumiKudryavka>, NoumiKudryavka, ShiinaMashiro>prepare(b).<RealBlueWhitePants<ShiinaMashiro>>gift(mashiro); 

先强转,再调用(嗯,很是Mashiro,Mashiro得我都想给设计Java泛型的人执行Plan C寄刀片了www)。如下是上面代码使用实例函数的实现:

class A<E extends Shitagi<ORIGIN_N>, ORIGIN_N extends Ningen, BASE extends Pants<NEW_N>, NEW_N extends Ningen> {
    <T extends BASE> A<T, NEW_N, RealPants<?>, ?> gift(NEW_N ningen) {
        return null;
    }

    @SuppressWarnings("unchecked")
    final <P_NEW_N extends Ningen> A<E, ORIGIN_N, Pants<P_NEW_N>, P_NEW_N> prepareA() {
        return (A<E, ORIGIN_N, Pants<P_NEW_N>, P_NEW_N>) this;
    }
}

class B<E extends RealShitagi<ORIGIN_N>, ORIGIN_N extends Ningen, BASE extends RealPants<NEW_N>, NEW_N extends Ningen> extends A<E, ORIGIN_N, BASE, NEW_N> {
    @Override
    <T extends BASE> B<T, NEW_N, RealPants<?>, ?> gift(NEW_N ningen) {
        return null;
    }

    @SuppressWarnings("unchecked")
    final <P_NEW_N extends Ningen> B<E, ORIGIN_N, RealPants<P_NEW_N>, P_NEW_N> prepareB() {
        return (B<E, ORIGIN_N, RealPants<P_NEW_N>, P_NEW_N>) this;
    }
}

调用改成:

B<RealBlueWhitePants<NoumiKudryavka>, NoumiKudryavka, RealPants<?>, ?> b = <Other Code>; ShiinaMashiro mashiro = <Other Code>; B<RealBlueWhitePants<ShiinaMashiro>, ShiinaMashiro, RealPants<?>, ?> new_b = b.<ShiinaMashiro>prepareB().<RealBlueWhitePants<ShiinaMashiro>>gift(mashiro); 

Java再谈泛型之泛型边界

首先本文假定读者对Java的泛型有基础的了解,若须要请参考其余资料配合阅读。

泛型的泛参(type argument)可使用实际类型或者通配符(wildcard)。其中通配符能够经过边界(bound)来限制其接受的实际参数的类型。根据其种类,能够分为无界(unbounded)、上界(upper bound)和下界(lower bound)。其泛型边界决定了输入(input)和输出(output)分别能接受什么类型。

输入为其函数的参数、属性可以赋值的值的类型,输出为函数的返回值、获取到的属性的值的类型。

如下将详细描述。

1、实际类型

泛型的泛参可使用实际类型。也就是相似于List,直接指定泛型的类型。这时候泛型的表现最容易理解,输入和输出都为实际类型。须要注意的一点是,泛型不支持协变(Covariant),协变需使用通配符。为何泛型不支持协变呢。咱们先从支持协变的数组开始考虑。考虑如下代码:

Object[] array = new String[1];
array[0] = 12.450F;

这段代码是能够经过编译的,然而会让静态类型的Java语言在没有任何强制类型转换的状况下出现类型异常。咱们尝试往一个String类型的数组索引为0的位置赋值一个Float类型的值,这固然是行不通和彻底错误的。Java数组可以协变是一个设计上的根本错误,它能致使你的代码在你彻底不知情的状况下崩溃和异常,但如今改已经为时已晚。幸亏咱们有和常用集合API,不然最多见的状况可能以下:

public Number evil;
public void setAll(Number[] array) {
    for (int i = 0;i < array.length;i++) {
        array[i] = evil;
    }
}
public void looksGood() {
    atSomewhereWeDontKnown(); //We summoned evil to our kawaii and poor code
    Float[] ourKawaiiArray = getOurKawaiiArray(); //Oops
}
public void atSomewhereWeDontKnown() {
    evil = 12450;
}
public Float[] getOurKawaiiArray() {
    Float[] weWantFloatFilled = new Float[0xFF];
    setAll(weWantFloatFilled); //Buts... we got (1)E(2)V(4)I(5)L(0)...
    return weWantFloatFilled;
}

咱们可不想让(1)E(2)V(4)I(5)L(0)充满咱们的代码。因此,泛型吸收了这个教训,自己就是为了提升类型安全性而设计的泛型不能犯这样的低级错误。因此你不能写如下代码:

List<Object> array = new ArrayList<String>;
array.set(0, 12.450F);

这段代码在第一行就没法经过编译,由于你尝试协变一个泛型。其解决办法和其余的说明将在后续讨论。

2、通配符

1.无界通配符

无界通配符为”?”,能够接受任何的实际类型做为泛参。其能接受的输入和输出类型十分有限。

①可用输入类型

严格意义上不能接受任何的类型做为输入,考虑如下代码:

List<?> list = new ArrayList<String>();
list.add("123");

你可能以为这段代码看起来没有问题。一般会这样考虑,咱们能够简单的把无界通配符”?”当作Object,往一个Object类型的列表加一个String有什么问题?何况其实际就是String类型。其实并不能经过编译,这并非编译器出现了错误。这里有个逻辑漏洞,咱们仔细考虑无界通配符的意义。无界通配符表明其接受任何的实际类型,但这并不意味着任何的实际类型均可以做为其输入和输出。其语义上有微妙的但巨大的区别。其含义是不肯定究竟是哪一个实际类型。多是String,多是UUID,多是任何可能的类型。若是这是个UUID列表,那么往里面加String等就会出事。若是是String列表,往里面加UUID等也会出事。或者咱们无论其是什么类型的列表,往里面加Object,然而Object里有你的实际类型的属性和方法么。即便实际是Object列表,咱们也没法肯定。那么,无界通配符就不能接受任何输入了么,看起来是这样。其实有个例外,null做为一个十分特殊的值,表示不引用任何对象。咱们能够说String类型的值能够为null、UUID类型的值能够为null,甚至Object类型的值能够为null。不管是什么类型,均可以接受null做为其值,表示不引用任何对象。因此无界通配符的输入惟一可接受的是可为全部类型的null。

②可用输出类型

无界通配符的输出类型始终为Object,由于其意义为接受任何的实际类型做为泛参,而任何的实际类型均可以被协变为Object类型,因此其输出类型天然就为Object了。没有什么须要注意的地方。

2.上界通配符

上界通配符为”extends”,能够接受其指定类型或其子类做为泛参。其还有一种特殊的形式,能够指定其不只要是指定类型的子类,并且还要实现某些接口。这种用法很是少用,我在不少开源项目中基本没看到这种用法。因为这和本章内容无关,不影响输入和输出的类型,因此暂不描述。

①可用输入类型

严格意义上一样不能接受任何的类型做为输入,出于严谨目的,咱们再从头分析一遍,此次以Minecraft的源代码为例,考虑如下代码:

List<? extends EntityLiving> list = new ArrayList<EntityPlayer>();
list.add(player);

你可能以为这段代码又没问题了,EntityPlayer确实继承了EntityLiving。往一个EntityLiving的列表里加EntityPlayer有什么问题?放肆!12450!好不闹/w\。这里的问题在于若是其实是EntityPig的列表呢。这么想你就应该懂了,和无界通配符差很少,其只是限定了列表必须是EntityLiving的子类而已,咱们并不知道实际是什么。因此在这里咱们只能添加EntityLiving类型的对象。是否是以为有什么不对?对了,我就是超威蓝猫!好不闹/w\,咱们能在EntityLiving上调用EntityPlayer的getGameProfile么,明显不能,何况咱们到底能不能实例化EntityLiving也是个问题。这里真的很容易混淆概念,必定要牢记,只能使用null做为上界通配符的输入值。

②可用输出类型

好了,此次终于能玩了,上界通配符的输出类型为其指定的类型,实际上若是通配符位于泛型类的声明中例如:

public class Foo<T extends EntityLiving> {
    public T entity;
}

这个类中entity字段的实际类型不是全部类型的父类Object了,而是EntityLiving,这能够用查看字节码的方式证明。固然其类型是Object也不会有太大的差异,能够想到的问题是当咱们以某种方式往其内部传入了Object类型或其余不是EntityLiving类型或其子类的对象时,可能会出现类型转换异常或者更严重的留下随时代码会崩溃的隐患。而直接使用EntityLiving类型做为其实际类型就会在尝试这么作的同时抛出类型转换异常,从而避免这种问题。

3.下界通配符

下界通配符为”super”,能够接受其指定类型或其父类做为泛参。可能不少人都没有用过下界通配符,由于其真的不多用。其主要用处之一是在使用Java或第三方的API的泛型类时,对泛参类型不一样,但泛参具备继承关系,且主要关注其输入的泛型对象进行概括。以Minecraft的源码为例,考虑如下代码:

private EntityMob ourKawaiiMob;
private EntityMob otherKawaiiMob;
public int compareMobEntity(Comparator<? super EntityMob> comparator) {
    return comparator.compare(ourKawaiiMob, otherKawaiiMob);
}

此方法能够接受一个比较器,用于比较两EntityMob。这里的含义是,咱们但愿接受一个EntityMob或其父类的比较器。例如Comparator只会把EntityMob当作一个Entity进行比较,这样咱们就能够对EntityMob的某一部分进行比较。咱们不能将一个彻底不是EntityMob的父类的比较器,例如Comparator做为参数传入。也不能将一个EntityMob的子类的比较器,例如Comparator做为参数传入。由于实际咱们比较的是EntityMob或其子类的对象,即便咱们传入的是其子类的比较器,咱们也不能保证不会发生用Comparator比较一个EntityEnderman的状况。又或者即便咱们利用Java的类型擦除这么作了,java的动态类型检查会强制抛出ClassCastException。因此在这种状况下应该使用下界通配符。

①可用输入类型

下界通配符的输入类型为其指定的类型或子类。由于其意义为接受其指定类型或其父类做为泛参。那么不管咱们提供的对象是什么类型,只要是其指定的类型或子类的对象,那么毫无例外必定是其指定的类型的对象。咱们不能提供其指定的类型的父类做为对象,考虑如下代码:

private EntityLiving our;
private EntityLiving other;
Comparator<? super EntityMob> comparator = new EntityMobComparator();
comparator.compare(our, other);

这段代码不能经过编译,咱们尝试用一个EntityMob的比较器来比较EntityLiving。不仔细考虑可能觉得这并无什么问题,EntityMob的比较器彻底有能力来比较EntityLiving啊?可是实际状况是若是这段代码成功编译,并且没有动态类型检查的话EntityMob的比较器就可能会尝试其获取EntityLiving并无的,属于EntityMob的属性,而后就会获取到非法的数据,或致使Java运行时崩溃,这固然是不行的。好在咱们即便这么作了,Java也会强制抛出ClassCastException。

②可用输出类型

下界通配符的输出类型始终为Object,由于其意义为接受其指定类型或其父类做为泛参,咱们并不知道具体是哪个父类。而任何的实际类型均可以被协变为Object类型,因此其输出类型天然就为Object了。

3、回顾泛型边界和输入输出类型的区别

泛型边界并不直接表明着能接受的输入输出的类型,其含义为能接受什么样的实际类型。而输入输出类型能是什么则是根据泛型边界的含义得出的,其中的限制是因为咱们只能经过泛型边界对实际类型进行猜想而产生的,但愿你们能仔细理解其中的含义。

Java再谈泛型之泛型是语法糖

首先本文假定读者对Java的泛型有基础的了解,若须要请参考其余资料配合阅读。

泛型系统是做为Java 5的一套加强类型安全及减小显式类型转换的系统出现的。泛型也叫参数化类型,顾名思义,经过给类型赋予必定的泛型参数,来达到提升代码复用度和减小复杂性的目的。

在Java中,泛型是做为语法糖出现的。在虚拟机层面,并不存在泛型这种类型,也不会对泛型进行膨胀,生成出相似于List、List之类的类型。在虚拟机看来,List这个泛型类型只是普通的类型List而已,这种行为叫泛型擦除(Type Erasure)。

那么在Java中泛型是如何如何实现其目的的呢?Java的泛型充分利用了多态性。将无界(unbounded)的通配符(wildcard)理解为Object类型,由于Object类型是全部除标量(Scalar)之外,包括普通的数组和标量数组的类型的父类。将全部有上界(upper bound)的通配符理解为其上界类型例如将被理解为CharSequence类型。并在相应的地方自动生成checkcast字节码进行类型检查和转换,这样就既能够实现泛型,又不须要在字节码层面的进行改动来支持泛型。这样的泛型叫作伪泛型。

下面是几个帮助理解的例子

public class Foo<T extends CharSequence> {
    private T value;
    public void set(T value) {
        this.value = value;
    }
    public T get() {
        return this.value;
    }
    public static void main(String[] args) {
        Foo<String> foo = new Foo<String>();
        foo.set("foo");
        String value = foo.get();
    }
}

编译后:

public class Foo {
    private CharSequence value;
    public void set(CharSequence value) {
        this.value = value;
    }
    public CharSequence get() {
        return this.value;
    }
    public static void main(String[] args) {
        Foo foo = new Foo();
        foo.set("foo");
        String value = (String) foo.get();
    }
}
相关文章
相关标签/搜索