Java通过反射获取方法的返回值的类型以及参数化的实际类型(下篇)

发布于 2020-07-28  518 次阅读


笔者最近在学习mybatis的框架,在写源码的时候遇到了一些关于通过反射获取返回值类型的实际参数类型的问题,并且已经写有一篇关于简单的返回值类型的获取,诸如public User getOne(int id);获取返回值的类型是User,public List< User > getAll();的返回值类型是List< User >,其参数化类型数组是 {User},也就是参数化类型是<> 里面包含的参数类型,比如public Map getAll();的返回值是Map,其参数化类型数组是 {String,Integer},具体可参考笔者之前写的文章:Java通过反射获取方法的返回值的类型以及参数化的实际类型(上篇)
该文章对一些简单的返回值的参数类型有了比较详细的讲解,那么对于更加负责的参数化类型,比如public Map< T,V > getAll()中,假若在实例化的时候这个T为Dog.java类,V为Cat.java类,那么在获取其返回值参数化数组的实际类型时,其参数化数组为 {Dog,Cat},对应的全限定类名为:{entity.Dog,entity.Cat},比较复杂的该怎么获取呢?下面我们通过几个小例子进行详细讲解。
1.首先建立用于测试的StudentDao.java接口以及其实现类StudentDaoImpl.java。

package dao;

import java.util.Map;

/**
 * @author 陌意随影
 TODO :学生dao测试类
 *2020年7月26日  下午11:02:10
 */
public interface StudentDao<T,V> {
    /**
     * 获取所有对象
     * @return 返回一个map
     */
    public Map<T,V> getAll();
    /**
     * 获取一个T对象
     * @return 返回T
     */
    public T getT();
    /**
     * 获取一个V对象
     * @return 返回V
     */
    public  V getV();

}

该接口的实现类StudentDaoImpl.java的源码为:

package dao;

import java.util.List;
import java.util.Map;

/**
 * @author 陌意随影
 TODO :学生dao测试类
 *2020年7月26日  下午11:02:10
 */
public interface StudentDao<T,V> {
    /**
     * 获取所有对象
     * @return 返回一个map
     */
    public Map<T,V> getAll();
    /**
     * 获取一个T对象
     * @return 返回T
     */
    public T getT();
    /**
     * 获取一个V对象
     * @return 返回V
     */
    public  V getV();
    /**
     * @return 返回T的集合
     */
    public List<T> getAllT();

}

2.我们参考着上一篇:Java通过反射获取方法的返回值的类型以及参数化的实际类型(上篇)来进行测试看看是否能获取我们想要的结果。首先建立测试文件:

package test;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import org.junit.jupiter.api.Test;

import dao.StudentDao;
import dao.StudentDaoImpl;
import entity.Cat;
import entity.Dog;

class StudentDaoTest {

    @Test
    void test() throws Exception {
        //父接口充当声明部分
        StudentDao<Dog, Cat> studentDao  = new StudentDaoImpl();
         //根据StudentDao的实例化对象获取字节码对象
        Class<?> studentDaoClass =studentDao.getClass();
        //获取UserDao所有的方法
        Method[] methods = studentDaoClass.getMethods();
        for(Method method: methods) {
            //获取方法的名称
            String methodName = method.getName();
            //判断是否是studentDao中的get方法,
            if(methodName.startsWith("get")&& !methodName.startsWith("getClass")) {
                //返回一个Type对象,表示由该方法对象表示的方法的正式返回类型。
              Type genericReturnType = method.getGenericReturnType();
              //获取实际返回的参数名
              String returnTypeName = genericReturnType.getTypeName();

              System.out.println(methodName+"的返回参数是:"+returnTypeName);
              //判断是否是参数化类型
              if(genericReturnType instanceof ParameterizedType) {
                  //如果是参数化类型,则强转
                  ParameterizedType parameterizedType = (ParameterizedType) genericReturnType;
                //获取实际参数类型数组,比如List<Dog>,则获取的是数组[Dog],Map<Dog, Cat> 则获取的是数组[Dog, Cat]
                  Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                  System.out.println(methodName+"的返回值类型是参数化类型,其类型为:");
                  for(Type type:actualTypeArguments) {
                      //强转
                      Class<?> actualTypeArgument = (Class<?>) type;
                     //获取实际参数的类名
                      String name = actualTypeArgument.getName();
                      System.out.print("  "+name);
                  }
                  System.out.println();

              }else {
                  //不是参数化类型,直接获取返回值类型
                  Class<?> returnType = method.getReturnType();
                  //获取返回值类型的类名
                  String name = returnType.getName();
                  System.out.println(methodName+"的返回值类型不是参数化类型其类型为:"+name);

              }

            }


        }
        }

    }

进行JUnit测试,其结果为:

在这里插入图片描述

发现能运行打印输出一部分,中途出现异常直接中断运行了。根据JUnit测试的 提示:
在这里插入图片描述

在进行对public Map getAll();这个参数化方法进行解析的时候,强转遇到异常从而中断程序的运行。下面我们仔细分析看看这个原因是啥:
2.1首先获取字节码

Class<?> studentDaoClass =studentDao.getClass();

获取字节码后,我们知道在字节码中方法的返回值类型和代码中的返回值类型是一致的,比如public Map getAll();,那么返回值的类型就是Map;虽然我们在实例化时:

StudentDao<Dog, Cat> studentDao  = new StudentDaoImpl<>();

已经使用了实际类型,但是我们获取的字节码对象studentDaoClass 中是没有具体类型的信息的,因为我们获取返回值的类型的时候会获得的是Map,然后在

 Class<?> actualTypeArgument = (Class<?>) type;

进行转换的时候,由于Map中的T和V是泛型,而泛型是不支持强转的,所以会报错,从而结束程序运行。显然,在上述的情况下我们是无法进行获取到方法的返回值的正确类型的。因此我们要想获取函数中的返回值的具体类型,就必须要修改StudentDaoImpl.java,改为:

package dao;

import java.util.List;
import java.util.Map;

import entity.Cat;
import entity.Dog;


/**
 * @author 陌意随影
 TODO :StudentDao接口的实现了
 *2020年7月27日  上午12:35:32
 */
public class StudentDaoImpl implements StudentDao<Dog,Cat>{

    @Override
    public Map<Dog, Cat> getAll() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Dog getT() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Cat getV() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public List<Dog> getAllT() {
        // TODO Auto-generated method stub
        return null;
    }




}

也就是在子类实现的时候就该指定泛型的具体类型,然后
运行JUnit测试,其结果为:
在这里插入图片描述

从结果中我们发现,运行成功,并且打印输出,但是发现如果是不是参数化的每个方法的返回值都回打印两次,并且每次的返回值的参数化实际类型都不一样;而参数化的每个方法的返回值都值打印一次,并且打印的结果均正确。其原因是为何呢?
其实原因是因为,StudentDao是父接口,其实现类StudentDaoImpl继承了StudentDao接口的所有方法,当我们

//父接口充当声明部分
        StudentDao<Dog, Cat> studentDao  = new StudentDaoImpl();
         //根据StudentDao的实例化对象获取字节码对象
        Class<?> studentDaoClass =studentDao.getClass();

获取的是StudentDaoImpl的字节码对象,studentDaoClass 中包含了自己定义的方法列表以及父类接口的方法列表。由于父类StudentDao接口中是泛型,public T getT();和public V getV();的返回值对象都是Object,而Map getAll();和List getAllT();的返回对象分别是Map,List.
StudentDaoImpl中

@Override
    public Dog getT() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Cat getV() {
        // TODO Auto-generated method stub
        return null;
    }

这两个函数的返回值类型分别是Dog和Cat,这时候子类StudentDaoImpl和StudentDao中的方法的返回值不一样,子类的方法无法覆盖父类的方法,所以字节码studentDaoClass 中实际上包含有四个方法 public Object getT();public Dog getT(); 和public Object getV();public Cat getV();
另外,返回值类型是集合的,不考虑参数化类型,比如public Map getAll();的返回值类型是Map; public List getAllT()的返回值类型是List;
因此,无论是在StudentDao还是在StudentDaoImpl中,public Map< T,V > getAll(),public Map< Dog,Cat > getAll()返回值类型都是Map;public List getAllT();和public List< Dog > getAllT();的返回值类型都是List,这说明子类的public Map< Dog, Cat > getAll() ;和public List< Dog> getAllT() 完全覆盖了父类的方法,因此studentDaoClass只有子类StudentDaoImpl的
public Map< Dog,Cat> getAll()和public List< Dog> getAllT();方法。
综上所述,studentDaoClass中实际上是有我们自定义的八个(StudentDao有四个,StudentDaoImpl有四个)方法中的六个方法。分别是:
public Object getT();public Dog getT(); 和public Object getV();public Cat getV();以及public Map< Dog,Cat> getAll()和public List< Dog> getAllT();这六个方法。因此在我们获取方法的返回值时,打印输出的是上述结果。
3.本次实验项目源码以及打包上传到服务器,需要的请自行下载,解压后直接导入eclipse即可:http://moyisuiying.com/wp-content/uploads/2020/07/javademo.rar


繁华落尽,雪花漫天飞舞。