Java 泛型(generics)是 JDK 5 中引入的一个新特性, 在面向对象编程及各种设计模式中有非常广泛的应用。泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是 参数化类型 ,也就是说所操作的数据类型被指定为一个参数。
注意参数的类型只能是引用类型,不能是原始类型(int, char, double 这种),据说是当初设计师偷懒!!!
根据惯例,类型参数是单个大写字母,该字母用于指示所定义的参数类型。下面列出每个用例的标准类型参数:
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分,类型可以有多个:。最典型的就是各种容器类,如: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()); } } 复制代码
如果我们使用一个泛型类,但是不带类型参数,那么我们使用的是原始类。
List list = new ArrayList(); list.add(new Object()); list.add(99); list.add("123"); 复制代码
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); } } 复制代码
限制类型为特定类型或者特定类型的子类
当获取数据是会隐式的转为其基类(或者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; } 复制代码
限制类型为特定类型或者特定类型的超类
/** * 只能传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域都不可以引用类的类型变量,因为类在类型擦除后就不存在类型变量了。
不能创建一个泛型类型的实例化,下面代码是错误的:
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); } } } 复制代码