本文接上一篇 Java中的泛型generics从0到1(上)




java 返回值类上的泛型 java泛型方法返回值_类型参数


泛型的方法

泛型方法就是引入了类型参数的方法,这和定义泛型类一样。但类型参数只能用于所声明的方法中。

泛型方法可以是静态的,也可以是非静态的,也可以是类的构造函数。

泛型方法的语法是,在返回值前面,出现类型参数列表。对于静态方法,类型参数必须出现在返回值的类型前。

下面的实例代码 类Util包含一个泛型方法compare,代码如下

public class Util {    public static  boolean compare(Pair p1, Pair p2) {        return p1.getKey().equals(p2.getKey()) &&               p1.getValue().equals(p2.getValue());    }}public class Pair {    private K key;    private V value;    public Pair(K key, V value) {        this.key = key;        this.value = value;    }    public void setKey(K key) { this.key = key; }    public void setValue(V value) { this.value = value; }    public K getKey()   { return key; }    public V getValue() { return value; }}

调用这个compare方法的完整语法如下

Pair p1 = new Pair<>(1, "apple");Pair p2 = new Pair<>(2, "pear");boolean same = Util.compare(p1, p2);

在上面的例子中,显示的提供了类型,但一般来说,也可以不指定类型,由编译器来自动推断。

Pair p1 = new Pair<>(1, "apple");Pair p2 = new Pair<>(2, "pear");boolean same = Util.compare(p1, p2);

这个特性叫类型推断 ,将在后面介绍到。它让你在调用泛型方法时就像调用普通方法一样。

上面介绍的泛型是任意类型都可以,下面介绍的有界泛型就可以把类型限制在一个小范围内,这样就可以利用到该类型的方法。

有界的类型参数

有时候,你想限制一下实际传入的类型参数,这时有界的类型参数就派上用场了。

比如有个类,只想处理Number和它的子类,这时就需要限制一下传入的类型。

申明一个有界的类型参数的语法是,先列出类型参数,后面跟上关键字 extends,然后再跟上它的上界。下面这个例子中的上界是Number 。注意这里的extends其实有两个含义, 既可以是继承自某个类,也可以是实现了某个接口。

public class Box {    private T t;              public void set(T t) {        this.t = t;    }    public T get() {        return t;    }    public  void inspect(U u){        System.out.println("T: " + t.getClass().getName());        System.out.println("U: " + u.getClass().getName());    }    public static void main(String[] args) {        Box integerBox = new Box();        integerBox.set(new Integer(10));        integerBox.inspect("some text"); // error: this is still String!    }}

由于在泛型方法的类型参数上增加了上界,main方法中的第三行这时编译就出错了。因为inspect的参数类型需要Number及其子类,String并不是。

有界参数不仅可以用于泛型方法,还可以将其用在泛型类上,这样你就可以调用该上界的方法了。

因此有界类型有两个作用

1,可以限制初始化时的实际类型

2,在定义了有界的类型的类中,可以使用该有界类型的类的方法。

比如下面的例子,由于T是Integer的子类,因此在isEven方法中,n可以调用Number的方法intValue,

这在实现泛型的算法上很关键。

public class NaturalNumber {    private T n;    public NaturalNumber(T n)  { this.n = n; }    public boolean isEven() {        return n.intValue() % 2 == 0;    }    public static void main(String[] args) {        NaturalNumber naturalNumber = new NaturalNumber<>(4);        boolean even = naturalNumber.isEven();        System.out.println(even);    }}

类型参数可以有多个界限

上面的类型展示了类型参数有一个界限,实际上,类型参数可以有多个界限,语法如下

它的含义是T类型是界限类型列表中全部类的子类。换句话说,T类实现了这些接口。

而且如果在这些上界中有一个是class,必须放在第一位置,把接口放在后面,不然编译会报错。

由于Java单继承只有一个父类,所以只有第一个类型可能是class,后面的都是接口类型。表示T实现了这些接口。

Class A { /* ... */ }interface B { /* ... */ }interface C { /* ... */ }class D  { /* ... */ }    // 合法,class在第一个位置class D  { /* ... */ }    // compile-time error

泛型方法中的有界的类型参数

有界的类型参数是实现泛型算法的关键。

比如下面的代码是一个算法,用于计算一个数组中,比给定的元素elem大的个数,实现的方法很容易理解,但却无法编译,因为大于号>只能对原始类型进行比较。不能用于比较对象。 为了解决这个问题,需要用到有界限的类型参数。

public static  int countGreaterThan(T[] anArray, T elem) {    int count = 0;    for (T e : anArray)        if (e > elem)  // compiler error 无法编译            ++count;    return count;}

将上面的方法改成有界限的类型参数,指明了这些对象都需要实现这个比较接口Comparable,新代码如下

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

这样这个算法就可以编译了。

在集合框架中有许多要用到的比较的方法,其元素都要求实现这个Comparable接口。如Map中的方法

public static , V> Comparator> comparingByKey() {            return (Comparator> & Serializable)                (c1, c2) -> c1.getKey().compareTo(c2.getKey());        }

泛型有关的继承和子类型

在Java中,在类型合理的情况下,可以把一种类型的对象赋给另一种类型。如下面的代码,可以把Integer的对象赋给Object类型的变量,因为Object是Integer的超类型supertype

Object someObject = new Object();Integer someInteger = new Integer(10);someObject = someInteger;   // OK

在面向对象的术语中,这是一个“is a“关系。Integer是一种Object,所以这种赋值合法。另外Integer也是一种 Number,所以下面的代码也合法。

public void someMethod(Number n) { /* ... */ }someMethod(new Integer(10));   // OKsomeMethod(new Double(10.1));   // OK

这种逻辑在泛型中也是OK的,下面在实例化Box时传入的类型是Number,调用box的方法时,传入Number的子类型都是可以的。

Box box = new Box();box.add(new Integer(10));   // OKbox.add(new Double(10.1));  // OK

下面考虑这个方法

public void boxTest(Box n) { /* ... */ }

这个方法,可以传什么实参进来,可以是Box或Box吗?答案是No。

为什么呢?

虽然Integer是Number的子类,但是Box并不是Box的子类。

在泛型中这非常容易搞混,所以需要重点的学一下。

Box和Box的共同父对象是Object


java 返回值类上的泛型 java泛型方法返回值_泛型_02

尽管Integer是Number的子类型,Box不是Box的子类型


如果两个泛型类的类型参数是相关的,那这两个泛型类有类似子类型的关系吗?有,等到后面了解到通配符的知识了,就可以了解这部分内容了。

泛型类的子类型

创造一个泛型类的子类型可以通过继承或实现接口的方式 。

这里用Collections中的类来举例,下面是ArrayList,List的定义。

public class ArrayList extends AbstractList implements List {}public interface List extends Collection {}

可以看出ArrayList实现了List,List又继承了Collection。

所以 ArrayList是List的子类型,

List是Collection的子类型。

只要他们的实参类型是相同的,他们之间就有子类型的关系。


java 返回值类上的泛型 java泛型方法返回值_java compare 返回值_03


现在假设你自定义了一个List叫 PayloadList,关联了一个可选的泛型类型P到每个元素上,这个接口的声明如下

interface PayloadList extends List {  void setPayload(int index, P val);  ...}

下面这几个参数化的类型都是List的子类型

  • PayloadList
  • PayloadList
  • PayloadList


java 返回值类上的泛型 java泛型方法返回值_java compare 返回值_04


本文就介绍到这里,下一篇的内容包括 泛型通配符,有上界的通配符,类型擦除等。

本文为《Java中的泛型系列》第二篇,其他的还有

Java中的泛型generics从0到1(上)

Java中的泛型generics从0到1(中)

Java中的泛型generics从0到1(下)

Java中的泛型generics从0到1(甲)

Java中的泛型generics从0到1(乙)

Java中的泛型generics从0到1(丙)

Java中的泛型generics从0到1(丁)