Java泛型-2(通配符)

泛型学习目录:

Java泛型-1(泛型的定义)
Java泛型-2(通配符)
Java泛型-3(实践篇-protostuff序列化与反序列化)
Java泛型-4(类型擦除后如何获取泛型参数)

1.1 泛型的通配符

在泛型代码中,称为通配符的问号(?)表示未知类型。
通配符可用于各种情况:作为参数,字段或局部变量的类型,有时候作为返回类型。

类型 说明 用途
<? extends A> 上限有界通配符,泛型参数一般是A类型或者A子类型 一般用于输入参数
<?> 无界限通配符
<? super A> 下限有界通配符,泛型类型一般是A类型或者A的父类型 一般用于输出参数

1.2 上限有界通配符(Upper Bounded)

You can use an upper bounded wildcard to relax the restrictions on a variable.

你可以使用上限通配符来放宽对变量的限制。

举一个例子:
例如:假如要编写适用于List<Integer>List<Double>List<Number>的方法,你会怎么做?

  • 你可能回想到,参数类型边界,即我们泛型上篇讲到的extends关键字。指定参数边界。
  • 所以可以使用通配符(?),后面跟着extends关键字,后面跟着上限

(小胖友情提醒:此时extends关键字代表的就是extends或者implements)。

答案:

List<? extends Number>

1.3 无界通配符

The unbounded wildcard type is specified using the wildcard character (?), for example, List<?>. This is called a list of unknown type.

使用通配符(?)指定无界限通配符类型,例如List<?>。这被称为未知类型的列表。

无界匹配符的适用范围:

  • 无论该对象的泛型是何种类型,均允许传入,例如:允许List<Integer>List<String>传入。

  • 代码使用泛型类方法不依赖于类型参数(T)时。例如,Class<?>经常被使用因为Class<T>里面的大部分方法都不依赖与T

还是举个例子说明吧:

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

请问是否能打印List<Integer>List<String>等类型?

重要的事情说三遍:

  1. List<Integer>不是List<Object>的子类型;
  2. List<Integer>不是List<Object>的子类型;
  3. List<Integer>不是List<Object>的子类型;

对于具体的类型AList<A>List<?>的子类型。此方法中,可以输入List<String>List<Integer>

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

无界通配符的注意事项:插入除null以外的数据,出现编译错误。

Class<?>类型的特点

List<Object>List<?>是不一样的,可以将Object或者Object的任何子类插入到List<Object>。但是只能在List<?>中插入null

1.4 下限有界通配符(Lower Bounded)

a lower bounded wildcard restricts the unknown type to be a specific type or a super type of that type.

下限有界通配符将未知类型(?)限制为该类型的特定类型或者超类类型。

使用通配符(?)表示下限有界通配符,后跟super关键字,后跟下限
<? super A>

注意:可以指定通配符的上限,也可以指定下限,但不能同时指定两者。

假设要编写方法,他的参数是Integer类型,但是为了最大限度地提高灵活性,可以在方法里面输入List<Integer>List<Number>List<Object>任何保存Integer值的方法。

可以使用List<? super Integer>类型,因为List<Integer>值匹配Integer类型的List,但是List<? super Integer>匹配Integer类型以及超类类型。

1.5 通配符和子类型

我们在上一节知道,尽管IntegerNumber的子类,但List<Integer>不是List<Number>的子类型,实际上,两个类型不相关,List<Number>List<Integer>的共同父类是List<?>

公共父类是List<?>

为了在这些类之间创建关系以便代码可以通过List<Integer>的元素访问List<Number>的方法,可以使用上限有界通配符

List<? extends Integer> intList=new ArrayList<>();
List<? extends Number> numList=intList; 

因为IntegerNumber的子类型,而numListNumber对象的列表。所以intList(Integer对象列表)numList之间存在关系。

通配符之间的继承关系

1.6 泛型的多重边界

在泛型中,可以使用&符号来设定多重边界,指定泛型类型t必须是A和B的共有子类型,而此时t就具有所有限定的方法和属性。可以更加方便的进行判断。

比如,社会中,每个人都有多重身份,比如是员工,是乘客,是儿子,是父亲。若是在程序中对一类人进行管理:在公交车费优惠系统中,对部分人员(如工资低于2500元的上班族并且是站立的乘客)车费打八折。

public class Me implements Staff, Passenger {
    @Override
    public int getSalary() {
        return 2000;
    }

    @Override
    public boolean isStanding() {
        return true;
    }

    //泛型的多重限定
    public static <T extends Staff & Passenger> void discount(T t) {
        if (t.getSalary() < 2500 && t.isStanding()) {
            System.out.println("恭喜你,车票打8折");
        }
    }

    //工资低于2500元的员工并且站立的乘客车票打8折
    public static void main(String[] args) {

        discount(new Me());

    }
}

interface Staff {
    //工资
    int getSalary();
}

//乘客
interface Passenger {
    boolean isStanding();
}

注意:使用&符号设定多重边界,指定泛型类型T必须是Staff和Passenger的共有类型。

泛型通配符和泛型的多重边界的区别

为什么要说明多重边界?是因为编辑者太少使用它。使用通配符虽然可以缩小泛型边界,但是可能还需要进行泛型类型判断。故可以使用泛型的多重限定来优雅的解决泛型问题。

1.7 通配符使用指南(重点)

我们在学习了泛型编程之后,是不是不确定什么时候使用上限有界通配符什么时候使用下限有界通配符

参考代码:

//带有两个参数的复制方法
void copy(src,dest){
}

1. 一个In变量:
"in"变量向代码中提供数据,正如上面代码中:src参数提供要复制的数据,因此它是in参数。

2. 一个out变量
"out"输出变量以供其他地方使用,正如示例中的desc

3. in&out变量
当然,一些变量即用于"进入"又用于"输出"。


在决定是否使用通配符以及适合使用哪种类型的通配符时,可以使用inout原则。下面是通配符指南:

  • in变量使用extends关键字定义带有上界有限通配符。
  • out变量使用super关键字定义带有下界有限通配符。
  • in变量是Object类型时,使用无界通配符。
  • in&out变量时,不使用通配符。

这些指南不适用于方法的返回类型应该避免使用通配符作为返回类型,因为他会强制程序员使用代码来处理通配符。

注意的是:
一个list被定义为List<? extends ...>可以被认为是只读的,但并不是一个严格的结论。
假设有两个类:

public class NaturalNumber {
    private int i;
    public NaturalNumber(int i) {
        this.i = i;
    }
}

class EvenNumber extends NaturalNumber {
    public EvenNumber(int i) {
        super(i);
    }
}

测试类编译错误

我们可以看到List<? extends NaturalNumber>在严格意义上不是只读的,因为可能无法存储或改变列表中现有的元素。

1.8 通配符的顺序

建议采用的顺序是List<T>、List<?>、List<Object>

List<T>、List<?>、List<Object>这三者都可以容纳所有的对象,但使用的顺序应该是首选List<T>,次之List<?>,最后选择List<Object>

1. List<T>是确定的某一个类型

List<T>表示的是List集合中的元素都为T类型,具体类型在运行期决定;List<?>表示的是任何类型;List<Object>则表示List集合中的所有元素为Object类型,因为Object是所有类型的父类,所以Object<Object>也可以容纳所有的类类型。从这一字面意义上分析,List<T>更符合习惯:编译者知道它是某一个类型,只是在运行期才确定。

2. List<T>可以进行读写操作

  • List<T>可以进行诸如add、remove等操作,因为他的类型是固定的T类型,在编译期不需要任何的转型操作。

  • List<?>是只读操作的,不能进行增加、修改操作。因为编译器不知道List容纳的是什么类型的元素,也就无法校验类型是否安全。而且List<?>读取出来的元素都是Object类型,需要主动转型,所以常用于泛型方法的返回值。但是List<?>虽然无法增加,修改元素,但是可以删除元素,如执行remove、clear等方法。

  • List<Object>也可以进行读写操作,但是它执行写入操作时需要向上转型,在读取操作后需要向下转型,而此时已经失去了泛型存在的意义了。

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