前言
最近在写业务代码时,有很多相似的逻辑,利用泛型可以进行较好的抽象,提高代码复用性,因为泛型使用较少,开发起来速度较慢,所以这里总结一下泛型的一些基本使用
为什么会有泛型
- 泛型实现了参数化类型,使代码可以应用于多种对象类型,提高了代码的复用性。在没有泛型的情况下,我们可以通过Object来存储任意类型的对象,但是在使用的时候需要进行显式的类型转换,这种转换要求开发者能够预知对应的参数类型,代码不够优雅的同时也容易带来安全隐患(出现了没有预知到的对象)
- 引入泛型后编译时可以进行类型检查,提高了安全性
泛型种类
泛型接口
public interface GenericInterface<T> {
List<T> getValue();
}
class GenericInterfaceImplement implements GenericInterface<String> {
@Override
public List<String> getValue() {
return Arrays.asList("Hello", "World");
}
public static void main(String[] args) {
GenericInterfaceImplement genericInterfaceImplement = new GenericInterfaceImplement();
System.out.println(genericInterfaceImplement.getValue());
}
}
泛型类、泛型方法
- 泛型可以应用于整个类上,比如我们常见的Java中的各种容器类
- 即使是泛型类,同样可以包含参数化方法,也就是说,泛型方法的存在与其所在的类是否是泛型类没有关系
// 这是一个泛型类
public class GenericClass<T> {
private T t;
public T getT() {
return t;
}
/**
* 这是一个泛型方法吗?答案是否定的
* 因为GenericClass类实例化的时候,已经指明了此泛型类的具体类型
*
* @param t
*/
public void setT(T t) {
this.t = t;
}
/**
* 这是一个泛型方法吗?答案是肯定的
* 只有在进行方法调用的时候才能确定这个方法的具体类型
*
* @param e
* @param <E>
*/
static <E> void printMethod(E e) {
System.out.println(e);
}
/**
* 这是一个泛型方法吗?答案是肯定的
* 泛型方法和泛型类的泛型没有关系
*
* @param t
* @param <T>
*/
static <T> void printMethodTwo(T t) {
System.out.println(t);
}
/**
* 这个也是泛型方法,并且参数是可变参数
* @param args
* @param <E>
*/
static <E> void printMethodThree(E... args) {
for (E e : args) {
System.out.println(e.getClass().toString() + ":" + e);
}
}
public static void main(String[] args) {
GenericClass<String> genericClassString = new GenericClass<>();
genericClassString.setT("hello world");
System.out.println("泛型类:String类型:" + genericClassString.getT());
GenericClass<Long> genericClassLong = new GenericClass<>();
genericClassLong.setT(12L);
System.out.println("泛型类:Long类型:" + genericClassLong.getT());
GenericClass.printMethod("泛型方法:Integer类型:" + Integer.class);
GenericClass.printMethodTwo("泛型方法:Double类型:" + Double.class);
GenericClass.printMethodThree(1L, 2F, 3, "Hello");
}
}
泛型通配符
在定义泛型时,常常会用到T、K、V、E、?等等,这些通配符是什么意思呢?
- T(Type)表示一个具体的java类型
- K、V(Key、Value)一般用于表示java中键值对的类型
- E(Element)一般在遍历时用于表示元素的类型
- ?表示不确定的java类型
这些都是在编码时约定俗成的东西,提高了代码的可读性,当然,你也可以不按照这个规定来,比如T改成A,不会影响程序运行
extend和super上下界
<? extends T>
:表示参数化的类型必须是T或者T的子类
<? super T>
:表示参数化的类型必须是T或者T的父类
测试一下
代码中类的父子关系为:Person -> Man -> StrongMan
public class Person {
@Override
public String toString() {
return "Person";
}
}
public class Man extends Person {
@Override
public String toString() {
return "Man";
}
}
public class StrongMan extends Man {
@Override
public String toString() {
return "StrongMan";
}
}
public class SuperExtendTest {
public static void main(String[] args) {
List<Man> manList = new ArrayList<>();
List<Person> personList = new ArrayList<>();
List<StrongMan> strongManList = new ArrayList<>();
test(manList);
testTwo(personList);
// 编译不通过
testTwo(strongManList);
}
// 接收参数是Person或者Person的子类
static void test(List<? extends Person> persons) {
persons.forEach(System.out::println);
}
// 接收参数是Man或者Man的父类
static void testTwo(List<? super Man> mans) {
mans.forEach(System.out::println);
}
}
泛型使用的限制
泛型使用也有一些限制,可以参考这篇博客
泛型擦除
Java的泛型是伪泛型,因为在编译阶段,所有的泛型信息都会被擦除掉,生成的字节码中不包含泛型类型,只保留原始类型,我们称之为泛型擦除
什么是原始类型?比如代码中定义的List,在编译阶段都会变成List,这个List就是原始类型,对于JVM来说,泛型参数类型是不可见的
下面用两个例子证明一下泛型擦除
public static void main(String[] args) {
List<String> stringList = new ArrayList<String>();
stringList.add("reflect");
List<Integer> integerList = new ArrayList<Integer>();
integerList.add(123);
System.out.println(stringList.getClass() == integerList.getClass());
}
输出结果
true
两个List的泛型类型是不一样的,类信息却是相同的,说明在JVM中,只保留了原始类型
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
List<Integer> list = new ArrayList<Integer>();
list.add(1);
// 这样编译无法通过
// list.add("reflect");
//通过反射的方式则可以存储String
list.getClass().getMethod("add", Object.class).invoke(list, "reflect");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
输出结果
1
reflect
一个存储Integer对象的List,使用反射的方式竟然可以存储String,是不是意想不到,编译时有类型检查,无法添加,使用反射的方式绕过这个检查,就可以添加了,说明在JVM中,只保留了原始类型。需要注意的是,平时的开发中,不能使用这种方式让一个List存储多种类型对象,因为在使用该数据,类型转换时会报错
关于泛型的反射
泛型与反射结合使用,效果更佳
public class GenericReflectionTest {
Map<String, Integer> map;
public Map<String, Integer> getMap() {
return map;
}
public void setMap(Map<String, Integer> map) {
this.map = map;
}
public static void main(String[] args) throws NoSuchMethodException {
Class clazz = GenericReflectionTest.class;
Method method = clazz.getMethod("getMap");
Type genericReturnType = method.getGenericReturnType();
// 获取一个方法的泛型返回类型
if(genericReturnType instanceof ParameterizedType){
ParameterizedType parameterizedType = ((ParameterizedType) genericReturnType);
// 因为泛型的返回类型可能不止一个,所以返回回来的是一个数组
Type[] types = parameterizedType.getActualTypeArguments();
for (Type type : types){
Class actualClz = ((Class) type);
System.out.println("参数化类型为 : " + actualClz);
}
}
// 获取一个方法的泛型参数类型
Method setMethod = clazz.getMethod("setMap", Map.class);
Type[] genericParameterTypes = setMethod.getGenericParameterTypes();
for (Type genericParameterType: genericParameterTypes){
System.out.println("GenericParameterTypes为 : " + genericParameterType.getTypeName());
if(genericParameterType instanceof ParameterizedType){
ParameterizedType parameterizedType = ((ParameterizedType) genericParameterType);
System.out.println("ParameterizedType为 :" + parameterizedType.getTypeName());
Type types[] = parameterizedType.getActualTypeArguments();
for (Type type : types){
System.out.println("参数化类型为 : " + ((Class) type).getName());
}
}
}
}
}