1、概述
- 在之前的27、【JavaSE】【Java 核心类库(上)】集合类-Collection等文章中,代码的写法是下面这样的:
List<Object> list = new ArrayList<>();
Queue<Object> queue = new LinkedList<>();
好,那个时候,并没有对那个<>尖括号里的Object进行解释。但是那个时候,我们也发现了新创建的集合中,不管什么样类型的数据都能往里面放。实际上,当我们编写的时候,不要<>时,也是能够放不同类型的东西。
通常情况下集合中可以存放不同类型的对象,是因为将所有对象都看作
java.lang.Object类型放入的,因此从集合中取出元素时也是java.lang.Object类型,为了表达该元素真实的数据类型,则需要强制类型转换,而强制类型转换可能会引发类型转换异常。为了避免上述错误的发生,从 Java 5 开始增加泛型机制,也就是在集合名称的右侧使用<数据类型>的方式来明确要求该集合中可以存放的元素类型,若放入其它类型的元素则编译报错。
泛型只在编译时期有效,在运行时期不区分是什么类型。但是,虽然运行期间不区分类型,但也不能进行下面的这种操作:
List<String> list1 = new ArrayList<>();
List<Integer> list2 = list1; // 直接报错,编译不通过
2、基本使用
import java.util.ArrayList;
import java.util.List;
public class OneTest {
public static void main(String[] args) {
List<String> list = new ArrayList<String>(); // 只允许放入 String 类型
list.add("one");
list.add("two");
System.out.println(list);
}
}
自 Java 7 开始,有“菱形特性”,也就是说,List<String> list = new ArrayList<String>();语句中右侧<>中的数据类型可以不用写以简化代码,List<String> list = new ArrayList<>();
3、泛型的底层原理
- 泛型的本质就是参数化类型,也就是让数据类型作为参数传递,其中 E 相当于形式参数负责占位,而使用集合时
<>中的数据类型相当于实际参数,用于给形式参数 E 进行初始化,从而使得集合中所有的 E 被实际参数替换,由于实际参数可以传递各种各样广泛的数据类型,因此得名为泛型。

4、泛型类与泛型接口
上面也提到过泛型的本质就是一种“类型参数”,但是这种参数能使得在开发中更为灵活,这个灵活就会体现在一些包括自定义的泛型接口、泛型类、泛型方法等地方。
泛型接口:泛型接口和普通接口的区别就是后面添加了类型参数列表,可以有多个类型参数,如:
<E, T, ...>等。
public interface IGenericity<T> {
void set(T elem); // 方法的参数类型取决于泛型的具体情况
T get(); // 方法的返回值类型取决于泛型的具体情况
}
// 泛型最终具体指定为 java.lang.Integer 类型
// 然后,注意看所要实现接口中的方法
public class GenericityImpl implements IGenericity<Integer> {
@Override
public void set(Integer elem) {
}
@Override
public Integer get() {
return null;
}
}
- 泛型类:泛型类和普通类的区别就是类名后面添加了类型参数列表,可以有多个类型参数,如:
<E, T, ...>等。
// 泛型不一定只有1个!!!
public class Genericity<K, V> {
private K key; // 成员变量使用泛型
private V value;
// 方法的参数类型使用泛型
public void set(K key, V value) {
}
public V get(K key) {
return null;
}
}
public class GenericityTest {
public static void main(String[] args) {
Genericity<String, String> test = new Genericity<>();
test.set("key", "value");
String value = test.get("key");
}
}
- 实例化泛型类、实现泛型接口等情况下应该指定具体的数据类型(当然,不指定的话,一般会按照
java.lang.Object进行处理,但是不提倡这样做,因为泛型的初衷就是为了让使用者指定自己所需要的),并且是引用数据类型而不是基本数据类型(定义的时候,可以用像 E、T、V、K 等来表示一种“占位”,待到具体实现的时候,需要将那个“占位”的换成具体的类型)!
5、泛型类继承过程中泛型的处理
- 父类有泛型,被继承时,子类可以选择保留泛型也可以选择直接指定泛型类型。
// 父类
public class Genericity<K, V> {
private K key;
private V value;
public void set(K key, V value) {
}
public V get(K key) {
return null;
}
}
- 情况一:“泛型擦除”的一种,即子类不再保留父类的泛型特性,在这种情况下,一般会按照
java.lang.Object处理(不建议这么做)。
public class SubGenericity extends Genericity {
}
public class GenericityTest {
public static void main(String[] args) {
SubGenericity test = new SubGenericity();
// set(Object key, Object value)
test.set(1, "xxxx");
// Object get(Object key);
Object obj = test.get(1);
}
}
- 情况二:“泛型擦除”的一种,即子类不再保留父类的泛型特性,但是在继承的时候,直接指定具体的类型。
public class SubGenericity extends Genericity<Integer, String> {
}
public class GenericityTest {
public static void main(String[] args) {
// 这种情况下,如果希望使用“父类引用指向子类对象”,那么父类的引用的类型必须将其泛型指定为和子类一样的,否则是错误的
Genericity<Integer, String> one = new SubGenericity();
// 由于继承时,已经失去了泛型特性,所以,没有 <> 了
SubGenericity two = new SubGenericity();
}
}
- 情况三:子类保留父类的泛型特性。
// 注意:这种情况下,两个 <> 中的表示 类型参数 的字母顺序、数量、内容要完全一致
// 像 class SubGenericity<E, V> extends Genericity<K, V> 是不允许的
// 父类的 <> 中的数量必须和之前父类定义的时候是一致的
// 像 class SubGenericity<K, V> extends Genericity<K, V, E> 是一定不被允许的
// class SubGenericity<V, E> extends Genericity<V, E> 是允许的
public class SubGenericity<K, V> extends Genericity<K, V> {
}
public class GenericityTest {
public static void main(String[] args) {
Genericity<String, String> one = new SubGenericity<>();
SubGenericity<Integer, String> two = new SubGenericity<>();
}
}
- 情况四:子类保留父类的泛型特性,但同时,子类可以增加自己的泛型。子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型。
// 其中 E 是子类自身所增加的泛型
// 注意:子类增加的泛型放在子类的 <> 中的哪一个位置上没有规定,但是增加泛型的前提是保证父类的 <> 中的东西,子类的 <> 中也必须有
// class SubGenericity<K, V, E> extends Genericity<K, V> 可以
// class SubGenericity<E, K, V> extends Genericity<K, V> 可以
public class SubGenericity<K, E, V> extends Genericity<K, V> {
private E elem;
}
public class GenericityTest {
public static void main(String[] args) {
Genericity<Integer, String> one = new SubGenericity<Integer, Boolean, String>();
SubGenericity<Integer, Boolean, String> two = new SubGenericity<>();
}
}
6、泛型方法
泛型方法(包括自定义泛型方法)就是我们输入参数的时候,输入的是泛型参数,而不是具体的参数。我们在调用这个泛型方法的时需要对泛型参数进行实例化。
关于泛型方法,大家可能会将下面的这样的情况误认为是泛型方法:
public class Genericity<K, V> {
private K key;
private V value;
public void set(K key, V value) {
}
public V get(K key) {
return null;
}
}
上面的代码中,可以看到set和get这两个方法中都带有“泛型参数”,但是严格意义上说它们不是泛型方法,因为这些方法中的“泛型参数”,在被调用之前,就已经被具体化了,因为要使用这些方法的前提是必须先实例化对象,而一旦实例化就必须指定类的泛型参数,进而就指定这些方法中的“泛型参数”。
真正意义上的泛型方法,应该是其泛型参数与这个类的泛型参数无关。
public class GenericityTest {
public static void main(String[] args) {
Genericity<Integer, String> one = new Genericity<>();
// 方法中的“泛型参数”已经被具体化了
// void set(Integer key, String value) {······}
// String get(Integer key) {······}
}
}
泛型方法的格式:
[访问权限] [static] <泛型> 返回值类型 方法名([泛型标识 参数名称]) { 方法体; }泛型方法的举例:
public class Genericity {
public <T> void printArray(T[] arr) {
for (T elem : arr) {
System.out.println(elem);
}
}
}
可以看到,上面的这个方法,是真正意义上的泛型方法。泛型参数的具体类型,在该方法使用的时候才被指定。
public class GenericityTest {
public static void main(String[] args) {
Genericity genericity = new Genericity();
Integer[] arr1 = {1, 2, 3};
genericity.printArray(arr1);
String[] arr2 = {"Tom", "Jerry"};
genericity.printArray(arr2);
}
}
1
2
3
Tom
Jerry
- 在静态方法中使用泛型参数的时候,需要我们把静态方法定义为泛型方法。这一点,也比较容易理解,一个方法中如果出现泛型参数,无外乎两种情况,一种情况是该方法的泛型参数是与类一致的,另一种情况是该方法是泛型方法。如果是前者,显然这个方法是绝不可能使用
static修饰,否则的话,这个方法中的泛型参数如何与类保持一致?至于后者,方法中的泛型参数和类的泛型参数(如果有的话)是无关的,所以,是可以将其用static修饰以方便使用。
public class Genericity {
public static <T> void printArray(T[] arr) {
for (T elem : arr) {
System.out.println(elem);
}
}
}
public class GenericityTest {
public static void main(String[] args) {
Integer[] arr1 = {1, 2, 3};
// 通过类名直接调用
Genericity.printArray(arr1);
String[] arr2 = {"Tom", "Jerry"};
Genericity.printArray(arr2);
}
}
7、泛型通配符
如果 B 是 A 的一个子类或子接口,而 G 是具有泛型声明的类或接口,则 G<B> 并不是 G<A> 的子类型!比如:String 是 Object 的子类,但是 List<String> 并不是 List<Object> 的子类。
泛型中有三种通配符形式:
<?>:无限制通配符,表示可以传入任意类型的参数。
<? extends E>:表示类型的上界是 E,只能是 E 或者是 E 的子类。
<? super E>:表示类型的下界是 E,只能是 E 或者是 E 的父类。
import java.util.ArrayList;
import java.util.List;
public class GenericityTest {
public static void main(String[] args) {
List<?> list = new ArrayList<>();
// 注意,只能获取不能添加
}
}
public class Genericity {
}
public class SubGenericity extends Genericity {
}
import java.util.ArrayList;
import java.util.List;
public class GenericityTest {
public static void main(String[] args) {
List<? extends Genericity> list = new ArrayList<>();
// 注意,只能获取不能添加
// 解释一下为什么不能添加:首先要明确 List<? extends Genericity> 的意义
// List<? extends Genericity> 的含义应该是 List<Genericity> 或者 List<Genericity的某个子类>
// 所以,这样的话,add(new Genericity()) 是不合适的,万一是 List<SubGenericity>
// add(new SubGenericity()) 也是不合适的,万一是 List<OtherSubGenericity>
// 如果是获取的话,必须使用 Genericity 类型的引用来接收,其他的类型都不可以,因为不管怎么样,放在其中的东西,都是 Genericity 类或其子类
}
}
import java.util.ArrayList;
import java.util.List;
public class GenericityTest {
public static void main(String[] args) {
List<? super Genericity> list = new ArrayList<>();
list.add(new Genericity());
list.add(new SubGenericity());
// 注意,可以获取也可以添加,但是添加的必须是 Genericity 类或其子类,因为下限是 Genericity
// 尽管能添加了,但还是说要理解 List<? super Genericity> 的意义,List<Genericity> 或 List<Genericity的某个父类>
}
}
- 关于泛型的这个通配符
?及其限制通配符,一般在 Java 的源码中比较常见,而开发的时候,不大常用,一般理解其含义即可。