Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
泛型的好处:统一数据类型,将运行期的错误提升到编译期
注意事项:泛型中只能编写引用数据类型,如Integer、Double等,使用int、double会报错
java 中泛型标记符:
- E – Element (在集合中使用,因为集合中存放的是元素)
- T – Type(Java 类)
- K – Key(键)
- V – Value(值)
- N – Number(数值类型)
- ? – 表示不确定的 java 类型
泛型类
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。
class Student<E> {
private E e;
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
}
创建不同类型的对象,会创建不同数据类型的成员变量 e 。
泛型方法
下面是定义泛型方法的规则:
- 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的 <E>)。
- 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
- 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
泛型方法也分为静态方法和非静态方法。
非静态泛型方法
非静态的方法 : 内部的泛型, 会根据类的泛型去匹配。
如上文中的 setE() 方法:
public void setE(E e) {
this.e = e;
}
静态泛型方法
静态的方法 : 静态方法中如果加入了泛型, 必须声明出自己独立的泛型。在调用方法, 传入实际参数的时候, 确定到具体的类型。
public static<T> void printArray(T[] arrays){
for (T t : arrays){
System.out.println(t);
}
}
public static void main(String[] args) {
String[] arr1 = {"a", "b", "c"};
Integer[] arr2 = {1, 2, 3};
Double[] arr3 = {1.1, 2.2, 3.3};
printArray(arr1);
printArray(arr2);
printArray(arr3);
}
运行结果:
泛型接口
类实现接口的时候,如果接口带有泛型,有两种操作方法
- 实现类:实现接口的时候确定到具体的类型
- 类延续接口的泛型:没有指定具体类型, 就让接口的泛型, 跟着类的泛型去匹配
实现类
给出一个泛型接口,其中有一个show方法:
interface Inter<E> {
void show(E e);
}
对于实现类,在实现接口的时候就要确定具体的数据类型:
class InterAImpl implements Inter<String> {
@Override
public void show(String s) {
}
}
类延续接口的泛型
没有指定具体类型, 就让接口的泛型, 跟着类的泛型去匹配。
类延续了接口的泛型:
class InterBImpl<E> implements Inter<E>{
@Override
public void show(E e) {
}
}
在创建对象的时候再确定具体的数据类型
InterBImpl<String> i = new InterBImpl<>();
泛型通配符
- ?(任意类型)
- ? extends E(只能接收 E 或是 E 的子类)
- ? super E(只能接收 E 或是 E 的父类)
给出抽象父类和两个子类:
abstract class Employee {
private String name;
private double salary;
public Employee() {
}
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public abstract void work();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String toString() {
return "Employee{name = " + name + ", salary = " + salary + "}";
}
}
class Coder extends Employee {
@Override
public void work() {
System.out.println("程序员写代码...");
}
}
class Manager extends Employee {
@Override
public void work() {
System.out.println("项目经理分配任务...");
}
}
创建含有泛型通配符的静态方法:
public static void method(ArrayList<?> list){
for (Object o : list) {
Employee e = (Employee) o;
e.work();
}
}
在主函数中创建对象并调用method方法
public static void main(String[] args) {
ArrayList<Coder> list1 = new ArrayList<>();
list1.add(new Coder());
ArrayList<Manager> list2 = new ArrayList<>();
list2.add(new Manager());
method(list1);
method(list2);
}
再创建一个String类型的对象,并调用method方法。由于list3不是类对象,会发生报错
public static void main(String[] args) {
ArrayList<Coder> list1 = new ArrayList<>();
list1.add(new Coder());
ArrayList<Manager> list2 = new ArrayList<>();
list2.add(new Manager());
ArrayList<String> list3 = new ArrayList<>();
list3.add("abc");
method(list1);
method(list2);
method(list3);
}
修改method方法,使用 ?extends E 通配符,会发现method传入list3参数,编译报错:
public static void method(ArrayList<? extends Employee> list){
for (Object o : list) {
Employee e = (Employee) o;
e.work();
}
再创建一个Object类型的对象,并调用method方法。由于list4不是Employee的子类对象,编译报错
修改method方法,使用 ?super E 通配符,调用method方法传入list4参数,正常运行:
public static void method(ArrayList<? super Employee> list){
for (Object o : list) {
Employee e = (Employee) o;
e.work();
}