夯实JAVA基本之一——泛型详解(2)

页面导航:首页 > 软件编程 > Java编程 > 夯实JAVA基本之一——泛型详解(2)

夯实JAVA基本之一——泛型详解(2)

来源: 作者: 时间:2016-01-21 09:39 【

前言:被温水煮惯了,梦想的东西总是不敢于尝试,失败了又怎样,最多从头来过。相关文章:1、《夯实JAVA基本之一 泛型详解(1)》2、《夯实JAVA基本之一 泛型详解(2)》上一篇给大家初

前言:被温水煮惯了,梦想的东西总是不敢于尝试,失败了又怎样,最多从头来过。
 

 

相关文章:

1、《夯实JAVA基本之一 —— 泛型详解(1)》
2、《夯实JAVA基本之一 —— 泛型详解(2)》


上一篇给大家初步讲解了泛型变量的各种应用环境,这篇将更深入的讲解一下有关类型绑定,通配符方面的知识。

 

一、类型绑定

1、引入

我们重新看上篇写的一个泛型:

 

 

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

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

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

    public T getX() {
        return this.x;
    }

    public T getY() {
        return this.y;
    }
}

//使用
Point p1 = new Point();
p1.setX(new Integer(100));
System.out.println(p1.getX());
首先,我们要知道一点,任何的泛型变量(比如这里的T)都是派生自Object,所以我们在填充泛型变量时,只能使用派生自Object的类,比如String,Integer,Double,等而不能使用原始的变量类型,比如int,double,float等。
然后,问题来了,那在泛型类Point内部,利用泛型定义的变量T x能调用哪些函数呢?
private T x;  
当然只能调用Object所具有的函数,因为编译器根本不知道T具体是什么类型,只有在运行时,用户给什么类型,他才知道是什么类型。编译器唯一能确定的是,无论什么类型,都是派生自Object的,所以T肯定是Object的子类,所以T是可以调用Object的方法的。
那么问题又来了,如果我想写一个找到最小值的泛型类;由于不知道用户会传什么类型,所以要写一个接口,让用户实现这个接口来自已对比他所传递的类型的大小。
接口如下:
public interface Comparable{
    public boolean compareTo(T i);
}
但如果我们直接利用T的实例来调用compareTo()函数的话,会报错,编译器截图如下:
/

 

这是因为,编译器根本无法得知T是继承自Comparable接口的函数。那怎么样才能让编译器知道,T是继承了Comparable接口的类型呢?
这就是类型绑定的作用了。

2、类型绑定:extends

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

 

 

此定义表示T应该是BoundingType的子类型(subtype)。T和BoundingType可以是类,也可以是接口。另外注意的是,此处的”extends“表示的子类型,不等同于继承。
一定要非常注意的是,这里的extends不是类继承里的那个extends!两个根本没有任何关联。在这里extends后的BoundingType可以是类,也可以是接口,意思是说,T是在BoundingType基础上创建的,具有BoundingType的功能。目测是JAVA的开发人员不想再引入一个关键字,所以用已有的extends来代替而已。
(2)、实例:绑定接口
同样,我们还使用上面对比大小的接口来做例子
首先,看加上extends限定后的min函数:
public interface Comparable {
    public boolean compareTo(T i);
}
//添加上extends Comparable之后,就可以Comparable里的函数了
public static   T min(T...a){
    T smallest = a[0];
    for(T item:a){
        if (smallest.compareTo(item)){
            smallest = item;
        }
    }
    return smallest;
}
这段代码的意思就是根据传进去的T类型数组a,然后调用其中item的compareTo()函数,跟每一项做对比,最终找到最小值。
从这段代码也可以看出,类型绑定有两个作用:1、对填充的泛型加以限定 2、使用泛型变量T时,可以使用BoundingType内部的函数。
这里有一点非常要注意的是,在这句中smallest.compareTo(item),smallest和item全部都是T类型的,也就是说,compareTo对比的是同一种类型。
然后我们实现一个派生自Comparable接口的类:
public class StringCompare implements Comparable {
    private String mStr;

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

    @Override
    public  boolean compareTo(StringCompare str) {
        if (mStr.length() > str.mStr.length()){
            return true;
        }
        return false;
    }
}
在这段代码,大家可能会疑惑为什么把T也填充为StringCompare类型,记得我们上面说的吗:smallest.compareTo(item),smallest和item是同一类型!!所以compareTo的参数必须是与调用者自身是同一类型,所以要把T填充为StringCompare;
在这段代码中compareTo的实现为,对比当前mstr的长度与传进来实例的mstr长度进行比较,如果超过,则返回true,否则返回false;
最后是使用min函数:
StringCompare result = min(new  StringCompare(123),new StringCompare(234),new StringCompare(59897));
Log.d(TAG,min:+result.mStr);
结果如下:
/

 

这里有extends接口,我们开篇说过,extends表示绑定,后面的BindingType即可以是接口,也可以是类,下面我们就再举个绑定类的例子。

在文章底部给出

 

(3)、实例:绑定类
我们假设,我们有很多种类的水果,需要写一个函数,打印出填充进去水果的名字:
为此,我们先建一个基类来设置和提取名字:

 

class Fruit {
    private String name;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
然后写个泛型函数来提取名字:
public static  String getFruitName(T t){
    return t.getName();
}
这里泛型函数的用法就出来了,由于我们已知水果都会继承Fruit基类,所以我们利用就可以限定填充的变量必须派生自Fruit的子类。一来,在T中,我们就可以利用Fruit类中方法和函数;二来,如果用户填充进去的类没有派生自Fruit,那编译器就会报错。
然后,我们新建两个类,派生自Fruit,并填充进去它们自己的名字:
class Banana extends Fruit{
    public Banana(){
        setName(bababa);
    }
}
class Apple extends Fruit{
    public Apple(){
        setName(apple);
    }
}
最后调用:
String name_1 = getFruitName(new Banana());
String name_2 = getFruitName(new Apple());
Log.d(TAG,name_1);
Log.d(TAG,name_2);
结果如下:
/

 

 

源码在文章底部给出(4)、绑定多个限定
上面我们讲了,有关绑定限定的用法,其实我们可以同时绑定多个绑定,用&连接,比如:

 

 

public static  String getFruitName(T t){
    return t.getName();
}
再加深下难度,如果我们有多个泛型,每个泛型都带绑定,那应该是什么样子的呢:
public static  T foo(T a, U b){
	…………
}
大家应该看得懂,稍微讲一下:这里有两个泛型变量T和U,将T与Comparable & Serializable绑定,将U与Runnable绑定。
好了,这部分就讲完了,下面讲讲有关通配符的用法。

二、通配符

通配符是一个非常令人头疼的一个功能,理解与掌握难度比较大,下面我尽力去讲明白它与泛型变量的区别与用法。

1、引入

重新来看我们上篇用的Point泛型定义:
class Point {
    private T x;      
    private T y;      
    
    public Point(){
        
    }
    public Point(T x,T y){
        this.x = x;
        this.y = y;
    }

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

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

    public T getX() {
        return this.x;
    }

    public T getY() {
        return this.y;
    }
}
这段代码很简单,引入了一个泛型变量T,然后是有两个构造函数,最后分别是利用set和get方法来设置和获取x,y的值。这段代码没什么难度,不再细讲。
我们看看下面这段使用的代码:
Point integerPoint = new Point(3,3);
…………
Point floatPoint = new Point(4.3f,4.3f);
…………
Point doublePoint = new Point(4.3d,4.90d);
…………
Point longPoint = new Point(12l,23l);
…………
在这段代码中,我们使用Point生成了四个实例:integerPoint,floatPoint,doublePoint和longPoint;
在这里,我们生成四个实例,就得想四个名字。如果我们想生成十个不同类型的实例呢?那不得想十个名字。
光想名字就是个事,(其实我并不觉得想名字是个什么大事…… T _ T ,没办法,想不出更好的例子了…… )
那有没有一种办法,生成一个变量,可以将不同类型的实例赋值给他呢?

2、无边界通配符:?

(1)、概述
先不讲无边界通配符是什么,同样拿上面的例子来看,如果我们这样实现:
Point  point;

point = new Point(3,3);
point = new Point(4.3f,4.3f);
point = new Point(4.3d,4.90d);
point = new Point(12l,23l);
在这里,我们首先,利用下面的代码生成一个point实例,注意到,在填充泛型时,用的是?
Point  point;
然后,各种类型的Point实例,都可以赋值给point了:
point = new Point(3,3);
point = new Point(4.3f,4.3f);
point = new Point(4.3d,4.90d);
point = new Point(12l,23l);
这里的?就是无边界通配符。通配符的意义就是它是一个未知的符号,可以是代表任意的类。
所以这里可能大家就明白了,这里不光能将泛型变量T填充为数值类型,其实任意Point实例都是可以传给point的:比如这里的Point(),Point
Tags:

文章评论

最 近 更 新
热 点 排 行
Js与CSS工具
代码转换工具

<