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 ?
有关泛型的信息可参见网址 [https://docs.oracle.com/javase/tutorial/java/generics/]。下文部分内容源自该网址。泛型功能于 Java 1.5 版本中引入。
假设有一个提供各类信息的 Web 服务。该 Web 服务有时可能无法提供这些信息,此时必须向客户端报告错误。我们可以将 Web 服务的响应标准化为以下形式:
public class Response<T> {
// ----------------- properties
// operation status
private int status;
// any error messages
private List<String> messages;
// the body of the reply
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表达式中被广泛使用,因为在lambda表达式中,方法的泛型参数的实际类型通常会被省略。
现在,在客户端,[getDataFromWebService] 方法可以具有以下签名:
<T1,T2> Response<T1> getDataFromWebService(String urlWebService, String httpMethod, T2 post)
这里我们有一个由两个类型 T1 和 T2 泛型化的方法:
- T1 是预期响应正文的类型;
- T2 是 POST 操作中提交的值的类型,对于 GET 操作则为 null;
- 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 表达式的基础。在此,该接口的方法定义如下:
因此,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"); // error: this is still String!
}
}
- 第 3 行:[Box] 类由类型 T 泛型化,该类型即第 5 行字段的类型;
- 第 15 行:[inspect] 方法接受类型为 U 的参数。方法也可以具有泛型参数。此时,其声明如下:
此处,U 被声明为该方法的泛型类型,使用 <U> 声明,但带有约束条件 <U extends Number>,即 U 必须继承 [Number] 类。由于此约束,编译器在第 25 行报告了错误;
- 第 23–24 行:对 [Number] 派生类型的 [inspect] 方法的调用;
得到以下结果:
注意:要在 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] 类的超类。由于这一限制,整数 1 到 3 可以被添加到列表中(第 2–4 行),并且可以按以下方式调用该方法:
List<Number> numbers=new ArrayList<>();
// ajout double Number <-- Double
numbers.add(7.8);
// ajout Long Number <-- Long
numbers.add(1L);
// ajout List<Number> Number <-- Integer
addNumbers(numbers);
// affichagede List<Number>
for(Number number : numbers){
System.out.println(number.getClass().getName());
}
这将产生以下结果:
要扩展 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函数了。


