简体   繁体   English

Java 在通过通用包装器调用时没有选择最具体的方法?

[英]Java not choosing the most specific method when called through a generic wrapper?

According to this https://docs.oracle.com/javase/specs/jls/se18/html/jls-15.html#jls-15.12.2.5 , the Java compiler will attempt to choose the most specific method to invoke when multiple applicable and accessible ones are available.根据这个https://docs.oracle.com/javase/specs/jls/se18/html/jls-15.html#jls-15.12.2.5 ,Java 编译器将尝试选择最具体的方法来调用多个适用和可访问的可用。 The intuition is that more specific method can be substituted by less specific ones but not vice versa.直觉是更具体的方法可以用不太具体的方法代替,反之亦然。

So I'm a bit surprised that this won't work when we wrap the ambiguous call within a generic wrapper as follows:所以我有点惊讶,当我们将不明确的调用包装在通用包装器中时,这将不起作用,如下所示:

public class Test{
        static <T> void direct(T t) { System.out.println("generic");}
        static void direct(int t) { System.out.println("specific-int");}

        static <T> void indirect(T t) { direct(t);}
        
        public static void main ( String [] args ) {
            direct(1);    // print specific-int
            indirect(1);  // print generic
        }
     }

So we can see that just calling direct makes it works as expected.所以我们可以看到direct调用就可以按预期工作。 But when calling indirect , the less specific method is called instead.但是当调用indirect时,会调用不太具体的方法。

The behavior changes if I change the type of the direct method as such如果我这样更改直接方法的类型,行为就会改变

    static void direct(short t) { System.out.println("specific-short");}

In this case both lines print generic instead (the short method isn't called in either case), which tells me that the literal 1 was implicitly casted to int in the first instance.在这种情况下,两行都打印generic (在任何一种情况下都不会调用short方法),这告诉我文字1在第一个实例中被隐式转换为int If that's the case, why wasn't it called with the more specific method that takes in int as an argument?如果是这样,为什么不使用将int作为参数的更具体的方法调用它呢?

2 unrelated reasons, both of which lead to the generic version: 2个不相关的原因,这两个原因都导致了通用版本:

Boxing拳击

type parameters are neccessarily subtypes of Object (for now, Project Valhalla is a java update in the pipeline that may change this somewhat).类型参数必然是 Object 的子类型(目前,Valhalla 项目是管道中的 java 更新,可能会对此有所改变)。

Hence, the direct(t) call in your indirect method cannot possibly treat direct(int) as the most specific version of it, as the t parameter in indirect cannot possibly be an int .因此, indirect方法中的direct(t)调用不可能将direct(int)视为它的最具体版本,因为indirect中的t参数不可能是int

Specifically, when you invoke indirect(1) , that 1 is an int which isn't a valid value for indirect (as indirect 's parameter is of type T ; T is a type parameter with as lower bound java.lang.Object , and 1 isn't a valid value for Object ).具体来说,当您调用indirect(1)时, 1 是一个int ,它不是indirect indirect参数类型为T ; T 是一个类型参数,其下限为java.lang.Object ,并且1不是Object的有效值)。

However, java also has the concept of 'boxing', where primitives will be converted to their boxed type automatically, but only if the code wouldn't have compiled otherwise.然而,java有“装箱”的概念,其中基元将自动转换为其装箱类型,但前提是代码不会以其他方式编译。 You can see this with javap - you'll notice that the compiler replaced 1 with Integer.valueOf(1) to make it work.您可以使用javap看到这一点 - 您会注意到编译器将1替换为Integer.valueOf(1)以使其工作。

Names are compile-time名称是编译时的

Which method is chosen is locked in at compile time .选择哪种方法是在编译时锁定的 Note that overrides (where a subtype implements the exact same method, ie if that method is annotated with @Override , the compiler will accept it) are entirely a runtime thing and java always picks the implementation from the subtype, but static doesn't "do" subtyping, so it's not relevant here.请注意,覆盖(子类型实现完全相同的方法,即如果该方法用@Override注释,编译器将接受它)完全是运行时的事情,java 总是从子类型中选择实现,但static不会“做”子类型,所以这里不相关。

The 2 direct methods you have are not the same;您拥有的两种direct方法不一样; at the JVM level, they have completely different names, so this is just about which of the 2 direct methods is picked by javac , hence, compile time is the only time that matters here.在 JVM 级别,它们具有完全不同的名称,因此这只是javac选择的 2 个direct方法中的哪一个,因此,编译时间是这里唯一重要的时间。

Let me make that clear: The 2 direct methods do not have the same name, therefore at runtime the JVM does not have the freedom to pick one or the other depending on types.让我说清楚: 2 个direct方法没有相同的名称,因此在运行时 JVM不能根据类型自由选择一个或另一个。 For the same reason it can't replace a call to foo() with a call to bar() - not the same name .出于同样的原因,它不能用对bar()的调用替换对foo()的调用 - not the same name

indirect , the method, has no idea what T is. indirect方法不知道 T 是什么。 Therefore it could not possibly call direct(int) even if the boxing thing wasn't the case!因此它不可能调用direct(int)即使装箱不是这样! - looking up which of the two direct methods to pick is not done at runtime. - 查找要选择的两种direct方法中的哪一种不是在运行时完成的。

To reiterate重申一下

Given:鉴于:

public class Parent {
  void foo(int i) { System.out.println("Parent-int"); }
  void foo(Integer i) { System.out.println("Parent-Integer"); }
}

class Child extends Parent {
    void foo(int i) { System.out.println("Child-int"); }
}

...

Parent p = new Child();
p.foo(5); // prints Child-int
p.foo(Integer.valueOf(5)); // prints Parent-Integer

a final note最后一点

You wrote in a comment:你在评论中写道:

"which tells me that the literal 1 was implicitly casted to int in the first instance" “这告诉我文字 1 在第一个实例中被隐式转换为 int”

No. integer literals in java are int - the java lang spec defines them as such. java 中的 integer 文字int - java 语言规范将它们定义为 int。 You can write short x = 5;你可以写short x = 5; only because of a special exemption rule that states you don't need the cast there, but languagewise, all non-decimal-pointed (and non 0x0p form) numeric AST nodes are treated as int .只是因为一个特殊的豁免规则声明你不需要在那里进行强制转换,但在语言方面,所有非小数点(和非0x0p形式)数字 AST 节点都被视为int They are THEN implicitly casted if eg a long is needed.如果需要long ,它们将被隐式转换。

You can ask javac to treat things as long by sticking a capital L at the end.您可以通过在末尾添加大写字母 L 来要求 javac 将事物视为 long 。 Given:鉴于:

void foo(byte i) {System.out.println("byte");
void foo(short i) {System.out.println("short");
void foo(int i) {System.out.println("int");
void foo(long i) {System.out.println("long");

foo(5); // prints 'int'
foo(5L); // prints 'long'

Even though '5' fits in 'byte', it is an int, and hence the int variant is chosen.尽管 '5' 适合 'byte',但它是一个 int,因此选择了int变体。

why wasn't it called with the more specific method that takes in int as an argument?为什么不使用以 int 作为参数的更具体的方法调用它?

The Java compiler decides which method to invoke at compile time. Java 编译器决定在编译时调用哪个方法。 That is, for the indirect method, it chooses an overload of direct which can be safely invoked for all invocations of indirect .也就是说,对于indirect方法,它选择direct的重载,可以为indirect的所有调用安全地调用它。

The only such overload is the direct(T) method: this accepts any Object parameter, as does indirect(T) .唯一这样的重载是direct(T)方法:它接受任何Object参数, indirect(T)也是如此。 It can't invoke direct(int) because not all Object s are Integer s.它不能调用direct(int)因为并非所有Object都是Integer s。

indirect doesn't do anything different when invoked with an int parameter.当使用int参数调用时, indirect没有做任何不同的事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM