Java基础-泛型

309 阅读4分钟

前言

最近在写业务代码时,有很多相似的逻辑,利用泛型可以进行较好的抽象,提高代码复用性,因为泛型使用较少,开发起来速度较慢,所以这里总结一下泛型的一些基本使用

为什么会有泛型

  1. 泛型实现了参数化类型,使代码可以应用于多种对象类型,提高了代码的复用性。在没有泛型的情况下,我们可以通过Object来存储任意类型的对象,但是在使用的时候需要进行显式的类型转换,这种转换要求开发者能够预知对应的参数类型,代码不够优雅的同时也容易带来安全隐患(出现了没有预知到的对象)
  2. 引入泛型后编译时可以进行类型检查,提高了安全性

泛型种类

泛型接口
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());
    }
​
}

image.png

泛型类、泛型方法
  1. 泛型可以应用于整个类上,比如我们常见的Java中的各种容器类
  2. 即使是泛型类,同样可以包含参数化方法,也就是说,泛型方法的存在与其所在的类是否是泛型类没有关系
// 这是一个泛型类
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");
    }
}

image.png

泛型通配符

在定义泛型时,常常会用到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);
    }
​
}

泛型使用的限制

泛型使用也有一些限制,可以参考这篇博客

blog.csdn.net/hanchao5272…

泛型擦除

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());
                }
            }
​
        }
    }
    
}

参考资料

github.com/byhieg/Java…