解析java泛型(二)

     上篇我们简单的介绍了java中泛型的最基本的内容,知道了什么是泛型以及泛型对我们的程序编写有什么好处,最后以类型限定收尾。本篇将从类型限定开始阐述java泛型中很重要的概念:通配符 一、何为通配符      首先我们看一段代码:

/*我们声明了一个泛型方法*/
public class Pair<T> {
    public static <T extends Double> void show(myArray<T> a){   
    }
}

     上文声明了一个泛型方法,但是实在是太过啰嗦,于是我们可以使用通配符来简化这种泛型方法的声明形式。有时我们也称通配符为更加简洁的类型变量的限定。接下来我们看看使用通配符如何声明一个泛型方法。

public class Pair<T> {
    public static void show(myArray<? extends Double> a){   
    }
}

     对比两段代码,我们可以看出来,类型限定也就是<T extends Double>主要用于类型参数的定义,声明在访问修饰符后面函数返回值之前;而通配符也就是<? extends Double>主要用于实例化类型参数,声明在类型变量之前,简化了泛型方法的声明。所以,凡是可以由通配符实现的都可以使用类型限定来实现,因为通配符是简化了的类型限定。 二、通配符的只读性      以上是这个通配符的一个特性,通配符的只读性是其第二个重要特性,我们知道:Child类继承于Base类是不能推出Pair<Child>继承于Pair<Base>的,所以:

public static void print(Pair<Base>){

}

     Pair<Child>是不能作为参数来使用上述方法了,因为Pair<Child>和Pair<Base>是完全不同的两种类型。但是我们可以使用通配符完成两者的联系。

public static void print(Pair<? extends Base>){

}

     这样,Base及其子类都是可以调用此方法的。说明了,Pair<Base>和Pair<Child>都是Pair<? extends Base>的子类型。下面我们看看为什么通配符要具有只读性,不允许修改属性值。

/*定义泛型类*/
public class Pair<T> {
    private T a;
    public Pair(T a){
        this.a = a;
    }
    public void setA(T a){
        this.a = a;
    }
    public T getA(){
        return this.a;
    }
}
/*main方法*/
public class Test2 {
    public static void main(String[] args){     
        Pair<Integer> p = new Pair<>(0);
        p.setA(10);
        System.out.println(p.getA());
    }
}
输出结果:10

没有使用通配符之前,一切安好;下面使用通配符:

/*定义泛型类*/
public class Pair<T> {
    private T a;
    public Pair(T a){
        this.a = a;
    }
    public void setA(T a){
        this.a = a;
    }
    public T getA(){
        return this.a;
    }
}
/*main方法*/
public class Test2 {
    public static void main(String[] args){     
        Pair<? extends Number> p = new Pair<Integer>(0);
        Pair<Double> d = new Pair<>(1.5);
        p.setA(d);//编译错误
        System.out.println(p.getA());
    }
}

     下面我们一起看看java中为什么不允许这种机制的存在,我们不妨现假设java中不存在这种机制,也就是可以对通配符类型进行修改设置其值,语句:Pair<? extends Number> p = new Pair<Integer>(0);,p中装的是Pair<? extends Number>的子类型,d实际上也是Pair<? extends Number>的子类型,所以可以调用set函数,将Double数值赋值给实际类型Integer,必然出错,所以,java中为了避免这种问题,直接不允许更改通配符修饰的类型。 三、超类通配符      超类通配符从某种角度上来说,和上述的普通的通配符作用上是相反的。其主要的形式为:<? super E>,是否和上述的<? extends E>形式上相似?接下来我们一起看看这种通配符。一段代码:

Class Base implements Comparable<Base>{
    private int x;
    public Base(int x){
        this.x = x;
    }
    //实现compareTo方法
    public int compareTo(Base b){
        .......
    }
}

Class Child extends Base{
    public Child(int x){
        super(x);
    }
}

public static void main(String[] args){
    //定义一个容器类
    DynamicArray<Child> childs = new DynamicArray<Child>();
    //添加元素到容器中
    childs.add(new child(10));
    childs.add(new child(20));
    Child maxA = max(childs);
}

     此段程序编译时出错,原因在于,max方法推断传入的参数类型为Child类,调用comparaeTo方法时,发现类型不匹配,拒绝执行,父类Base实现了接口Comparable<Base>,而max方法需要Comparable<Child>方法,由于子类child并没有重新实现此接口而虽然Child继承与Base,但是Comparable<Base>与Comparable<Child>却毫无关系。      解决办法,使用我们的通配符

/*将max方法修改如下*/
public static <T extends Comarable<? super T>> T max(DynamicArray<T> a){
    //这样child和Base都可以被匹配到
} 

     超类通配符主要用于灵活的写入和比较而<? extends E>主要用于读写,不能写入或者修改。 四、泛型的实现的细节      首先我们需要讨论一下由类型擦除导致的一些冲突,我们来看一个代码块:

public class Base implements Comparable<Base>{
    
}
public class Child extends Base implements Comparable<Child>{

}
/*这是不允许的*/

     虽然在编译器看来他们分别继承的是不同的接口类型,但是由于类型擦除导致父类和子类继承了同一个接口,也就是说子类两次继承了同一个接口类型,这在java中是不允许的。      最后我们看看泛型中的数组问题,如果我告诉你泛型是不能创建数组的,你能想到原因么?下面我们就从为什么泛型中不能创建数组来看看泛型的一些细节问题。      首先我们先假设泛型是可以创建数组的。

Integer[] ins = new Integer[10];
Number[] nums = ins;
Object[] obj = ins;
/*在一般数组中这是允许的,因为编译器会为我们进行检查*/
Pair<Integer>[] p = new Pair<Integer>[10];
Object[] obj = p;
obj[0] = "dfa";
/*编译器不会报错,但是在jvm运行时肯定抛异常*/

     由于以上所示原因,java泛型是不允许定义数组的。

     最后,由于本篇文章是博主自学时查阅书籍和博客学习的笔记和总结,如有错误,还望大家指出!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏python学习指南

Elasticsearch--Date math在索引中的使用

在Elasticsearch,有时要通过索引日期来筛选某段时间的数据,这时就要用到ES提供的日期数学表达式   描述:   特别在日志数据中,只是查询一段...

1629
来自专栏性能与架构

mysql B+树索引

上图就是一棵B+树,每个部分有3个主要概念:物理磁盘块、数据项(蓝色)、指针(红色) 如磁盘块1,包含数据项 17、35,包含指针 P1、P2、P3,P1指向...

3217
来自专栏架构之路

索引优先队列-IndexedPrirotyQueue的原理及实现(源码)

1.索引优先队列的意义 索引优先队列是一个比较抽象的概念,它是一个优先队列,又带有索引,这个索引是用来干什么的呢? 在正常的队列中,我们只能访问队列头元素,整个...

3418
来自专栏云霄雨霁

Mybatis映射文件笔记----参数处理

954
来自专栏青玉伏案

窥探Swift之别具一格的Struct和Class

  说到结构体和类,还是那句话,只要是接触过编程的小伙伴们对这两者并不陌生。但在Swift中的Struct和Class也有着令人眼前一亮的特性。Struct的功...

1887
来自专栏牛肉圆粉不加葱

[6] - 类和对象之进阶(二)

Scala 中的可见性非常灵活且复杂,这篇文章希望通过大量的示例来说清楚各种情况下的可见性是怎么样的。

632
来自专栏tkokof 的技术,小趣及杂念

再谈谈列表元素的删除

之前(以及更早之前)都提到了列表元素的删除,也提到过几种方法,有兴趣的朋友可以去看看,其中一种个人比较倾向的写法大概是这个样子(C++):

551
来自专栏SeanCheney的专栏

Python基础回顾基本数据类型和运算容器分支和循环函数、生成器和类map, reduce和filter列表生成(list comprehension)字符串文件操作和pickle异常多进程(mult

Python shell输入import this 可以看到The Zen of Python 基本数据类型和运算 基本数据类型 Python中最基本的数据类...

3777
来自专栏每日一篇技术文章

Swift3.0 - 扩展

b.如果想要在定义协议的时候,不指定变量名称,在实现协议的时候,再去设定变量类型,应该怎么写?

533
来自专栏鬼谷君

python datetime模块参数详解

1062

扫码关注云+社区