为什么这个绑定的泛型方法返回任何类型? [英] Why can this generic method with a bound return any type?

查看:91
本文介绍了为什么这个绑定的泛型方法返回任何类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为什么下面的代码会被编译?
方法 IElement.getX(String)返回类型为 IElement 或其子类的实例。 Main 类中的代码调用 getX(String)方法。编译器允许将返回值存储到 Integer 类型的变量中(显然它不在 IElement

  public interface IElement extends CharSequence {
< T extends IElement> T getX(字符串值);
}

public class Main {
public void example(IElement element){
Integer x = element.getX(x);


不应该返回类型仍然是一个 IElement 的实例 - 甚至在类型擦除之后?



字符代码 getX(String)方法是:

  public abstract< T extends IElement> T getX(java.lang.String); 
flags:ACC_PUBLIC,ACC_ABSTRACT
签名:#7 //< T :: LIElement;>(Ljava / lang / String;)TT;

编辑:替换字符串一致与整数

解决方案

类型推断*。



我们可以将其缩小到以下示例( Ideone ):

  interface Foo {
< F扩展Foo> F bar();

public static void main(String [] args){
Foo foo = null;
String baz = foo.bar();




$ b

允许编译器推断一个(无意义的,真的)交集类型字符串 &  Foo 因为 Foo 是一个接口。对于这个问题中的例子,推断出 Integer&  IElement 这是无意义的,因为转换是不可能的。我们不能自己做这样的转换:

  //不会编译,因为整数是最终的
整数x =(Integer& IElement)元素;






类型推断基本适用于:




  • 每个方法的类型参数的一组推断变量
  • a必须符合
  • 边界集。有时约束约束


在算法结束时,每个变量都根据约束集解析为交集类型,如果它们是有效的,则调用编译。



该过程以 8.1.3


当推理开始时,一个绑定集合通常是从一个类型参数声明列表中产生的 P 1 ,  ...,  P p 和相关的推理变量α 1 ,  ..., α p / code>。这样的绑定集合构造如下。对于每个 l(1  l ≤  p)


  • [否则,对于由&分隔的每个类型 T TypeBound ,绑定α 1  <:  T [P 1 : =α 1 ,  ...,  P p :=α p ] 出现在设置[…]。



  • F <:  Foo (这意味着 F Foo )。



    移至 18.5.2 ,将考虑返回目标类型:



    < blockquote>

    如果调用是一个poly表达式,[…] let R 是返回类型 m ,让 T 为调用的目标类型,然后:


  • [hellip;]


  • 否则,约束公式 会被减少,并与[bound set]合并。



  • 约束公式 被减少到 R θ <:  T 的另一个界限,所以我们有 F <:  String



    稍后,根据 18.4
    $ b


    [… ]为每个α i T i c>:


    • 否则,其中α i 具有适当的上限 U 1 ,  ...,  U k T i   =  glb(U 1 ,  ...,  U



    界限α 1   =  T 1 ,  .. ...,         n< / sub>< / code&

    回想一下,我们的一组边界是 F <:  Foo,  F <:  String glb(String,  Foo)被定义为字符串 &  Foo 。这显然是 glb ,它只需要:


    如果对任何两个类都是编译时错误 not interfaces V i / sub> V i 不是 V j ,反之亦然。


    最后:


    如果解析成功实例化 T 1 ,  ...,  T p 为推理变量α 1 ,  ..., α p ,让θ'代替 [P 1 := T 1 ,  ;. ..,&NBSP; p <子> p := T <子> p ] 。然后:


    • 如果未检查转换对于该方法的适用而言不是必需的,则调用类型 m 是通过将θ'应用于 m 类型获得的。

    • $ b

    因此,该方法使用 String &  Foo 作为 F 的类型。我们当然可以把它分配给一个 String ,因此不可能将 Foo 转换为一个字符串。


    $ b String / Integer code>是最终的类显然没有考虑。






    *注意:输入擦除 is /与这个问题完全无关。

    此外,虽然这也编译在Java 7上,但我认为这是合理的说我们不需要担心那里的规格。 Java 7的类型推断实质上是Java 8的一个不太复杂的版本。它编译的原因类似。




    作为一个附录,虽然很奇怪,但这可能永远不会导致问题那还不存在。编写一个返回类型完全从返回目标中推断出来的泛型方法是很有用的,因为只有 null 可以从这种方法返回而不需要转换。



    假设我们有一些存储特定接口的子类型的地图模拟:

     接口FooImplMap {
    void put(String key,Foo value);
    < F扩展Foo> F get(String key);
    }

    class Bar implements Foo {}
    class Biz implements Foo {}

    出现以下错误已经非常合适:

      FooImplMap m = .. 。 
    m.put(b,new Bar());
    Biz b = m.get(b); //将Bar转换为Biz

    所以我们也可以也可以做的事实 Integer i = m.get(b); 不是新的错误。如果我们是这样编程的代码,它已经可能不合适开始。



    一般来说,一个类型参数只能从目标类型推断出来,如果没有限制它的理由,例如 Collections.emptyList() Optional.empty()

      private static final可选<?> EMPTY = new可选<>(); 

    public static< T>可选< T> empty(){
    @SuppressWarnings(unchecked)
    可选< T> t =(可选的< T>)EMPTY;
    return t;



    $ b

    因为 Optional.empty( )既不能产生也不会消耗 T


    Why does the following code compile? The method IElement.getX(String) returns an instance of the type IElement or of subclasses thereof. The code in the Main class invokes the getX(String) method. The compiler allows to store the return value to a variable of the type Integer (which obviously is not in the hierarchy of IElement).

    public interface IElement extends CharSequence {
      <T extends IElement> T getX(String value);
    }
    
    public class Main {
      public void example(IElement element) {
        Integer x = element.getX("x");
      }
    }
    

    Shouldn't the return type still be an instance of IElement - even after the type erasure?

    The bytecode of the getX(String) method is:

    public abstract <T extends IElement> T getX(java.lang.String);
    flags: ACC_PUBLIC, ACC_ABSTRACT
    Signature: #7                           // <T::LIElement;>(Ljava/lang/String;)TT;
    

    Edit: Replaced String consistently with Integer.

    解决方案

    This is actually a legitimate type inference*.

    We can reduce this to the following example (Ideone):

    interface Foo {
        <F extends Foo> F bar();
    
        public static void main(String[] args) {
            Foo foo = null;
            String baz = foo.bar();
        }
    }
    

    The compiler is allowed to infer a (nonsensical, really) intersection type String & Foo because Foo is an interface. For the example in the question, Integer & IElement is inferred.

    It's nonsensical because the conversion is impossible. We can't do such a cast ourselves:

    // won't compile because Integer is final
    Integer x = (Integer & IElement) element;
    


    Type inference basically works with:

    • a set of inference variables for each of a method's type parameters.
    • a set of bounds that must be conformed to.
    • sometimes constraints, which are reduced to bounds.

    At the end of the algorithm, each variable is resolved to an intersection type based on the bound set, and if they're valid, the invocation compiles.

    The process begins in 8.1.3:

    When inference begins, a bound set is typically generated from a list of type parameter declarations P1, ..., Pp and associated inference variables α1, ..., αp. Such a bound set is constructed as follows. For each l (1 ≤ l ≤ p):

    • […]

    • Otherwise, for each type T delimited by & in a TypeBound, the bound αl <: T[P1:=α1, ..., Pp:=αp] appears in the set […].

    So, this means first the compiler starts with a bound of F <: Foo (which means F is a subtype of Foo).

    Moving to 18.5.2, the return target type gets considered:

    If the invocation is a poly expression, […] let R be the return type of m, let T be the invocation's target type, and then:

    • […]

    • Otherwise, the constraint formula ‹R θ → T› is reduced and incorporated with [the bound set].

    The constraint formula ‹R θ → T› gets reduced to another bound of R θ <: T, so we have F <: String.

    Later on these get resolved according to 18.4:

    […] a candidate instantiation Ti is defined for each αi:

    • Otherwise, where αi has proper upper bounds U1, ..., Uk, Ti = glb(U1, ..., Uk).

    The bounds α1 = T1, ..., αn = Tn are incorporated with the current bound set.

    Recall that our set of bounds is F <: Foo, F <: String. glb(String, Foo) is defined as String & Foo. This is apparently a legitimate type for glb, which only requires that:

    It is a compile-time error if, for any two classes (not interfaces) Vi and Vj, Vi is not a subclass of Vj or vice versa.

    Finally:

    If resolution succeeds with instantiations T1, ..., Tp for inference variables α1, ..., αp, let θ' be the substitution [P1:=T1, ..., Pp:=Tp]. Then:

    • If unchecked conversion was not necessary for the method to be applicable, then the invocation type of m is obtained by applying θ' to the type of m.

    The method is therefore invoked with String & Foo as the type of F. We can of course assign this to a String, thus impossibly converting a Foo to a String.

    The fact that String/Integer are final classes is apparently not considered.


    * Note: type erasure is/was completely unrelated to the issue.

    Also, while this compiles on Java 7 as well, I think it's reasonable to say we needn't worry about the specification there. Java 7's type inference was essentially a less sophisticated version of Java 8's. It compiles for similar reasons.


    As an addendum, while strange, this will likely never cause a problem that was not already present. It's rarely useful to write a generic method whose return type is solely inferred from the return target, because only null can be returned from such a method without casting.

    Suppose for example we have some map analog which stores subtypes of a particular interface:

    interface FooImplMap {
        void put(String key, Foo value);
        <F extends Foo> F get(String key);
    }
    
    class Bar implements Foo {}
    class Biz implements Foo {}
    

    It's already perfectly valid to make an error such as the following:

    FooImplMap m = ...;
    m.put("b", new Bar());
    Biz b = m.get("b"); // casting Bar to Biz
    

    So the fact that we can also do Integer i = m.get("b"); is not a new possibility for error. If we were programming code like this, it was already potentially unsound to begin with.

    Generally, a type parameter should only be solely inferred from the target type if there is no reason to bound it, e.g. Collections.emptyList() and Optional.empty():

    private static final Optional<?> EMPTY = new Optional<>();
    
    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }
    

    This is A-OK because Optional.empty() can neither produce nor consume a T.

    这篇关于为什么这个绑定的泛型方法返回任何类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆