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

Java 泛型方法与多态,这样解释对吗

  •  
  •   amiwrong123 · 2019-09-07 16:33:04 +08:00 · 6012 次点击
    这是一个创建于 1910 天前的主题,其中的信息可能已经有所发展或是发生改变。
    class A{}
    class B extends A{}
    
    class GenericMethods {
        public <T> T f1(T x) {
            System.out.println(x.getClass().getName());
            return x;
        }
    }
    
    public class testP {
        public static void main(String[] args) {
            GenericMethods gm = new GenericMethods();
            A a = gm.f1(new B());
            //B b = gm.f1(new A());编译报错
        }
    }
    

    f1 方法是一个泛型方法,返回值是< >里的T,形参也是< >里的T。所以二者推断出来的具体类型必须一样,或者符合多态。

    执行A a = gm.f1(new B())时,返回值处的T被推断为A,形参处的T本来会被推断为B,但是由于前者,形参处的T这里被推断为A。这里传B对象作为实参符合多态,泛型和多态不冲突。

    还有就是 B b = gm.f1(new A());编译通不过怎么解释比较好,可以认为这句是推断为B返回的也是B只是这里不允许向下转型吗?

    第 1 条附言  ·  2019-09-07 17:08:31 +08:00
    想问问 f1 这个泛型方法,返回值里的 T,形参里的 T。这两个地方的类型参数推断是分别进行的吗?

    我认为是分别进行的。形参里的 T 是靠实参推断的;返回值里的 T,是靠函数赋值过去的那个对象推断的。
    第 2 条附言  ·  2019-09-08 12:17:55 +08:00
    ```java
    interface Generator<T> {
    T next();
    }

    public class BasicGenerator<T> implements Generator<T> {

    private Class<T> type;
    public BasicGenerator(Class<T> type) {
    this.type = type;
    }

    @Override
    public T next() {
    try {
    return type.newInstance();
    } catch (InstantiationException | IllegalAccessException e) {
    throw new RuntimeException(e);
    }
    }

    public static <T> Generator<T> create(Class<T> type) {
    return new BasicGenerator<T>(type);
    }

    public <T> Generator<T> create1(Class<T> type) {
    return new BasicGenerator<T>(type);
    }

    public <T> void test (T t) {
    System.out.println(t.getClass().getName());
    }

    public static void main(String[] args) {
    Generator<Integer> gen = BasicGenerator.create(Integer.class);
    for (int i = 0; i < 5; i++) {
    System.out.println(gen.next());
    }

    //gen.creat1(String.class); 以下两行编译报错
    //gen.test(1);
    }
    }
    ```
    各位,不好意思,又有个问题。这是我对上面的解释:
    create 方法是静态的泛型方法,而泛型类 BasicGenerator 的类型参数是与对象相关的,所以 create 方法里的 T 不是泛型类的 T。
    而 create1 方法是成员的泛型方法,因为对象 gen 已经被创建出来了,所以 create 方法里的 T 就是泛型类 BasicGenerator 的 T。也就是说,T 是 Inteter,现在所有的成员方法你都要把 T 看做 Integer 了。所以那两行会报错(是不是可以理解:使用同一个类型参数的情况下,只有在非泛型类里的成员泛型方法才能体现出泛型方法的灵活性,即泛型方法能够独立于类而产生变化)。报错信息如下:
    Error:(39, 12) java: 找不到符号
    符号: 方法 creat1(java.lang.Class<java.lang.String>)
    位置: 类型为 Generator<java.lang.Integer>的变量 gen
    Error:(40, 12) java: 找不到符号
    符号: 方法 test(int)
    位置: 类型为 Generator<java.lang.Integer>的变量 gen
    28 条回复    2019-09-26 13:44:01 +08:00
    hantsy
        1
    hantsy  
       2019-09-07 16:39:03 +08:00
    牛马不相及的两个东西。
    Raymon111111
        2
    Raymon111111  
       2019-09-07 16:42:55 +08:00
    B 是一个 A

    而 A 却不是一个 B

    换个简单的说法, 有一个装水果的篮子可以装苹果, 但是有一个装苹果的篮子肯定不能装水果, 因为这个水果可能是香蕉.
    Bromine0x23
        3
    Bromine0x23  
       2019-09-07 16:44:00 +08:00
    类型参数只能从方法参数推导的
    `gm.f1(new B())` 就推导成 `B f1(B)`,`gm.f1(new A())` 就推导成 `A f1(A)`
    amiwrong123
        4
    amiwrong123  
    OP
       2019-09-07 16:53:31 +08:00
    @Bromine0x23
    可是我觉得类型参数也能通过返回值处的类型参数推断啊。比如:
    ```java
    class GenericMethods {
    public <T> T f2(Object x) {
    System.out.println(x.getClass().getName());
    return (T)x;
    }
    }

    public class testP {
    public static void main(String[] args) {
    GenericMethods gm = new GenericMethods();
    int o1 = gm.f2(2);
    String o2 = gm.f2(2);//能通过编译,但运行时报错
    }
    }
    ```
    这里可以认为,执行`String o2 = gm.f2(2)`后,由于这里`T`就被推断为了`String`,一个实际为 2 的 Object 对象在向上转型为`String`类型后,执行(String)x,这句会受到 RTTI 的检查,被发现无法转型后便报错。
    Bromine0x23
        5
    Bromine0x23  
       2019-09-07 17:31:03 +08:00   ❤️ 1
    @amiwrong123
    详细说的话是 f2 的 T 参数在信息不足的情况下被推延了。
    这个推导是分段进行的,首先是对 gm.f2(2),由于 T 没出现在参数中所以这里不能确定(或者说可选范围是 <= Object )
    然后 int o1 = <T> 或者 String o2 = <T>,在就能确定 T 的类型了

    对前面 B b = gm.f1(new A()), 在 gm.f1(new A()) 中已经能确定 T = A 了,所以之后 B b = <A> 的表达式由于类型不匹配导致编译失败
    amiwrong123
        6
    amiwrong123  
    OP
       2019-09-07 17:38:33 +08:00
    @Bromine0x23
    谢谢回答。但 A a = gm.f1(new B());这里该怎么理解呢,照你这么说,意思就是,有了方法参数的推断,就不需要返回值的推断了。那这里 f1 的 T 就被推断为 B 了呗。

    只是 gm.f1(new B())返回了一个 B 对象,然后由于赋值,向上转型为了一个 A 对象。
    Bromine0x23
        7
    Bromine0x23  
       2019-09-07 17:40:31 +08:00
    @amiwrong123 是的
    cigarzh
        8
    cigarzh  
       2019-09-07 19:42:12 +08:00 via iPhone
    你这报错和泛型有啥关系……
    amiwrong123
        9
    amiwrong123  
    OP
       2019-09-07 19:59:42 +08:00 via Android
    @cigarzh
    主要类型参数推断理解错了
    axlecho
        10
    axlecho  
       2019-09-07 21:08:01 +08:00 via Android
    A a = new B ok
    B b = new A failed
    这里跟泛型没关系
    fengpan567
        11
    fengpan567  
       2019-09-07 21:17:00 +08:00
    和泛型没关系。这个是 B 是子类,A 是父类,父类的实例引用不能指向子类,但是子类实例引用是可以指向父类的
    ninjachen
        12
    ninjachen  
       2019-09-07 21:52:24 +08:00 via Android
    乡下转型。。。
    这个是什么词汇?
    只听过强行 cast,向下是不可能自动做的
    ninjachen
        13
    ninjachen  
       2019-09-07 21:52:45 +08:00 via Android
    乡下 typo,是向下
    amiwrong123
        14
    amiwrong123  
    OP
       2019-09-07 23:02:45 +08:00 via Android
    @ninjachen
    不好意思,创造了个新词汇 囧 rz
    jxie0755
        15
    jxie0755  
       2019-09-08 09:01:16 +08:00
    泛型和多态好像没有什么很相关的地方? 虽然都是面向对象编程里的概念
    泛型是针对容器做出的设定
    多态是针对继承关系做出的设定
    如果你一定要对几个容器之间做继承关系的话.........那你参考下 Collection 和 Arraylist 之间是怎么个安排的吧?
    lolizeppelin
        16
    lolizeppelin  
       2019-09-08 10:57:58 +08:00
    你先用 python 之类的动态语言写一遍

    再用 java 这样的静态语言写一遍

    然后你就理解为什么静态语言需要泛型这玩意了

    不要死脑筋去理解,功能做出来是为了解决痛点的,你知道为什么痛了,自己然就懂了
    amiwrong123
        17
    amiwrong123  
    OP
       2019-09-08 12:20:55 +08:00
    @Bromine0x23
    大佬,能否再帮忙看下附言 2 的疑问,感谢!
    是不是成员泛型方法的 T 被泛型类的 T 覆盖掉了。
    oneisall8955
        18
    oneisall8955  
       2019-09-08 12:46:14 +08:00 via Android
    我写了个博客,https://liuzhicong.cn/index.php/study/extends-T-and-super-T.html,
    想看精简版本,看借鉴参考的第二篇 stackoverflow 的就行了,私认为解释的很清楚
    amiwrong123
        19
    amiwrong123  
    OP
       2019-09-08 13:25:24 +08:00
    @oneisall8955
    是通配符方面的知识哈,等我看书看到这块再去观摩你的博客把。
    话说层主能否帮看下附言 2,不知道我的理解对吗。。
    Bromine0x23
        20
    Bromine0x23  
       2019-09-08 13:29:44 +08:00   ❤️ 1
    @amiwrong123
    你声明的 gen 是 Generator 而不是 BasicGenerator,当然调用不了 create1 和 test
    amiwrong123
        21
    amiwrong123  
    OP
       2019-09-08 13:55:30 +08:00
    @Bromine0x23
    不好意思,刚才没注意到那个静态方法的返回值。修改静态方法后可执行。

    经过测试发现,虽然成员的泛型方法和泛型类用了同一个标识符 T,但是成员泛型方法的 T 并没有被对象的具体类型 CountedObject 所覆盖,而是独立于对象的。
    guyeu
        22
    guyeu  
       2019-09-08 18:35:08 +08:00
    向下转型在 Java 里是不存在的,只能把一个对象转为对象真正的类型或者它的派生类。
    你可以把泛型理解为一个语法糖,这个语法糖的作用只是做一种类型提示,告诉编译器这个地方可能是什么类型,帮助用户和编译器做类型推断来检查一些错误。
    Justin13
        23
    Justin13  
       2019-09-08 19:39:24 +08:00 via Android
    我的理解是泛型就是多态的一种,泛型函数更关注数据结构(接口)而非具体的数据类型。
    godloveplay
        24
    godloveplay  
       2019-09-25 18:45:20 +08:00
    @guyeu #22 "只能把一个对象转为对象真正的类型或者它的派生类"
    这个派生类是说反了吗?

    class B extend A
    B 是 A 的派生类,A 是 B 的基类,B 由 A 派生出来

    只能把一个对象转为对象真正的类型或者它的基类?
    guyeu
        25
    guyeu  
       2019-09-25 20:17:57 +08:00
    @godloveplay #24 对对对,应该是基类。
    guyeu
        26
    guyeu  
       2019-09-25 20:19:02 +08:00
    @godloveplay #24 对象的类型并不会发生改变,改变的是引用的类型。
    godloveplay
        27
    godloveplay  
       2019-09-25 23:26:23 +08:00
    @guyeu #26 看得我陷入转牛角尖的状态。。
    有个疑问,那这个强转背后到底做了哪些事情,怎么实现的。- -
    ```java Object o = new A(); A a = (A) o```
    guyeu
        28
    guyeu  
       2019-09-26 13:44:01 +08:00
    @godloveplay #27 这个对象本来就是 A,强转也不过是是检查一下对象的类型
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5405 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 09:20 · PVG 17:20 · LAX 01:20 · JFK 04:20
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.