黑马程序员--04.泛型深入--03【泛型知识补充】【类型推断】

来源:互联网 发布:离线运行unity3d游戏 编辑:程序博客网 时间:2024/05/17 22:01

泛型深入--3

泛型知识补充           类型推断

----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------

1.    泛型知识补充

1). Java中使用泛型擦除的原因

(1). Java中泛型产生的时机

[1]. JDK5之后,Java引进了泛型,之前Java中是没有泛型的。

[2]. Java中的泛型擦除

Java的泛型表面上非常类似C++的泛型。但是这种相似仅仅局限在代码的表面。Java语言中,泛型基本上完全在Compiler中实现【主要使用javac执行类型检查类型推断】。然后Javac会泛型清除并生成非泛型的字节码

【C++并不是这样的,所以C++的泛型Java的泛型仅仅是表面上具有相似性

(2). Java采用擦除的原因

由于泛型最开始不是Java的组成部分。如果不选择擦除,那么Java增加泛型必须要修改JVM的指令集,这是JVM厂商难以逾越的障碍。所以,为了暂时使用泛型的优点又不修改JVM的指令集采用擦除手段进行折中

2). extends 进行多边界泛型限定

(1). 泛型限定的extends可以使用在类型化参数中,也可以使用在类型参数定义的时候。

(2). 在泛型限定的时候,可以使用&来指定的多个父边界

【注意】由于是extends表示继承关系,java仅支持类之间的单继承关系,所以extends后面最多只能跟一个类类型,可以跟多个接口类型 (表示是这个接口的实现子类)。

[1]. 举例:跟多个类类型编译出错


[2]. 正确示例

class A{    public static<V extends Number & Runnable &Serializable & AnnotatedElement & Cloneable > void multiGenericRefine(){}}

3). 其他知识点

(1). 泛型可以用在普通方法,构造方法和静态方法中。

这里举一个构造方法采用泛型的例子

【举例】

class A{    public <T> A(T a){       System.out.println(a);        System.out.println("Generic Constructor iscalled...");    }} public class TestII{    public static void main(String[] args) {       Stringstr ="I amBenjamin...";       Aa =new A(str);    }}

【打印结果】

I am Benjamin...

Generic Constructor is called...

(2). 参数化异常

可以使用类型参数来表示异常,这样的异常称为参数化异常

举例

public static <T extends Exception> void sayHello () throws T{    System.out.println("Say Hello please...");}

2.    泛型类型推断

1). 泛型类型推断的基本知识

(1). 参数推断使用的场景

[1]. 泛型参数推断使用的场景:当同一个类型参数T在多个位置使用时候,并且接受的实际参数类型并不一致,此时javac并不报错,而是进行参数推断

[2]. 举例

class A{    public static <T> T getTwoNumbers(T a, T b){       System.out.println(a.getClass().getName());       System.out.println(b.getClass().getName());            return null;    }}

{1}. 调用的时候,以下方式全部正确


{2}. 配合返回值类型,编译出错


以上的问题就涉及到泛型的类型推断问题

(2). Java泛型类型推断的基本原则

[1]. 固化类型参数传递原则

{1}. 当泛型类型(原始类型中的<>中的类型参数没有具体化) 出现在方法的参数列表中,这个时候,调用这个方法实例化类型参数的时候具有传递性(一致原则) 。

{2}. 第一个<>中的类型参数一旦为固化为具体的参数之后,就要求方法列表返回值类型所涉及到类型参数被固化地方(无论是其他的泛型参数还是脱离<>的类型参数)必须和第一个<>中的固化类型一致【理解为一致原则

{3}. {2}中的表述也可以理解为第一个<>将类型参数固化为某一个具体的类型之后,就把这个具体类型传递到其他类型参数的地方【理解为传递原则

[2]. 固化类型参数交集原则/直接父类原则

{1}. 前提:如果在方法的参数列表中,没有出现泛型类型也就是涉及到泛型类型参数的地方全部是脱离了<>的类型参数作为形参的数据类型

【结论1那么实际调用方法的时候,可以在实参中多个需要固化类型参数的位置传入不一致的实际类型编译器javac可以通过编译。此时,类型参数javac推断为实参列表多个已经被固化不同具体类型交集,也就是这几个具体类型直接父类

【结论2如果此时方法的定义中返回值类型也是脱离<>的类型参数,那么位于方法返回值的类型参数的类型也是【结论1】的结果。

【总结】

泛型参数推断问题关键就看方法的形参列表中有没有泛型类型(<>中含有类型参数)如果没有,可以放心大胆地进行具体参数固化可以在具有同一个类型参数多个位置传入不一致具体参数但是出现在返回值类型中的类型参数javac推断的最终类型结果必须遵循交集原则/直接父类原则

       否则,一旦出现泛型类型,就必须遵循所有具体类型统一或者具体类型传递原则如果不一致或者没有进行参数传递,就无法通过编译

2). 泛型类型推断的举例

(1). 题目1:类型参数E被编译器推断为什么类型?

[1]. 方法原型:static <E> void swap(E[]a, int i,int j){}

[2]. 实际调用:swap(new String[3], 3, 4)

【分析】这里面仅仅涉及到一个类型参数E,其实是不必进行参数推断的。这是最简单的情况,E被javac推断成java.lang.String类型。

(2). 下面的实际调用可以通过编译么?如果通过变异的话,类型参数T被编译器推断为什么类型?

[1]. 方法原型:

static <T> void printColl(Collection<T> collection, Tobj){}

实际调用:

{1}1. printColl(newArrayList<Integer>(),new Integer(3));

【分析】由于方法类型参数中有泛型类型Collection<T>,所以所有的T遵循传递性原则。实际调用的时候,第一次<>中的T被newArrayList<Integer>()固化为Integer,所以后面出现的T固化的地方,必须都是Integer,这个编译通过。T被javac推断为Integer类型。

{1}2. printColl(newArrayList<Integer>(), 3.5f);

【分析】编译无法通过。违反了固化类型传递原则。第一个<>中的T被固化为Integer,但是后面传入的是3.5f会被自动打包成Float类型,Float与Integer不一致,所以编译无法通过。

[2]. 方法原型:

static <T> void copy(T[] src, T[]dest){}

实际调用

{1}. copy(new Date[10],new String[10]);

【分析】编译通过。因为没有参数化类型出现,所以,多个位置可以传入不同的参数,这个时候,javac把T推断成 Date和String的直接父类Object (交集原则)。

[3].方法原型:

static <T> T add(T a, T b){returnnull;}

实际调用

{1}1. Number x =A.add(1,2.5f);

【分析】编译通过。因为没有参数化类型出现,所以,多个位置可以传入不同的参数,这个时候,javac把T推断成自动装箱的Integer和Float的直接父类Number(交集原则)。这个方法有泛型返回值,所以返回值类型被推断为Number。因此用Number类型的引用接受通过编译。

{1}2. Number x =A.add(1,"String");

【分析】编译无法通过。Javac将T推断为Integer和String的直接公共父类Object。但是Object类型的对象不能被Number类型的引用指向。

----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------

原创粉丝点击