Java泛型的简历

本博文涉及的代码已上传Github,结合代码看,更容易理解:竹鱼/JavaBasis

目录

我问世的背景

我的本质

泛型类

 泛型类的几个Tips:

泛型接口

泛型方法

限定类型参数

我的庐山真面目(伪泛型)

桥接方法

面试题


我问世的背景

在我还未问世之前,Java程序猿&媛很容易在使用集合过程中碰到类型转换异常(ClassCastException)。

我伴随着JDK1.5的发布降生,我为程序猿&媛提供了编译时类型检测机制,如果你们在编译时不小心犯错,我是不能容忍你们的,你们必须修复它。

我的本质

我的本质是参数化类型,就是将类型参数化,方法的定义和调用想必大家都不陌生,我与之类似,只不过定义方法时传的参数是一个具体的数据类型,而定义我的时候是一个未知的数据类型,调用方法时传的实参是一个具体的值,而调用我时传的是一个具体的数据类型。

泛型类

我存在于三个地方,分别是泛型类、泛型接口、泛型方法。首先我从泛型类开始让大家了解我。

创建泛型类,只需要在类的后面添加类似<T>这样的泛型标识即可,通常我们使用的泛型标识有T、E、K、V、S、U等等。T的类型由外部使用类决定:

PS:常见类型参数命名约定如下:T - Type、E - Element (Java Collections Framework广泛使用)、K - Key、V - Value、S,U,V etc. 。

我有一个小缺点,就是不能支持基本数据类型,这个是有历史原因的,后面再讲。

在创建泛型类对象时,如果没有指定泛型具体类型,将按Object处理:
 Lottery<Object> objectLottery = new Lottery<>();
同一泛型类,根据不同泛型类型创建的对象,逻辑上可以看成不同对象,但本质上是同一类型。

 泛型类的几个Tips:

1>已指定父类或父接口泛型类型为某一具体引用类型,子类或实现类不需要再指定泛型类型;否则,子类或实现类的泛型标识必须包含与父类或父接口同样的泛型标识。


2>原始类型是没有指定任何类型参数的泛型类或接口的名称(有点没表达清楚,我也不知道怎么讲,看下面的代码应该就清楚了<~.~>):

        Box<Number> numberBox1 = new Box<>();//要创建Box的参数化类型,必须为形式类型参数T提供一个实际的类型参数
        Box box = new Box();//如果省略实际的类型参数,则创建Box的原始类型

但是,非泛型类或接口类型不是原始类型。
原始类型显示在遗留代码中,因为许多 API 类(例如 Collections 类)在 JDK 5.0 之前不是泛型的。使用原始类型时,基本上可以获得 pre-generics 行为——Box 为你提供 Object。为了向后兼容,允许将参数化类型赋值给其原始类型,但将原始类型分配给参数化类型,则会出现unchecked警告,如果你使用原始类型调用在相应的泛型类中定义的带有泛型标识的方法(包括泛型方法和以泛型标识作为形参的普通方法),也会出现unchecked警告:

3>类型通配符:用"?"代替具体的类型实参。通配符一般在方法中使用,不能在定义泛型类时使用

上图所述问题就需要用到泛型通配符解决:我们用“?”可以代替任意类型,并且使用“extends”关键字指定我的上界这样就相当于为装Integer的Box和装Number的Box建立了继承关系。
PS:“使用super关键字可以为我指定下界”

    private static void showBoxDate(Box<? extends Number> box) {
        Number firstDate = box.getFirstDate();
        System.out.println("firstDate === " + firstDate);
    }

泛型接口

泛型接口与泛型类定义方式类似,不再赘述:

public interface Plate<T> {
    public void set(T t);

    public T get();
}

泛型方法

泛型方法的定义是在方法的返回值类型前面添加类似<T>这样的泛型标识列表,泛型方法可以在任何类或者接口中定义,没有规定说必须在泛型类或泛型接口中定义。

public <T> AIPlate<T> getAIPlate(){
    return new AIPlate<T>();
}
    /**
     * 随机获取奖品的泛型方法
     * @param list 奖品的List
     * @param <T> 未知类型的奖品
     * @return 具体的奖品
     */
    public <T> T getPrize(ArrayList<T> list){
        return list.get(random.nextInt(list.size()));
    }

泛型方法的调用:

        //小明给的这个盘子只能装香蕉
        AIPlate<Banana> aiPlate = xiaoMing.<Banana>getAIPlate();

        //方法泛型推断
        AIPlate<Banana> aiPlate1 = xiaoMing.getAIPlate();

由上图可以看出,创建泛型类对象Lottery时传入的泛型类型是Integer,但调用泛型方法时返回值类型却是String,由此可见,泛型方法的泛型标识与泛型类的泛型标识没有一毛钱关系,它的类型是由调用者独立决定的。

限定类型参数

有时你可能想限制可以在参数化类型中用作类型参数的类型。例如,对数字进行操作的方法可能只希望接受 Number或其子类的实例,这就是限定类型。

class NaturalNumber<T extends Number> {
    private T t;

    public NaturalNumber(T t) {
        this.t = t;
    }

    /**
     * 判断是否是偶数
     *
     * @return true表示是偶数
     */
    public boolean isEven() {
        return t.intValue() % 2 == 0;
    }
}

上述代码中,T表示应该绑定类型的子类型,Number表示绑定类型,子类型和绑定类型可以是类也可以是接口。
PS:
1>通过“super”关键字限定泛型类型下届;
2>具有多个限定的类型变量是范围中列出的所有类型的子类型。如果范围之一是类,则必须首先指定它。

<3> extends左右都允许有多个,如 T,V extends Comparable & Serializable 

限定类型参数是实现通用算法的关键:

上面代码报错原因是大于运算符( > )仅适用于基本类型,你不能使用> 运算符比较对象。要解决此问题,需要使用Comparable 接口限定的类型参数:

    public static <T extends Comparable> int countGreaterThan(T[] anArray, T elem) {
        int count = 0;
        for (T e : anArray) {
            if (e.compareTo(elem) > 0) {
                count++;
            }
        }
        return count;
    }

我的庐山真面目(伪泛型)

前面已经讲过了,我是JDK1.5之后才降生的,在此之前是没有泛型这个概念的,但是我缺能很好的融入JDK这个大家庭里面,那是因为我只存在与代码编译阶段,在进入JVM之前,我的信息会被擦除掉,这叫做类型擦除。类型擦除分为无限制类型擦除有限制类型擦除无限制类型擦除会把类型参数擦除为Object:

有限制类型擦除则会把类型参数擦除为它的上界:

类型擦除也存在方法中:

桥接方法

桥接方法存在与泛型接口中,如下代码所示,我定义了泛型接口,并指定它的类型参数上界为Number,定义 showDate方法,定义 UpperBoundGenericImpl实现该接口:

还不明白的话看看下面的图应该就一目了然了。

面试题

Q1:JAVA泛型原理?
A1:JAVA泛型是JDK1.5引入的新特性,其实它是一种伪泛型机制。为什么这么说呢?因为JAVA开发团队为了向下兼容,JVM势必不能支持泛型,泛型类型只存在于编译期,为开发人员提供编译期类型检测机制,一旦到了运行期,也就是进入JVM之前,泛型信息就会编译器被擦除掉,而不会被写入字节码文件中。但是你会发现,我们通过反射却可以拿到之前定义的泛型信息,这是因为泛型信息被保留在了类的常量池中。

举个生活中的例子:去景区玩的时候,你需要买门票,门票又分为普通门票和活动门票(可以玩景区的某些或全部项目的门票),门票就相当于是泛型标识,只有在你进入景区检票的时候有用(编译期检测机制),检票的时候会撕票(编译器进行泛型擦除),进入景区就没有门票啥事了(进入JVM之后就没有泛型啥事了),但是你的购票信息会被保留在景区的电脑里(泛型擦除后会在类的常量池中

Q2:Java编译器具体是如何擦除泛型的?
A2:分四种情况:

1>首先检查泛型类型,获取目标类型;
PS:ArrayList<T> 中的“T”称为类型参数;ArrayList<String>中的“String”称为实际类型参数;ArrayList<T>整个称为泛型类型;ArrayList<String>整个称为参数化的类型(ParameterizedType) 。
2>擦除类型变量,并替换为限定类型:
  a.如果泛型类型的类型变量没有限定(<T>),则会被替换为Object;
  b.如果有限定(T extends XXXClass),则会被替换为XXXClass;
  c.如果有多个限定(T extends XXXClass & XXXAClass & XXXBClass),则会被替换为第一个边界XXXClass;
3>在必要时,编译器内部会帮我们进行类型转换,以保证类型安全;
4>生成桥方法,保证类和接口的实现关系,即多态性。

Q3:Java泛型有哪些缺陷?
1>Java泛型类型不能使用基本数据类型,这跟泛型擦除机制有关,泛型在编译时会进行类型擦除,最后只保留原始类型,而原始类型只能是Object及其子类,所以不能使用基本类型作为泛型类型的实参。

2>不能使用instanceof运算符,这个也跟擦除机制有关,擦除类型参数之后,运行时不跟踪类型参数,因此无法区分两者。
PS:我们可以通过通配符的方式进行instanceof运行期检查(不建议)

3>泛型类的静态上下文中,类型变量失效:不允许静态变量,但是静态泛型方法是可以的。静态变量在类中是被所有类的对象共享的,对象创建的代码执行先后顺序是static的部分,然后才是构造函数等等,而泛型类型只有类被实例化之后才能确定下来。

4>不能创建参数化类型的数组:

//6.可以定义泛型数组,但不能实例化泛型数组
Restrict<Double>[] restrictArray;
//Restrict<Double>[] restricts = new Restrict<Double>[10];// 编译报错

​​​​​​​5>泛型类不能extends Exception/Throwable:

private class Problem<T> extends Exception;// Generic class may not extend 'java.lang.Throwable'

也不能捕获泛型类对象:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值