Java基础

泛型作用

编译检查 + 强转

  1. 编译检查
  2. 取值时自动转型(其实作用1是为作用2进行铺垫)

各种复杂的泛型其实都是为了实现这两点,即保证:

  1. 我们存进去的类型确实我们需要存进去的
  2. 这样我们取出来的类型就也一定不会有错,所以编译器就可以帮助我们大胆的进行强转

一个小栗子

例如现在有这样的一个需求,我想要实现一个加法器:

  • 可以计算整数之和(只考虑int)
  • 可以计算小数之和(只考虑double)

假设我们还活在没有泛型的远古时代

  1. 首先定义一个接口
inteface Calculator {
    Object add(Object num1,Object num2);
}
  1. 接着用整数的方式来实现
public class IntegerCal implements Calculator {
    @Override
    public Object add(Object num1, Object num2) {
        return (Integer) num1 + (Integer) num2;
    }
}
  1. 先别急着写小数的,我们先来测试下这个整数加法器
public class CalTest {
    public static void main(String[] args) {
        Integer intRes = (Integer)new IntegerCal().add(1,2);
        System.out.println(intRes);
    }
}

似乎很完美,但是加入我们没按规矩,把小数当了参数呢?

Integer intRes = (Integer)new IntegerCal().add(1.1,2);

在运行时就会报一个类型转换的错误了,为了保证错误的友好型,我们决定在整数加法器中进行一些校验

  1. 加点类型校验
public class IntegerCal implements Calculator {
    @Override
    public Object add(Object num1, Object num2) {
        if(!(num1 instanceof Integer) ) {
            throw new RuntimeException(num1 + "不是整数");
        }
        if(!(num2 instanceof Integer) ) {
            throw new RuntimeException(num2 + "不是整数");
        }
        return (Integer) num1 + (Integer) num2;
    }
}
  1. 现在你准备好写小数加法器了吗?如果我告诉你这只是暂时的需求,未来还可能加入乘法,除法等方法呢?
    你说聪明如我,把这些校验逻辑抽成一个方法不就行了?
    那么如果我说后面还要支持字符串加法器,Person类加法器,Student类加法器,各种各样类型的加法器的时候又该如何呢?
    还有一个致命的缺点,这些错误你不把程序跑起来是发现不了的

升级后的栗子

  • 升级后的泛型接口
public interface CalculatorGeneric<T> {
    T add(T num1,T num2);
}
  • 升级后的整数加法器
public class IntegerCalGeneric implements CalculatorGeneric<Integer> {

    @Override
    public Integer add(Integer num1, Integer num2) {
        return num1 + num2;
    }
}
  • 使用全新的泛型加法器
public class CalTest {
    public static void main(String[] args) {
        // 不需要我们自己强转了
        Integer intRes = new IntegerCalGeneric().add(1,2);
        System.out.println(intRes);
    }
}

又有人不老实想传小数怎么办?编译时就给你打趴下

public class CalTest {
    public static void main(String[] args) {
        // 想蒙混过关?编译都不让你过
        Integer intRes = new IntegerCalGeneric().add(1,2);
        System.out.println(intRes);
    }
}

小结

上面这个栗子就很好的体现出了泛型的作用:
编译检查 + 强转
正因为编译时保证了我传进去的都是整数,所以我才可以放心大胆的在返回时进行强转

泛型与反射

泛型还有一个非常大的作用,就是在编译时,如果类的参数类型就可以确定了,泛型是可以被反射读到的。

例如下面这种情况

class StartListener extends AppListener<StartEvent>

class UserMapper extends BaseMapper<User>
  • 这个类的类型参数在编译的时候我就可以确定了,所以虽然说泛型被擦除了,但是最后在反射中是可以读取到它的信息的,JVM通过另外的方式存储了它的信息
  • 通过这个方法我们相当于在运行时拥有了一个参数化的类型,并且这个参数类型具体是什么我们还知道,我们就可以利用这个特性做很多事情

应用

  • Spring中的监听器机制就是利用这个原理实现的,通过泛型声明事件类型,从而拿到监听器想要监听的事件
  • Mybatis Plus中也是通过这种方式拿到UserMapper想要操作的表对象User

如何拿到?

关键就在于一个叫做ParameterizedType的类型
如何拿到呢?
可以通过Class类中的getGenericInterfaces或者getGenericSuperclass拿到。
顾名思义,就是拿到泛型的接口和泛型的父类,然后强转成ParameterizedType
例如下面这个例子:

public class UserMapper extends BaseMapper<User> {
    public static void main(String[] args) {
        // 第一步:拿到ParameterizedType类型
        ParameterizedType parameterizedType =
                (ParameterizedType) UserMapper.class
                        .getGenericSuperclass();

                        
        // 第二步:拿到真实的参数类型
        Class<User> clazz = (Class<User>) parameterizedType
                .getActualTypeArguments()[0];
        System.out.println(clazz);
    }
}

几个术语

  • ArrayList中的E称为类型参数变量 TypeVariable(暂时还没发现有啥实际用途)
  • ArrayList中的Integer称为实际类型参数
  • 整个ArrayList成为泛型类型
  • 整个ArrayList称为参数化的类型ParameterizedType
原文地址:https://www.cnblogs.com/bax-life/p/14590413.html