Skip to content

3. 泛型类和方法的签名

  

RxJava 库包含许多将泛型接口实例作为参数的方法。有时,这些方法的签名较为复杂。以下是一些示例:


public final <R> Observable<R> map(Func1<? super T,? extends R> func)

public final <R> Observable<R> flatMap(Func1<? super T,? extends Observable<? extends R>> func)

这两个方法使用了两个泛型类型 T 和 R。但例如 map 方法的参数定义:Func1<? super T,? extends R> func 是什么意思呢?

有关泛型的信息可查阅 URL [https://docs.oracle.com/javase/tutorial/java/generics/]。下文中的部分信息源自 URL。泛型自 Java 1.5 版本起引入。

假设有一个提供不同类型信息的Web服务。该Web服务有时可能无法成功提供信息,此时必须向客户端报告错误。因此,我们可以将Web服务的响应标准化为以下形式:


public class Response<T> {

    // ----------------- 属性
    // 操作状态
    private int status;
    // 可能出现的错误信息
    private List<String> messages;
    // 响应正文
    private T body;
...
}
  • 第 1 行:类 [Response] 由类型 T 进行参数化;
  • 第 9 行:类型 T 是响应正文的类型,即客户端实际期望接收的内容;
  • 第 5 行:错误代码,若无错误则为 0;
  • 第 7 行:解释错误的消息,若无错误则为 null

在客户端,我们可以这样编写:


Response<Product> product=getDataFromWebService(...) ;


Response<List<Product>>=getDataFromWebService(...) ;

形式类型 T 被替换为实际类型,此处为 [Product] 或 [List<Product>]。泛型类 [Response<T>] 在此处非常实用,因为它允许处理标准响应。

若要使用 new 运算符创建类型 [Response],应编写如下代码:


Response<List<Product>> response=new Response<List<Product>> (...) ;

自 Java 1.8 版本起,上述语句可以更简洁地写为:


Response<List<Product>> response=new Response<> (...) ;

现在,无需在等号右侧明确指定泛型参数的实际类型:系统将自动采用等号左侧指定的形式类型。 这被称为类型推断:编译器能够根据上下文自行推断泛型参数的实际类型。这一特性在lambda表达式中被广泛使用,因为在方法中,泛型参数的实际类型通常会被省略。

现在在客户端,方法 [getDataFromWebService] 的签名可能如下所示:


<T1,T2> Response<T1>  getDataFromWebService(String urlWebService, String httpMethod, T2 post)

这是一个由两个类型 T1 和 T2 参数化的泛型方法:

  • T1 是预期响应正文的类型;
  • T2 是针对 POST 操作提交的值的类型,null 是针对 GET 操作的类型;
  • urlWebService:是 Web 服务的 URL;
  • httpMethod:是用于查询该 URL 的方法,可使用 HTTP、GET 或 POST;

在客户端,可以调用以下方法:


Long id=... ;
Response<Product> product=this.<Product,Long>getDataFromWebService('http://localhost:8080/rest/product','POST',id) ;

此时将得到 T1=Product 以及 T2=Long

在 Java 8 中,我们可以使用类型推断,写法会更简洁:


Long id=... ;
Response<Product> product=getDataFromWebService('http://localhost:8080/rest/product','POST',id) ;

以下是另一种可能的调用方式:


Long[] ids=... ;
Response<List<Product>> product=getDataFromWebService('http://localhost:8080/rest/products','POST',ids) ;

在这种情况下,我们将得到 T1=List<Product>T2=Long[]

泛型类或方法可以对其泛型参数施加约束。让我们回顾一下 RxJava 中 [map] 方法的定义:


public final <R> Observable<R> map(Func1<? super T,? extends R> func)

该方法接受两个参数类型,T 和 R。类型 [Func1] 是一个泛型接口:

 

Func1 定义了一个功能接口,即仅包含一个方法的接口。功能接口是 Java 8 lambda 表达式的基石。在此,该接口的方法定义如下:

R call(T t)

因此,T 是传递给 [call] 的参数类型,R 是返回结果的类型。让我们回到 [map] 方法的定义:


public final <R> Observable<R> map(Func1<? super T,? extends R> func)
  • 方法 [map] 期望接收一个类型为 [Func1<? super T,? extends R>] 的参数,并返回类型为 [Observable<R>] 的结果;
  • ? super T:传递给方法 [Func1.call] 的参数必须是类型 T 或 T 的父类型
  • ? extends R:方法 [call] 返回的结果类型必须是 R 类型或其派生类型(若 R 为类),或实现 R 接口(若 R 为接口);

为了更好地理解上述两项约束,我们以 [https://docs.oracle.com/javase/tutorial/java/generics/bounded.html] 参考文档中的示例为例:


package tests;

public class Box<T> {

    private T t;

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }

    public <U extends Number> 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<Integer> integerBox = new Box<>();
        integerBox.set(new Integer(10));
        integerBox.inspect(new Long(20));
        integerBox.inspect(new Double(-1.78));
        //integerBox.inspect("some text"); // 错误:这仍然是字符串!
    }
}
  • 第 3 行:类 [Box] 由类型 T 参数化,该类型即第 5 行字段的类型;
  • 第 15 行:方法 [inspect] 接受类型为 U 的参数。方法也可以具有泛型参数。此时,其声明如下:
<T1, T2, ..., Tn> TResult méthode(paramètres ...)

此处,U 通过 <U> 声明为方法的泛型类型,但带有 <U extends Number> 的约束,即 U 必须继承类 [Number]。由于此约束,编译器在第 25 行报告了错误;

  • 第 23-24 行:使用从 [Number] 派生的类型调用 [inspect] 方法;

得到以下结果:

T: java.lang.Integer
U: java.lang.Long
T: java.lang.Integer
U: java.lang.Double

:要在 IntelliJ 中运行该类,请按以下步骤操作 [1, 2]:

 

现在,让我们来看以下示例:


    public void addNumbers(List<? super Integer> list) {
        for (int i = 1; i <= 3; i++) {
            list.add(i);
        }
}

方法 [addNumbers] 接受 List<T> 类型的参数,其中 T 是类 [Integer] 的父类。 由于这一限制,整数 int 1 到 3 可以添加到列表中(第 2-4 行),并且可以按以下方式调用该方法:


    List<Number> numbers=new ArrayList<>();
    // 添加双精度数 <-- Double
    numbers.add(7.8);
    // 添加 Long 类型数 <-- Long
    numbers.add(1L);
    // 添加 List<Number> 中的 Number <-- Integer
    addNumbers(numbers);
    // 显示 List<Number>
    for(Number number : numbers){
      System.out.println(number.getClass().getName());
}

此时将得到以下结果:

1
2
3
4
5
java.lang.Double
java.lang.Long
java.lang.Integer
java.lang.Integer
java.lang.Integer

要扩展 Box<T> 类,应编写如下代码:


class OtherBox<T> extends Box<T> {

}

子类本身也可以引入新的泛型参数:


class AnotherBox<T, T1> extends Box<T> {
    void consumer(T1 t1) {
        System.out.printf("%s%n", t1);
    }
}

接口也可以使用泛型参数:


interface I<T> {
    void doSomethingWith(T t);
}

要实现它,使用以下语法:


class A<T> implements I<T> {

    @Override
    public void doSomethingWith(T t) {
        System.out.printf("%s%n", t);
    }

}

现在我们对泛型已经有了足够的了解,可以开始探讨lambda表达式了。