泛型(一)->简单使用

泛型(一)->简单使用

从上周末到现在陆陆续续看了几天终于把<< java核心技术 >>泛型看完了,有种豁然开朗的感觉尤其是对于泛型擦除又有了新的认识,趁脑还热赶紧记录下来.
关于泛型我准备分两篇写,第一篇是关于泛型的使用属于基础(必须掌握),第二篇是泛型擦除等等一些问题属于进阶(可选).大家自行选择.

首先我们要知道泛型的英文是Generic,我曾经被中文版的安卓文档坑过他给翻译成一般结果始终不理解.

泛型类的定义

泛型类就是具有一个或者多个类型变量的类.

public class Pair<T> {

    private T first;
    private T second;

    public Pair() {
        first = null;
        second = null;
    }

    public Pair(T first, T second) {
        this.first = first;
        this.second = second;
    }

    public T getFirst() {
        return first;
    }

    public void setFirst(T first) {
        this.first = first;
    }

    public T getSecond() {
        return second;
    }

    public void setSecond(T second) {
        this.second = second;
    }
}

注意:本篇博客栗子都会用到这个类,后面使用的Pair就是指的这个类.

Pair引入了类型变量T,用<>括起来,放在类名后.泛型类可以有多个类型变量例如
public class Pair<T,U>{...}

类中指定的类型变量可以用来指定方法的返回值类型,成员变量,局部变量的类型.例如
public T getFirst() {...}

用具体的类型替换类型变量就可以实例化泛型类,例如
Pair<String> pair = new Pair<>();

这里举个实际的栗子,用静态的minmax方法遍历数组并计算最大最小值,用一个Pair对象返回两个结果

public class ArrayAlg {
    public static Pair<String> minmax(String[] a) {
        if (a == null || a.length == 0)
            return null;

        String min = a[0];
        String max = a[0];
        for (int i = 1; i < a.length; i++) {
            if (min.compareTo(a[i]) > 0)
                min = a[i];
            if (max.compareTo(a[i]) < 0)
                max = a[i];
        }
        return new Pair<String>(min, max);
    }
}

public static void main(String[] args){
    String[] words = {"crash","have","bad","a"};
    Pair<String> pair = ArrayAlg.minmax(words);
    System.out.println("min:"+pair.getFirst());
    System.out.println("max:"+pair.getSecond());
}

泛型方法

前面已经介绍了泛型类. 其实还可以定义一个带有类型参数的简单方法

public class ArrayAlg {

    public static <T> T getMiddle(T... a) {
        return a[a.length / 2];
    }

}

这是一个泛型方法,从<>括号可以看出这一点,需要注意的是泛型方法类型变量放在修饰符后面(这里是public static)返回值的前面.泛型方法可以定义在普通类,也可以定义在泛型类中.

调用一个泛型方法也非常简单
String middle = ArrayAlg.getMiddle("zly","ppjun","public");
编译器能够根据传入的String[]判断出T一定是String.

类型变量的限定

有的时候我们需要对类型变量加以限定,比如我们想利用Compareble接口的compareTo方法去比较大小.那么我们就需要传入的参数实现了Compareble接口.这个时候就可以通过类型变量T设置限定符实现

public static <T extends Compareble> T min(T[] a)

现在,泛型的min方法就只能被实现了Compareble接口的类的数组调用

一个类型变量或者通配符可以有多个限定,例如
T extends Compareble & Serializeble
限定符用&分隔,类型变量用,分隔

由于java是单继承多实现的,所以限定中可以有多个接口,但是最多只有一个类,并且如果用一个类做限定那么他必须放限定列表中第一个.

需要注意的是类型变量的限定只支持< T extends X >形式,不支持< T super X >形式不要跟后面介绍的通配符”?”混淆

泛型类型的继承规则

在使用泛型的时候我们需要了解一些关于子类和继承的规定,先举一个例子Employee和Manager是继承关系,而Pair< Employee >是Pair< Manager >父类么?答案是”不是”.相信这和大多数人的想法不太一样.

比如下面这个情况我们写代码时候应该都遇到过

public static void main(String[] args){
    List<Manager> list = new ArrayList<>();
    test(list);
}


public static void test(List<Employee> list){

}

test方法没法调用,简单的说无论T和S有什么关系Pair< T >与Pair< S >没有任何联系

泛型类可以扩展或者实现其他泛型类,这跟普通类没有什么区别,比如ArrayList< T >实现List< T >,一个ArrayList< Manager >可以被转换为List< Manager >,但是一个ArrayList< Manager >跟ArrayList< Employee >没有关系

通配符类型

通配符的子类限定

Pair< ? extends Employee >
表示类型参数是Employee子类的Pair

像前面说的给test方法无法传入List< Manager >的问题,解决方法很简单:使用通配符
public static void test(List<? extends Employee> list)
List< Manager >是List< ? extends Employee >的子类

这里有个问题需要注意当使用了List< ? extends Employee >后,list.add()方法将不能使用而list.get()是可以的,要了解原因看传入< ? extends Employee >后的add和get方法.

boolean add(? extends Employee e)
? extends Employee get(int index)

编译器只知道add方法参数是Employee的子类,但并不知道具体是什么类型.add方法拒绝传入任何特定的类型因为?不能跟他匹配.
而get方法就不存在问题,我们将返回值赋给Employee完全合法.

通配符的超类限定

除了与类型变量十分相似extends限定,通配符还有一个附加能力,即指定一个超类限定符,如下
? super Manager
这个通配符限制为Manager或者所有父类.可以为方法提供参数,但不能使用返回值.例如List< ? super Manager >

boolean add(? super Manager e)
? super Manager get(int index)

编译器不知道add方法确切类型,但是可以用Manager(或其子类)调用它,然而get方法因为返回值不确定就只能用Object去接收他

总结下,带有超类型限定(super)的通配符可以向泛型对象写入,带有子类型限定(extends)的通配符可以从泛型对象读取

无限定通配符

还可以使用无限定的通配符,例如Pair< ? >,并且会有如下方法

? getFirst()
void setFirst(?)

getFirst方法返回值只能赋给object,而setFirst方法将不能被调用即使传入Object也不行.那么问题来了这个有什么卵用.

看完下面这个栗子就知道啦

 public static boolean isNull(Pair<?> pair){
    return pair.getFirst() != null || pair.getSecond() != null;
 }

没错就是这种判断是否为空的操作用?非常好使,因为他不需要知道实际的类型.

总结

  1. 泛型类定义

    • public class Pair<T> {...}
  2. 泛型方法定义

    • public static <T> T getMiddle(T... a) {...}
  3. 泛型变量限定

    • <T extends X>代表T只能为X或者X的子类,不支持<T super X>

    • 一个类型变量或者通配符可以有多个限定,T extends Compareble & Serializeble,但是最多只有一个类,并且如果用一个类做限定那么他必须放限定列表中第一个.

  4. 泛型类的继承规则

    • 无论T和S有什么关系Pair< T >与Pair< S >没有任何联系
  5. 通配符的子类限定

    • Pair<? extends Employee> 表示类型参数是Employee子类的Pair

    • 使用了List< ? extends Employee >后,list.add()方法将不能使用而list.get()是可以的.

  6. 通配符的超类限定

    • Pair<? super Manager> 这个通配符限制为Manager或者所有父类,可以为方法提供参数,但不能使用返回值.

预告

本篇到此结束,下一篇泛型(2)将讲解泛型擦除和擦除所带来的形形色色的问题敬请期待.

猜你喜欢

转载自blog.csdn.net/zly921112/article/details/62889431