Java核心技术——泛型高级进阶

之前对泛型这一块儿的内容学习有点疏漏,现在抽空补一下。

原文参考:https://blog.csdn.net/harvic880925/article/details/49883589

类型绑定

1.定义

有时候,你会希望泛型类型只能是某一部分类型,比如操作数据的时候,你会希望是Number或其子类类型。这个想法其实就是给泛型参数添加一个界限。其定义形式为:

此定义表示 T 是 BoundingType 的子类型。 T 和 BoundingType 可以是类 也可以是接口,需要注意的是此处的 extends 表示的子类型,不同于继承

2.实例:绑定接口

类型绑定有两个作用:

  • 对填充的类型加以限定
  • 使用泛型变量 T 时,可以使用 BoundingType 内部的函数。

我们用泛型来实现根据传进去的T类型数组a,找到最小值的功能。

package com.gildata;

/**
 * Created by liaock on 2019/1/15
 **/
public interface Comparable<T> {
    public boolean compareTo(T o);
}


//添加上extends Comparable之后,就可以Comparable里的函数了
 public static <T extends Comparable> T min(T...a){
        T smallest = a[0];
        for(T item:a){
            if(smallest.compareTo(item)){
                smallest = item;
            }
        }
        return smallest;
    }

然后我们实现一个派生自Comparable接口的类:

package com.gildata;/**
 * Created by liaock on 2019/1/16
 **/

/**
 *
 **/
public class StringCompare implements Comparable<StringCompare>{
    private String mStr;

    public StringCompare(String string){
        this.mStr = string;
    }

    public String getmStr() {
        return mStr;
    }

    public void setmStr(String mStr) {
        this.mStr = mStr;
    }

    public boolean compareTo(StringCompare str) {
        if(mStr.length() > str.mStr.length()){
            return true;
        }
        return false;
    }
}

测试:

package com.gildata;

import java.util.Arrays;

/**
 * Created by liaock on 2019/1/15
 **/
public class Main {


    //添加上extends Comparable之后,就可以Comparable里的函数了
    public static <T extends Comparable> T min(T...a){
        T smallest = a[0];
        for(T item:a){
            if(smallest.compareTo(item)){
                smallest = item;
            }
        }
        return smallest;
    }

    public static void main(String []args){
        Point<Integer> p1 = new Point<Integer>();
        p1.setX(new Integer(100));
        System.out.println(p1.getX());
        // 類型綁定
        StringCompare result = min(new StringCompare("123"),new StringCompare("234"),new StringCompare("59897"));
        System.out.println(result.getmStr());

        //無邊界通配符: ?
        Point<?> point = new Point<Integer>(3,3);
        Point<?> point2 = new Point<Float>(4.3f,4.3f);
    }
}

二、通配符

通配符是一个比较难以理解的问题

1.引入

我们来看这样一个例子:

package com.gildata;
/**
 * Created by liaock on 2019/1/15
 **/

class Point <T> {
    private T x;  //表示X坐标
    private T y;  //表示Y坐标

    public Point(){}

    public Point(T x, T y){
        this.x = x;
        this.y = y;
    }


    public T getX() {
        return x;
    }

    public void setX(T x) {
        this.x = x;
    }

    public T getY() {
        return y;
    }

    public void setY(T y) {
        this.y = y;
    }
}

这段代码很简单,引入一个泛型变量 T,然后两个构造函数,set、get方法。
我们看看下面的这段代码:

Point<Integer> integerPoint = new Point<Integer>(3,3);
…………
Point<Float> floatPoint = new Point<Float>(4.3f,4.3f);
…………
Point<Double> doublePoint = new Point<Double>(4.3d,4.90d);
…………
Point<Long> longPoint = new Point<Long>(12l,23l);
    

在这段代码中,我们使用Point生成了四个实例:integerPoint,floatPoint,doublePoint和longPoint;
在这里,我们生成四个实例,就得想四个名字。如果我们想生成十个不同类型的实例呢?那不得想十个名字。
光想名字就是个事,(其实我并不觉得想名字是个什么大事…… T _ T ,没办法,想不出更好的例子了…… )
那有没有一种办法,生成一个变量,可以将不同类型的实例赋值给他呢?

2、无边界通配符

<1>.概述

我们这样来实现上面的例子:

Point<?> point;
 
point = new Point<Integer>(3,3);
point = new Point<Float>(4.3f,4.3f);
point = new Point<Double>(4.3d,4.90d);
point = new Point<Long>(12l,23l);

Point<?> 这里的?就是无边界通配符。通配符的意义就是它是一个未知的符号,可以是代表任意的类。

<2>. ? 与 T 的区别

? 与 T 没有任何联系 !
泛型变量 T 不能在代码用于创建变量,只能在类、函数、接口声明以后,才能使用。

而无边界通配符?则只能用于填充泛型变量T,表示通配任何类型!

三、通配符 ? 的extends绑定

1.概述

从上面我们可以知道通配符?可以代表任意类型,但跟泛型一样,如果不加以限定,在后期的使用中编译器可能不会报错。所以我们同样,要对?加以限定。绑定的形式,同样是 extends 关键字,意义和使用方法都和泛型变量一致。

  Point<? extends Number> point3;
        point3 = new Point<Number>();
        point3 = new Point<Float>(4.3f,4.3f);
        point3 = new Point<String>("","");

例如上面的代码,最后一行代码编译就会报错。
无边界通配符只是泛型T的填充方式,给他加上限定,只是限定了赋值给它(比如这里的point)的实例类型。
如果想从根本上解决乱填充Point的问题,需要从Point泛型类定义时加上:

class Point<T extends Number> {
    private T x;       // 表示X坐标
    private T y;       // 表示Y坐标
 
   …………
}
2.注意:利用 <? extends Number> 定义的变量,只可取其中的值,不可以修改

看下面的代码

  point3.getX();
  point3.setX(3.3f);

发现 明显在point3.setX(3.3f);;时报编译错误。但point3.getX()却不报错。

这是为什么呢?

首先,point的类型是由Point<? extends Number>决定的,并不会因为point = new Point(3,3);而改变类型。
即便point = new Point(3,3);之后,point的类型依然是Point<? extends Number>,即派生自Number类的未知类型!!!这一点很好理解,如果在point = new Point(3,3);之后,point就变成了Point类型,那后面point = new Point(12l,23l);操作时,肯定会因为类型不匹配而报编译错误了,正因为,point的类型始终是Point<? extends Number>,因此能继续被各种类型实例赋值。
回到正题,现在说说为什么不能赋值
正因为point的类型为 Point<? extends Number> point,那也就是说,填充Point的泛型变量T的为<? extends Number>,这是一个什么类型?未知类型!!!怎么可能能用一个未知类型来设置内部值!这完全是不合理的。
但取值时,正由于泛型变量T被填充为<? extends Number>,所以编译器能确定的是T肯定是Number的子类,编译器就会用Number来填充T

也就是说,编译器,只要能确定通配符类型,就会允许,如果无法确定通配符的类型,就会报错。

四、通配符? 的super绑定

1.概述

如果说 <? extends XXX>指填充为派生于XXX的任意子类的话,那么<? super XXX>则表示填充为任意XXX的父类!

五、通配符? 总结

总结 ? extends 和 the ? super 通配符的特征,我们可以得出以下结论:

  • 如果你想从一个数据类型里获取数据,使用 ? extends 通配符(能取不能存)
  • 如果你想把对象写入一个数据结构里,使用 ? super 通配符(能存不能取)
  • 如果你既想存,又想取,那就别用通配符。

最后重复一遍:构造泛型实例时,如果省略了填充类型,则默认填充为无边界通配符!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值