【问题标题】:Why is it not possible use primitive types with polymorphic return types?为什么不能使用具有多态返回类型的原始类型?
【发布时间】:2015-09-06 09:42:14
【问题描述】:

考虑以下两个类:

public interface Foo<T>
{
    public T moo();
}

public class IntFoo implements Foo<Integer>
{
    public int moo()
    {
        return 0;
    }
}

此代码将在publicintmoo 产生错误,表示int 与被覆盖方法的返回类型Integer 不兼容。严格来说,这是真的,因为int直接 等于Integer。但是,我们都知道可以使用自动(取消)装箱将它们隐式转换为彼此。鲜为人知的是编译器在这个例子中生成了一个桥接方法:

public class IntFoo implements Foo<Integer>
{
    public <synthetic> <bridge> Object moo()
    {
        return this.moo(); // upcast
    }

    public Integer moo() {
        return 0;
    }
}

必须这样做是因为 JVM 在解析方法时会区分返回类型,并且由于 Foo.moo 的擦除返回类型是 Object,因此编译器生成了一个与方法具有相同签名的桥接方法。

我想知道为什么这也不适用于原始多态返回类型:

public class IntFoo implements Foo<Integer>
{
    public <synthetic> <bridge> Object moo()
    {
        return Integer.valueOf(this.moo());
    }

    public int moo()
    {
        return 0;
    }
}

似乎没有任何理由不拥有此功能:

IntFoo intFoo = new IntFoo();
Foo<Integer> foo = intFoo;
Integer i = foo.moo(); // calls the synthetic method, which boxes the result of the actual implementation

事实上,这个 REPL 会话的屏幕截图表明,我什至能够在我的 custom programming language(编译成 Java 字节码)中实现它:

【问题讨论】:

  • 很高兴知道合成桥接法,那对我来说是新的。

标签: java polymorphism primitive return-type


【解决方案1】:

与这些问题一样,答案是您必须询问语言设计者。我看不出有什么理由不能做到这一点。但是在我看来,这个功能将毫无意义。正如您在问题中指出的那样,只有在对静态类型 IntFoo 的变量调用 moo 时,才会返回原语;在Foo&lt;Integer&gt; 类型的变量上,无论如何都会返回Integer。因此,您可以通过这样做实现基本相同的目标。

public class IntFoo implements Foo<Integer> {

    @Override
    public Integer moo() { return mooAsInt(); }

    public int mooAsInt() { return 0; }
}

我个人认为这更好,因为拳击发生/没有发生时更加明显。在您的提议中,moo() 可能会根据变量的静态类型返回 intInteger,这将非常令人困惑。

【讨论】:

  • 我不认为这个特性毫无意义:一个具体的例子是扩展Set&lt;Integer&gt;BitSet 类。这样,您可以获得 BitSet 的无框高性能,并且在需要通用 Set 时仍然使用 Set 功能。
  • @Clashsoft 我完全同意这一点。我自己也曾主张过类似的事情(请参阅问题“SparseArray vs HashMap”),我认为android的SparseArray&lt;T&gt;应该已经实现Map&lt;Integer, T&gt;。这样它可以在需要时被视为Map,但只处理原始键。那会很棒,但只是你已经可以通过重载方法来做到这一点——不需要新的规则。
  • 但是只能重载参数类型,不能重载返回类型。这使得在您的场景中无法覆盖 Map,这意味着您需要一个额外的包装器来实现 Map 接口并委托给 SpareArray 实例。
  • @Clashsoft 你说得对,我不是说在改变返回类型的情况下重载——方法必须有一个不同的名字。您绝对可以使用SparseArray 使用继承而不是像我所做的那样组合来做到这一点。 public final class SparseMap&lt;T&gt; extends SparseArray&lt;T&gt; implements Map&lt;Integer, T&gt; { @Override public T get(Object i) { return get((int) (Integer) i); } // etc }。我对这一切的主要问题是你必须抛弃总是对接口编程的规则。我期待 Java 9(或者是 10?)当我们可以拥有一个真正的Foo&lt;int&gt;
【解决方案2】:

在我看来,原因纯粹是句法。正如您在问题中所展示的那样,可以在字节码中生成一个返回int 的方法和一个实现Foo 接口方法的方法返回Object

但是,如果你从 Java 语法的角度来看问题(即不是字节码),

public class IntFoo implements Foo<Integer> {
    public int moo() { return 0; }
}

应该覆盖它没有覆盖的方法Integer moo()。正如您自己所说,Java 区分返回类型,int moo()Integer moo() 不一样。

【讨论】:

  • 确实,int moo() 覆盖 T moo()(使用 T = Integer)可能并不明显。但是如果你可以写Foo&lt;int&gt;,事情会更明显一些。猜猜我们将不得不等待 Java 10 :/
【解决方案3】:

原语的问题是它们在堆栈上需要不同数量的空间...与对象引用相反,它们都在堆栈上占用相同数量的空间。而且你不能有一个可变大小的堆栈帧,否则在运行时堆栈将不知道在方法退出时返回到哪里

【讨论】:

  • 在我的示例中,您在哪里需要不同的堆栈大小?如果你直接调用被覆盖的方法,你会从实现中得到一个int,否则桥方法会被调用并为你处理装箱。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-24
  • 2021-11-06
  • 2011-05-07
  • 2014-01-26
  • 1970-01-01
  • 2014-11-03
相关资源
最近更新 更多