V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
7911364440
V2EX  ›  Java

如何通过反射获取 List<T>中泛型 T 的真实类型?

  •  
  •   7911364440 · 2021-11-09 10:23:13 +08:00 · 5258 次点击
    这是一个创建于 1099 天前的主题,其中的信息可能已经有所发展或是发生改变。
    public class PageResult<T> {
    
        private int total;
        private List<T> data;
    
        public static <T> PageResult<T> of(Page<T> page) {
            return new PageResult<>(page.getSize(), page.getContent());
        }
    
    }
    
    35 条回复    2021-11-17 13:30:35 +08:00
    zongren
        1
    zongren  
       2021-11-09 10:24:55 +08:00
    好像获取不到吧,只能把 class 传进来
    chendy
        2
    chendy  
       2021-11-09 10:35:42 +08:00
    看你的需求
    如果只是想看看 List 里面是啥,取第一个元素看一眼类型就行了(对空 List 无效)
    v2lf
        3
    v2lf  
       2021-11-09 10:37:02 +08:00
    通过继承, 设置一个抽象的超类 ,实现这个需求
    wolfie
        4
    wolfie  
       2021-11-09 10:37:03 +08:00
    泛型是编译期,反射是运行时。
    orangie
        5
    orangie  
       2021-11-09 10:40:59 +08:00
    话说,为什么当初添加泛型功能的时候,没有给添加一个字段用于保存编译时能确定的这个泛型类型呢?比如给加一个 getGenericClass()方法,编译器实现内部代码让他返回 T 的真实类型。虽然这么做好像不能用于协变和逆变,不过再加一个方法用来返回协变、逆变、准确泛型?
    kiotech
        6
    kiotech  
       2021-11-09 10:45:22 +08:00
    泛型 T 可以为 interface ,即 PageResult<PageInterface>,这样就可以拿到 page.getSize(), page.getContent()
    youkiiiiiiiiiiiI
        7
    youkiiiiiiiiiiiI  
       2021-11-09 10:47:24 +08:00
    GG
    leeg810312
        8
    leeg810312  
       2021-11-09 10:47:38 +08:00 via Android
    @orangie JAVA 历史遗留问题,很难改了,你说的特性只有运行时真泛型才能做到,反射是运行时,编译时泛型是做不到的
    clf
        9
    clf  
       2021-11-09 11:25:04 +08:00   ❤️ 1
    SpringBoot 下泛型接口:
    public interface IService<T> {
    ...
    default Class<T> getClazz() {
    return (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), IService.class);
    }
    ...
    }
    clf
        10
    clf  
       2021-11-09 11:26:10 +08:00
    @clf #9 能拿到的是 public class XXXService extends IService<XXX> {} 的 XXX.class
    ZoteTheMighty
        11
    ZoteTheMighty  
       2021-11-09 11:30:30 +08:00
    T 里面的类继承一个抽象类,抽象类里放一个抽象方法 getClassInfo , 然后子类实现该接口。
    guyuesh2
        12
    guyuesh2  
       2021-11-09 11:30:53 +08:00   ❤️ 2
    Class#getDeclaredField

    Field#getGenericType

    ParameterizedType 和 Type 的一些子类了解一下,泛型反射是 Gson FastJson 的基础吧 !
    0xZhangKe
        13
    0xZhangKe  
       2021-11-09 13:09:33 +08:00
    可以看看 Gson 是怎么做的,就是上面说的继承会保存范型类型。
    passerbytiny
        14
    passerbytiny  
       2021-11-09 13:17:41 +08:00 via Android
    首先给答案,不能。原因:Java 泛型仅在编译时有效,编译之后任何泛型信息都被擦除了。
    passerbytiny
        15
    passerbytiny  
       2021-11-09 13:24:46 +08:00 via Android
    当然有折衷的方法。继承关系中会残留一些泛型信息到运行时,可以用来变相获取 T 。你不能在运行时获取 List<T>中 T 的实际类型,但是可以获取 List<T extend SomeAbstractClass/Interface>中 T 的实际类型。
    passerbytiny
        16
    passerbytiny  
       2021-11-09 13:28:59 +08:00 via Android
    上面说错了一点,是可以获取 List<T extend SomeAbstractClass/Interface<T>>中 T 的实际类型。
    INCerry
        17
    INCerry  
       2021-11-09 13:40:20 +08:00   ❤️ 2
    如果是 C#的话 直接即可
    public Type GetListType<T>(List<T> list)
    {
    return list.GetType().GetGenericArguments()[0];
    }
    thetbw
        18
    thetbw  
       2021-11-09 13:54:28 +08:00
    无解,我用的就是楼上的方法,当用 aop 对参数处理的时候,遇到 list 就把 list 中的数据取出来判断类型,如果没数据,跳过
    geligaoli
        19
    geligaoli  
       2021-11-09 14:02:59 +08:00
    可以有变通的方式,

    1. 在 PageResult<T> 的子类中获取。2. 直接 new 对象时传递 T 的类型。

    public class PageResult<T> {
    private Class<T> entityClass;

    /**
    * 子类中获取 entityClass
    */
    public PageResult() {
    this.entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    /**
    * new 对象的时候传递
    *
    * @param entityClass
    */
    public PageResult(Class<T> entityClass) {
    this.entityClass = entityClass;
    }
    Edsie
        20
    Edsie  
       2021-11-09 17:03:50 +08:00
    3 楼正解
    xingda920813
        21
    xingda920813  
       2021-11-09 17:17:34 +08:00
    对 3 楼思路的一个具体实现, 支持嵌套泛型. https://gist.github.com/xingda920813/6d01c009242b168796aa318d287a7f58
    wjploop
        22
    wjploop  
       2021-11-09 20:46:28 +08:00
    @guyuesh2 感谢,又涨知识了,附上一篇查到的博文。https://www.cxyzjd.com/article/sai_simon/98663284

    另外,我能不能这么理解?泛型擦除只是为了兼容以前无泛型的代码,运行时不去检验类型,而泛型信息还是保留着。
    humpy
        23
    humpy  
       2021-11-09 21:11:08 +08:00
    我之前尝试过

    /**
    * 获取集合的元素类型
    *
    * @param type 集合类型
    * @param genericType 集合的泛型类型信息
    */
    public static Type resolveCollectionElementType(Class<?> type, Type genericType) {
    if (genericType instanceof ParameterizedType) {
    ParameterizedType pt = (ParameterizedType)genericType;
    return pt.getActualTypeArguments()[0];
    } else {
    return type;
    }
    }
    learningman
        24
    learningman  
       2021-11-09 21:34:59 +08:00
    @wjploop #22 擦除了就是没了,楼上那些实现方法是自己加了个成员标记类型
    john6lq
        25
    john6lq  
       2021-11-09 21:38:21 +08:00 via iPhone
    三楼方法是可以,安卓封装 viewbinding 了解到的。
    sagaxu
        26
    sagaxu  
       2021-11-09 21:40:54 +08:00
    最好不要拿,拿了就跟调用方耦合了
    v2lf
        27
    v2lf  
       2021-11-09 23:10:29 +08:00
    补充下, 基本原理实现, 使用的话,最好按照需求,封装层级继承,并且需要添加其他逻辑,处理边界情况

    ```java

    static class Person extends TypeWrap<String> {

    }

    static abstract class TypeWrap<T>{
    private final Type type;

    protected TypeWrap(){
    Type genericSuperclass = getClass().getGenericSuperclass();
    Type raw = ((ParameterizedType)genericSuperclass).getActualTypeArguments()[0];
    //todo 需要处理范型作为类型参数
    type = raw;
    }

    public Type getType() {
    return type;
    }
    }

    ```
    v2lf
        28
    v2lf  
       2021-11-09 23:12:16 +08:00
    @wjploop 我理解哈,raw class 是没有类型信息的,ParameterizedType 的类型信息实现,是通过读取文本解析出来的, 可以看下源码哈,jdk 中的
    xarthur
        29
    xarthur  
       2021-11-10 00:11:04 +08:00 via iPhone
    这个就是类型擦除啊……
    wjploop
        30
    wjploop  
       2021-11-10 10:33:34 +08:00   ❤️ 1
    @v2lf 你提到的文本应该指的是编译后的字节码,而非*.java 源文件吧,不太明白提到的
    “raw class 是没有类型信息的,ParameterizedType 的类型信息实现”。

    既然在字节码中保留着参数类型的信息,自然就有办法提取出来,提取方法涉及到了 ParameterizedType 。

    验证字节码中仍保留着参数类型可以使用 javap 查看,具体参考 https://wiyi.org/type-erasure.html

    另外,若是泛型信息在字节码不存在了,那么 Gson 没法正确转换 json 数据包,这也是如 12 楼提到的。
    scruel
        31
    scruel  
       2021-11-10 13:58:05 +08:00
    v2ex 现在是国内的 stackoverflow 了吗?
    cheng6563
        32
    cheng6563  
       2021-11-10 16:37:57 +08:00
    @wjploop 反正就是擦了,但没完全擦。
    guyuesh2
        33
    guyuesh2  
       2021-11-10 16:59:33 +08:00   ❤️ 1
    @wjploop 可以去看一下 <<JAVA 虚拟机规范>> 这本书,泛型信息在 Class 文件中是通过签名(Class 文件的常量池中泛型类型标记的字符串)保留的. 泛型擦除我认为是运行时存储这个泛型变量的机制(即全部通过 Object 进行存储,而不是通过泛型的具体类型进行存储,如果通过 具体类型存储,那么 ArrayList 这个类会存在多少类型? ArrayList#get 之后好像会根据泛型类型,插入强制类型转换的代码,用于转换成为用户指定的 ArrayList 类型)

    额外扩展一下,泛型类型之间也存在父子关系,可以了解一下 PECS 法则和 协变逆变

    最重要的是,我很久没看这一块,这些只是我很早以前看得一个大概记忆和理解,可能有错误的地方,请批评指正
    dbpe
        34
    dbpe  
       2021-11-10 17:19:48 +08:00
    我在想一个问题...如果 aot 和类似 native image 的推广..那些反射有什么办法解决呢?
    Aresxue
        35
    Aresxue  
       2021-11-17 13:30:35 +08:00
    泛型擦除会擦除到类的上界,普通的对象的上界就是 Object ,一般是拿不到的,如果 method 里有声明可以依赖 ParameterizedType 去获取,不然就取里面的对象的类型,如果还是空的那 jvm 就感知不到了,需要你手动设一个上界或者传进去
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3799 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 00:52 · PVG 08:52 · LAX 16:52 · JFK 19:52
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.