Java泛型
java泛型简介
Oracle官网中对于java泛型的描述:https://docs.oracle.com/javase/tutorial/java/generics/why.html
泛型的用处:泛型使类型(类和接口)在定义类、接口和方法时成为参数,从而使相同的一段代码处理多种不同类型的传入数据
来自官网的例子,使用泛型的函数或类使用的形式:
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast
而没有使用泛型时,需要强制转换:
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
java中的List接口,提供对不同类型的类的标准方法。想象一下,若没有java泛型,则只能使用强制转换,并带来某些问题。
对于其中的get()函数:
ArrayList.java
......
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}
......
可见此函数直接返回容器内的类,不需要强制转换的步骤,程序更加安全。
泛型使程序员能够实现通用算法。 通过使用泛型,程序员可以实现适用于不同类型集合的泛型算法,安全且易于阅读
java泛型类
java创建泛型类的原因是为了容器类的实现。
容器类是用来保存对象的,将对象放入其中,并对放入的一个或多个对象进行操作,如排序,增加,删除,查找等。这就要求容器类需要接收不同类型的类。所以必须使用一个机制,使容器类对所有的类都来者不拒。泛型类应运而生。
java泛型类简介
举个例子,假设定义一个简单的类:
public class Box {
private Object object;
public void set(Object object) { this.object = object; }
public Object get() { return object; }
}
这样的类存在的问题:此类的功能时接收一个类和返回接收的类,所以可能导致接收的类和期望返回的类(可能使用强制转换)不是同一个类型,导致运行时错误。同时在编译时此类错误无法被识别。
于是,改写为java泛型类:
/**
* Generic version of the Box class.
* @param <T> the type of the value being boxed
*/
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
java泛型类保证了传入的类型和期望返回的类型是相同的。使用“public class Box”来创建泛型声明,这引入了类型变量 T,代表传入对象的类型。
java泛型类的定义
java泛型类使用如下格式定义:
class name<T1, T2, ..., Tn> { /* ... */ }
类型参数部分由尖括号 (<>) 分隔,跟在类名之后。 它指定类型参数(也称为类型变量)T1、T2、…和 Tn。
在java的文档上有一个类型参数命名约定:
- E - Element
- K - Key
- N - Number
- T - Type
- V - Value
- S,U,V etc. - 2nd, 3rd, 4th types
java泛型类使用
以Box泛型类为例,要从代码中引用泛型 Box 类,您必须执行泛型类型调用,它将 T 替换为一些具体值,例如 Integer:
Box<Integer> integerBox;
可以将泛型类型调用视为类似于普通方法调用,但不是将参数传递给方法,而是将类型参数(在本例中为 Integer)传递给 Box 类本身
与任何其他变量声明一样,此代码实际上并未创建新的 Box 对象。 它只是声明 integerBox 将保存对“Box of Integer”的引用,这就是 Box 的读取方式。 泛型类型的调用通常称为参数化类型。 要实例化这个类,像往常一样使用 new 关键字,但将 放在类名和括号之间:
Box<Integer> integerBox = new Box<Integer>();//泛型的实例化
java泛型函数
java泛型函数简介
和java泛型类类似,泛型函数通过引入所在类的类型参数。类型参数的范围仅限于声明它的方法。 允许使用静态和非静态泛型方法,以及泛型类构造函数。
java泛型函数定义
泛型方法的语法包括一个类型参数列表,在尖括号内,它出现在方法的返回类型之前,例如:
public <K,V> boolean function(K k,V v)
对于静态泛型方法,类型参数部分必须出现在方法的返回类型之前,例如:
public class Util {
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}
泛型函数可以使用泛型类中的类型参数,例如:
public class Pair<K, V> {//泛型类声明
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; }
}
有界类型参数
有界类型参数简介
在使用java泛型的时候,可能会希望限制用作参数化类型中的类型参数的类型,也就是限制传入类的类型种类。譬如,只接受Number类型及其子类的对象作为实例化参数。这就是有界类型参数的用途。
有界类型参数的定义
形如:
<T extends Integer>
称为有界类型参数
直接上示例代码:
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<Integer>();
integerBox.set(new Integer(10));
integerBox.inspect("some text"); // error: this is still String!
}
}
上述代码在编译阶段会检查inspect函数中传输的参数类型,并给出错误提示,以保证传入的类型是Number及其子类。
同样也可以在java泛型类声明的时候使用有界类型参数,在编译此类型参数在类中的函数使用参数类型T的时候进行检查,保证类中所有使用T的函数都符合限定的类型:
public class NaturalNumber<T extends Integer> {
private T n;
public NaturalNumber(T n) { this.n = n; }
public boolean isEven() {
return n.intValue() % 2 == 0;
}
// ...
}
多重有界类型参数的定义
前面的示例说明了使用具有单个边界的类型参数,但类型参数可以具有多个边界
<T extends B1 & B2 & B3>
具有多个边界的类型变量是边界中列出的所有类型的子类型。如果边界之一是类,则必须首先指定它
Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }
class D <T extends A & B & C> { /* ... */ }
如果未首先指定绑定 A,则会出现编译时错误
class D <T extends B & A & C> { /* ... */ } // compile-time error
泛型的继承和子类
如您所知,只要类型兼容,就可以将一种类型的对象分配给另一种类型的对象。
泛型也是如此。您可以执行泛型类型调用,将 Number 作为其类型参数传递,如果参数与 Number 兼容,则允许任何后续的 add 调用:
Box<Number> box = new Box<Number>();
box.add(new Integer(10)); // OK
box.add(new Double(10.1)); // OK
看看下面的示例
public void boxTest(Box<Number> n) { /* ... */ }
boxTest接受一个类型为Box的参数,但在使用中不能传入Box或者Box,因为不是Box的子类型。
所以可以有这样一张图:
给定两个具体类型 A 和 B(例如,Number 和 Integer),无论 A 和 B 是否相关,MyClass 与 MyClass 都没有关系。 MyClass 和 MyClass 的共同父对象是 Object
那么,java泛型类有怎样的继承关系呢:
以 Collections 类为例,ArrayList 实现了 List,而 List 扩展了 Collection。 所以 ArrayList 是 List 的子类型,List 是 Collection 的子类型。 只要不改变类型参数,类型之间的子类型关系就会保留。
若泛型类存在多个类型参数,也可以从但类型参数的泛型类中继承
interface PayloadList<E,P> extends List<E> {
void setPayload(int index, P val);
...
}
PayloadList 的以下参数化是 List 的子类型
PayloadList<String,String>
PayloadList<String,Integer>
PayloadList<String,Exception>
它们的关系如图所示
java通配符
在java泛型中,称为通配符的问号 ( ?
)表示未知类型。通配符可用于多种情况:作为参数、字段或局部变量的类型;有时作为返回类型。通配符永远不会用作泛型方法调用、泛型类实例创建或超类型的类型参数。
上界通配符
在Java编程中,可以使用上界通配符来放宽对类型参数的限制。
举一个例子
public static void process(List<? extends Foo> list) { /* ... */ }
上界通配符,<? extends Foo>
,其中Foo是任何类型的,匹配Foo和任何Foo的子类。下列process方法代码可以遍历使用list容器的Foo对象或者Foo类的子类对象的List容器类:
public static void process(List<? extends Foo> list) {
for (Foo elem : list) {
// ...
}
}
无界通配符
无界通配符类型使用通配符 ( ?
)指定,例如List<?>
。这称为未知类型列表。在两种情况下,无界通配符是一种有用的方法:
- 如果您正在编写可以使用
Object
类中提供的功能实现的方法。 - 当代码不依赖于泛型类中的类型参数时。例如,
List.size
或List.clear
。事实上,Class<?>
之所以如此常用,是因为Class<T> 中的
大多数方法都不依赖于T
。
内部没有使用T或者其他类型参数的函数:
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
下界通配符
下界通配符将未知类型限制为特定类型或该类型的父类型
以下代码将数字 1 到 10 添加到列表的末尾,该方法适用于List<Integer>
、List<Number>
和List<Object>
以及任何可以保存Integer
值的类:
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
有关于通配符的子类型
如泛型的继承和子类中所述,泛型类或接口之间的关系不仅仅因为它们的类型之间存在关系。但是可以使用通配符来创建通用类或接口之间的关系。
给定两个非泛型类
class A { /* ... */ }
class B extends A { /* ... */ }
编写下列代码是正确的
B b = new B();
A a = b;
此示例显示常规类的继承遵循此子类型规则:如果B
扩展A ,
则类B
是类A
的子类型。此规则不适用于泛型类型:
List<B> lb = new ArrayList<>();
List<A> la = lb; // compile-time error
上述代码可以看出List并不是List的子类型
于是在学习了java通配符后,有这么一种关系:
List和List共同的父级是List<?>
,而List和List之间没有关系。
为了在这些类之间创建关系,以便代码可以通过List<Integer>
的元素访问Number
的方法,请使用上限通配符:
List<? extends Integer> intList = new ArrayList<>();
List<? extends Number> numList = intList; // OK. List<? extends Integer> is a subtype of List<? extends Number>
Integer
是Number
的子类型,而numList
是Number
对象的列表,所以现在intList
(Integer
对象的列表)和numList
之间存在关系。下图显示了使用上限和下限通配符声明的几个List
类之间的关系。
根据上述规则可以列出通用List类声明的层次结构
对泛型的限制
- 无法使用原始类型实例化泛型类型
- 无法创建类型参数的实例
- 不能声明类型为类型参数的静态字段
- 不能对参数化类型使用 Casts 或
instanceof
- 无法创建参数化类型的数组
- 无法创建、捕获或抛出参数化类型的对象
- 无法将每个重载的形式参数类型擦除为相同原始类型的方法重载