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

不懂就问, Java 泛型,泛型方法,泛型接口

  •  
  •   RiceMarch · 2021-12-30 22:45:21 +08:00 · 3653 次点击
    这是一个创建于 1038 天前的主题,其中的信息可能已经有所发展或是发生改变。

    这是原有的泛型接口和具体的某一个实现类

    //泛型接口
    public interface SaveService<T> {
        void save(List<T> saveList);
    }
    
    //具体实现类
    public class UserSaveServiceImpl implements SaveService<User>{
        @Override
        public void save(List<User> saveList) {
            //...
        }
    }
    
    

    处于一些原因,必须把泛型接口改成非泛型接口...(很奇葩的问题,但是必须改掉接口的泛型)

    public interface SaveService {
       <T> void save(List<T> saveList);
    }
    

    于是想把泛型方法续上...强行续...

    public class UserSaveServiceImpl implements SaveService{
    
        @Override
        public <T> void save(List<T> saveList) {
            
        }
        
        //期望还能续上指定的类型 User
    }
    

    结果也很明显,具体实现类上的类型算是完蛋啦,请问如何能满足这样的奇葩需要么(能满足么...

    25 条回复    2022-01-07 11:34:58 +08:00
    donggexiongdi
        1
    donggexiongdi  
       2021-12-30 23:18:15 +08:00
    看下 hashmap
    RiceMarch
        2
    RiceMarch  
    OP
       2021-12-30 23:48:58 +08:00
    @donggexiongdi Map 也是泛型接口来着...public class HashMap<K,V> extends AbstractMap<K,V>,我就是不想加类的泛型
    Jooooooooo
        3
    Jooooooooo  
       2021-12-31 00:47:21 +08:00   ❤️ 1
    没看懂...

    而且你可以再想想是否提了 A/B 问题
    Macolor21
        4
    Macolor21  
       2021-12-31 01:48:11 +08:00 via iPhone   ❤️ 1
    续上类型是什么意思?
    你写的代码里写了一个泛型方法,那这个方法就是泛型的,它的约束范围只在方法级别。

    你的问题描述不清楚,但有两种走向:
    1. 你想实现类级别泛型,所有方法的返回类型等都是 User (只能泛型类 /接口)
    2. 你想实现 save 的泛型。(你已经写出了泛型方法,但你没指定泛型类型,在实现类还是 T ,需要将 T 改成 User ,就可以了)

    所以我还是不懂你的问题,你写的第二个实现中没有指定泛型类型,你的方法还是泛型方法。

    在你的第一个实现中你指定了泛型 User 。该类继承 T 的方法都是泛型。

    如果你是想用户使用 UserImpl 时,只能用 User 类型,那你把 T 改成 User 不就可以了?

    方法调用层只能看到 List<User>的入参
    wellsc
        5
    wellsc  
       2021-12-31 02:44:31 +08:00 via iPhone
    RedrumSherlock
        6
    RedrumSherlock  
       2021-12-31 06:11:28 +08:00 via Android
    什么叫续上指定的类型?调用方法的时候不就把类型传进去了么?
    hingbong
        7
    hingbong  
       2021-12-31 07:36:15 +08:00 via Android
    你是想接口方法是带泛型的,实现类不是?不行的,你想想你的需求在
    hingbong
        8
    hingbong  
       2021-12-31 07:37:56 +08:00 via Android
    你是想接口方法是带泛型的,实现类不是?不行的,结合多态来看,你想想你的需求在使用接口调用的时候怎么确定类型,除非在实现类判断类型,传参不对就报错
    zhilincom
        9
    zhilincom  
       2021-12-31 07:47:01 +08:00
    不让用泛型是公司不让用吗?要么遵守要么跳槽。如果不是干嘛给自己找不自在?
    Ariver
        10
    Ariver  
       2021-12-31 08:11:00 +08:00   ❤️ 1
    之前的实现,user 是被范型接口确定的,你的类,实现了这个,那么类型也就确定了。所以,方法上的,类型,也是确定的。
    你想的实现,接口不是范型的,那么实现类上也就没有那个 T 的类型了。
    那么,范型方法上的类型,就是在方法调用的时候根据传入的类型来决定的。

    ps.一般来说,这种范型方法我们通常做法是<? extends T>. 这样在你的 save 方法内,才可以使用 T 这个父类上的方法,不然没啥用。
    yidinghe
        11
    yidinghe  
       2021-12-31 08:20:09 +08:00 via Android
    接口改成这样,那就写一个唯一的实现类算了
    kujio
        12
    kujio  
       2021-12-31 09:13:28 +08:00
    可以试试不用泛型,用一个被继承的实体抽象类来代替泛型 T
    RiceMarch
        13
    RiceMarch  
    OP
       2021-12-31 09:19:43 +08:00 via iPhone
    @zhilincom 不是 是我另外的同事要在一个 list 塞这个接口的不同实现类 导致一个 list 有多种实现类,静态扫描被拒绝 所以他要求我把接口的泛型抹掉 哎 无奈
    cppc
        14
    cppc  
       2021-12-31 09:43:11 +08:00
    @RiceMarch 这难道不是你同事的问题,List<SaveService<?>> 不行?如果不用泛型,那么是不是会有 SaveUserService,SaveXxxService ,这样 List 就能装下你得接口了?
    timethinker
        15
    timethinker  
       2021-12-31 09:50:36 +08:00
    根据里氏替换原则,子类实例指向父类指针,也就是说假如有一个 SubUser extends User ,此时 List<User>是可以 add 这个 SubUser 实例的吧。如果想要返回 List<SubUser>,可以把方法签名改为 List<? extends User>。
    timethinker
        16
    timethinker  
       2021-12-31 09:56:36 +08:00
    @qwe520liao 这里说反了,应该是父类指针既可以指向父类实例,也可以指向子类实例。虽然 Java 没有指针的概念,但是这里相当于引用。
    wolfie
        17
    wolfie  
       2021-12-31 10:02:23 +08:00
    X Y +1
    aguesuka
        18
    aguesuka  
       2021-12-31 10:13:19 +08:00
    @RiceMarch XY 问题, 你只需将
    //泛型接口
    public interface SaveService<T> {
    void save(List<T> saveList);
    }
    改成
    //泛型接口
    public interface SaveService<T> {
    void save(List<? extends T> saveList);
    }
    aguesuka
        19
    aguesuka  
       2021-12-31 10:19:50 +08:00
    甚至是新的 method
    public interface SaveService<T> {
    void save(List<? extends T> saveList);
    void <T> saveAllWithoutTyping(List<T> saveList);
    }
    goalidea
        20
    goalidea  
       2021-12-31 11:47:48 +08:00
    如果你确定该实现类型是 User 强转过来就行了
    Joker123456789
        21
    Joker123456789  
       2021-12-31 13:00:23 +08:00   ❤️ 1
    按照你现在的写法,你传进来什么类型,T 就是什么类型,但是无法 在创建对象的时候 约束 这个方法只能接收什么类型。

    既然如此,不如 把 T 去掉咯,只要是 List 就收。

    如果 你想 实现在创建对象的时候 约束 这个方法只能接收什么类型,那么 接口上的 <T> 是必须保留的,也就是必须用你一开始的那种方式。

    如果你现在遇到的需求,用泛型是 最佳的方案,那你可以去跟 不让你用泛型的那个人 商量一下 怎么解决。
    如果不用泛型问题也不大,那么干脆去掉泛型把,因为你现在的这个做法 体现不出泛型的意义。
    aliveyang
        22
    aliveyang  
       2021-12-31 15:51:35 +08:00
    看不懂问题
    RiceMarch
        23
    RiceMarch  
    OP
       2021-12-31 18:44:18 +08:00 via iPhone
    @aguesuka 其实本质上是同事的奇葩想法 想把 interface SaveService<T>的这个 T 去掉我今天已经拒绝了他这个要求 hhhh
    RiceMarch
        24
    RiceMarch  
    OP
       2021-12-31 18:46:50 +08:00 via iPhone
    @Joker123456789 确实是的 今天已经和她商量过了 List 的类型肯定还是要的 不然静态检查直接被拦截了 hhhhh 还是按照第一种继续做了
    okou19900722
        25
    okou19900722  
       2022-01-07 11:34:58 +08:00
    很简单,因为 java 里的泛型是擦除的。
    目前我了解的可以通过反射获取到泛型的,似乎只有子类实现父类时,传给父类的泛型可以获取,比如

    List<String> list = new ArrayList<String>();
    这样写,拿不到,因为泛型是 ArrayList 上的,但

    List<String> list = new ArrayList<String>(){};
    这样写就可以拿到泛型,这是因为这实际上是生成了一个 ArrayList 子类的匿名类的对象。

    具体的原因,我说不清楚,写过 gson 的,应该对 TypeToken 很熟悉,基本都是 new TypeToken<xxxx>(){}的写法
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1071 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 19:37 · PVG 03:37 · LAX 12:37 · JFK 15:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.