概述
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 在面向对象编程及各种设计模式中有非常广泛的应用。泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是**参数化类型**,也就是说所操作的数据类型被指定为一个参数。
注意参数的类型只能是引用类型,不能是原始类型(int, char, double 这种),据说是当初设计师偷懒!!!
根据惯例,类型参数是单个大写字母,该字母用于指示所定义的参数类型。下面列出每个用例的标准类型参数:
- T 类型
- E 元素
- K 键
- V 值
- N 数字
- S、U、V 等:多参数情况中的第 2、3、4 个类型
泛型类别
泛型类
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分,类型可以有多个:<K, V>。最典型的就是各种容器类,如:List, Set, Map
List<String> strList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
Map<String, Object> map = new HashMap<>();
举个例子
通常我们在开发中会定义一个统一的接口返回类型,根据传入的参数类型 T 不同可以返回不同的数据类型,其中 T 可以用其他标识代替,如K, V, U
public class HttpResult<T> {
private T data;
public HttpResult(T data) {
this.data = data;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static void main(String[] args) {
HttpResult<String> strResult = new HttpResult<>("String类型");
HttpResult<Boolean> booleanResult = new HttpResult<>(true);
HttpResult<Integer> integerResult = new HttpResult<>(100);
System.out.println("str:" + strResult.getData());
System.out.println("boolean:" + booleanResult.getData());
System.out.println("integer:" + integerResult.getData());
}
}
原始类 (raw class)
如果我们使用一个泛型类,但是不带类型参数,那么我们使用的是原始类。
List list = new ArrayList();
list.add(new Object());
list.add(99);
list.add("123");
泛型方法
- 泛型方法,和泛型类相似,是在调用方法的时候指明泛型的具体类型 。
- 所有泛型方法声明都有一个**类型参数声明部分(由尖括号分隔: )在方法返回类型之前**。
- static 方法和static 域均不可以引用类的类型变量,因为存在**泛型擦除**,后面讲
- 只有声明了类型参数的方法才是泛型方法,泛型类中使用了泛型的方法不是泛型方法
扩展上面的例子
public class HttpResult<T> {
private int code;
private String msg;
/**
* 具体返回数据,定义为泛型
* T 可以为其他任意标识 E,K,V等
*/
private T data;
/**
* 这个不是泛型方法,虽然用到了 T
* 但是返回类型前面没有使用 类型声明
*/
public HttpResult(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
/**
* 这个是泛型方法
* 返回类型前面有类型声明<T>,T 可以为其他标识
* !注意 这里的类型声明和类的类型申明都是 <T>,但这2个声明是不相关的,
* static 方法和static 域均不可以引用类的类型变量,应为存在泛型擦除。
* 为了防止混淆,这里可以使用其他标识D代替:
* public static <D> HttpResult<D> success(D data){
* return new HttpResult<>(200, "success", data);
* }
*/
public static <T> HttpResult<T> success(T data) {
return new HttpResult<>(200, "success", data);
}
泛型接口
和定义泛型类相似。
public interface GenericService<T> {
T getOwn();
}
接口实现未传入具体类型的,仍然以泛型的形式实现
public class GenericServiceImpl<T> implements GenericService<T> {
/**
* 仍然显示泛型
*/
@Override
public T returnSelf(T oldEntity) {
System.out.println(oldEntity.getClass());
return oldEntity;
}
}
接口实现传入具体类型的,显示具体类型
public class GenericServiceImpl<Integer> implements GenericService<Integer> {
/**
* 这里用传入的Integer类型替换掉原来的泛型
*/
@Override
public Integer returnSelf(Integer oldEntity) {
System.out.println(oldEntity.getClass());
return oldEntity;
}
}
通配符
定义基本关系
// 基本树形结构
class BaseTree {}
// 部门树
class DepartmentTree extends BaseTree {}
// 菜单树继承基本树结构
class MenuTree extends BaseTree {}
无界通配符 ?
问号 (
?
) 通配符可用于使用泛型代码表示未知类型。通配符可用于参数、字段、局部变量和返回类型。但最好不要在返回类型中使用通配符,因为确切知道方法返回的类型更安全。
假设我们想编写一个方法来验证指定的List
中是否存在指定的对象。我们希望该方法接受两个参数:一个是未知类型的 List
,另一个是任意类型的对象。
public static <T> void checkList(List<?> myList, T obj){
if(myList.contains(obj)){
System.out.println("The list contains the element: " + obj);
} else {
System.out.println("The list does not contain the element: " + obj);
}
}
上限通配符 <? extends T>
限制类型为特定类型或者特定类型的子类
当获取数据是会隐式的转为其基类(或者Object基类)
/**
* 只能传 BaseTree 或者它的子类 DepartmentTree、 menuTree
*
* @param tree 传入的树结构
*/
public static <T extends BaseTree> T UpperBoundType(T tree) {
if (tree instanceof DepartmentTree) {
System.out.println("depTree");
} else if (tree instanceof MenuTree) {
System.out.println("menuTree");
} else {
System.out.println("baseTree");
}
return tree;
}
/**
* 指定类型 T 返回类型 T
*/
public static <T extends BaseTree> T UpperBoundList(List<T> treeList) {
T tree = treeList.get(0);
if (tree instanceof DepartmentTree) {
System.out.println("depTree");
} else if (tree instanceof MenuTree) {
System.out.println("menuTree");
} else {
System.out.println("baseTree");
}
return tree;
}
/**
* 使用 <? extends BaseTree>
* 获取列表元素时只能使用父类去接受,
* 但还是可以获取到子类的类型
*/
public static BaseTree UpperBoundList(List<? extends BaseTree> treeList) {
// 这里只能使用BaseTree去接收参数,要用子类型需要强转
BaseTree tree = treeList.get(0);
// 传入DepartmentTree 或者 MenuTree时,下面的依然可以执行
if (tree instanceof DepartmentTree) {
System.out.println("depTree");
DepartmentTree base = (DepartmentTree) tree;
} else if (tree instanceof MenuTree) {
System.out.println("menuTree");
MenuTree ment = (MenuTree) tree;
} else {
System.out.println("baseTree");
BaseTree base = (BaseTree) tree;
}
return tree;
}
下限通配符 <? super T>
限制类型为特定类型或者特定类型的超类
/**
* 只能传MenuTree或者它的父类的List
* 接收类型为Object
* 需要强转到对应的类型
*/
public static void LowerBoundType(List<? super MenuTree> menuTree) {
Object obj = menuTree.get(0);
if (obj instanceof MenuTree) {
System.out.println("menuTree");
MenuTree ment = (MenuTree) obj;
} else if (obj instanceof BaseTree) {
System.out.println("baseTree");
BaseTree base = (BaseTree) obj;
}
}
泛型擦除
泛型类可以由编译器通过所谓的类型擦除过程而转变成非泛型类。这样,编译器就生成了一种与泛型类同名的原始类,但是类型参数都被删除了。对应的类型变量由他们的类型界限来代替,上限通配符的为其父类,下限通配符的为Object。
泛型的限制
基本类型
基本类型(int,double)不能用做类型参数,必须使用包装类(Integer, Double)
static 语境
在一个泛型类中,static方法和static域都不可以引用类的类型变量,因为类在类型擦除后就不存在类型变量了。
泛型类的实例化
不能创建一个泛型类型的实例化,下面代码是错误的:
T obj = new T();
在类型擦除后T由它的限界代替,可能是Object(甚至是抽象类),因此对new没有调用意义。
泛型数组
同样不能创建一个泛型数组,下面代码是错误的:
T [] arr = new T(10);
在类型擦除后T由它的限界代替,很可能是Object T,于是对T[]的类型转换将无法进行,应为Object[] IS-NOT-A T[]。
一般不建议创建泛型数组,尽量使用ArrayList来代替泛型数组。下面提供了一种创建泛型数组的方法
public class GenericArrayFactory<T> {
private T[] array;
/**
* 初始化数组
*
* @param componentType 数组的类别
* @param length 容量
*/
public void init(Class<T> componentType, int length) {
// 这里会有一个类型转换警告 Object to T[]
//noinspection unchecked
array = (T[]) Array.newInstance(componentType, length);
}
/**
* 赋值
*
* @param index 索引
* @param item 值
*/
public void put(int index, T item) {
array[index] = item;
}
/**
* 获取数组
*
* @return arr
*/
public T[] getArray() {
return array;
}
public static void main(String[] args) {
GenericArrayFactory<Integer> genericArrayFactory = new GenericArrayFactory<>();
// 初始化Integer类型的数组
genericArrayFactory.init(Integer.class, 3);
genericArrayFactory.put(0, 9);
genericArrayFactory.put(1, 8);
genericArrayFactory.put(2, 7);
Integer[] intArray = genericArrayFactory.getArray();
for (Integer integer : intArray) {
System.out.println(integer);
}
}
}