当前位置: 首页 > 知识库问答 >
问题:

Java通过泛型包装器调用时不选择最特定的方法吗?

马祺
2023-03-14

根据这一https://docs.oracle.com/javase/specs/jls/se18/html/jls-15.html#jls-15.12.2.5,Java编译器将尝试选择最具体的方法来调用时,有多个适用的和可访问的,直觉是更具体的方法可以取代较不具体的,但不是相反。

所以我有点惊讶,当我们在通用包装器中包装模糊调用时,这不会起作用,如下所示:

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
        }
     }

所以我们可以看到,只要调用direct就可以让它按预期工作。但是当调用间接时,会调用不太具体的方法。

如果我改变直接方法的类型,行为就会改变

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

在这种情况下,两行都打印generic(两种情况下都不调用short方法),这告诉我,literal1在第一个实例中被隐式转换为int。如果是这样,为什么不使用更具体的方法调用它,将int作为参数?

共有2个答案

轩辕越泽
2023-03-14

为什么不使用更具体的方法调用它,将int作为参数?

Java编译器决定在编译时调用哪个方法。也就是说,对于indirect方法,它选择一个重载direct,该重载可以安全地用于indirect的所有调用。

唯一的重载是Direct(T)方法:它接受任何Object参数,就像间接(T)一样。它不能调用Direct(int),因为并非所有Object都是intgers。

间接在使用int参数调用时没有任何不同。

颜君浩
2023-03-14

2个不相关的原因,这两个原因都导致了通用版本:

类型参数必须是对象的子类型(目前,Project Valhalla是一个正在进行的java更新,可能会有所改变)。

因此,您的间接方法中的直接(t)调用不可能将直接(int)作为最特定的版本,因为间接方法中的参数不可能是int。

具体来说,当您调用indirect(1)时,1是一个int,它不是indirect的有效值(asindirect的参数类型为t;t是一个具有下限java.lang.Object的类型参数,1不是Object的有效值)。

然而,java也有“装箱”的概念,即原语将自动转换为装箱类型,但前提是代码不会以其他方式编译。您可以在javap中看到这一点——您会注意到编译器将1替换为整数。valueOf(1)使其工作。

选择的方法在编译时被锁定。请注意,重写(子类型实现完全相同的方法,即,如果该方法用@Override注释,编译器将接受它)完全是运行时的事情,java总是从子类型中选择实现,但静态不“执行”子类型,因此在这里不相关。

你拥有的两种direct方法不一样;在JVM级别,它们有完全不同的名称,所以这只是关于javac选择了2个direct方法中的哪一个,因此编译时间是这里唯一重要的时间。

让我明确一点:2direct方法没有相同的名称,因此在运行时,JVM没有根据类型选择其中一种方法的自由。出于同样的原因,它不能将对foo()的调用替换为对bar()的调用,而不是相同的名称。

间接法不知道T是什么。因此,它不可能调用direct(int),即使装箱的事情不是这样查找要选择的两个direct方法中的哪一个不是在运行时完成的。

鉴于:

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

你在评论中写道:

“这告诉我,文本1在第一个实例中被隐式转换为int”

java中的整数字面值是int-java lang规范这样定义它们。您可以编写短x=5;,这仅仅是因为有一个特殊的豁免规则,声明您不需要强制转换,但是在语言方面,所有非十进制指向(和非0x0p形式)的数字AST节点都被视为int。如果需要long,它们将被隐式强制转换。

你可以让javac在末尾加一个大写字母L,让它把事情处理得一样长。鉴于:

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'

尽管“5”适合“byte”,但它是一个int,因此选择了int变量。

 类似资料:
  • 问题内容: 示例代码为: 输出为: 的第一个调用会调用带有String参数的方法,根据可以理解。 1)谁能解释我在先前的调用中是基于什么来调用的? 2)再说一次,说一个if条件: 它总是使用 调用方法。 编译器在编译时会计算表达式吗?我想知道表达式是在编译时还是在运行时计算的。谢谢。 问题答案: Java使用早期绑定。在编译时选择最具体的方法。根据参数数量和参数类型选择最具体的方法。在这种情况下,

  • 我想在我的android项目中自动转换视图。所以,我想重写 方法 方法但是java编译不允许这样做,但是重写的方法不会与父方法冲突,并且总是返回视图对象或其子对象。我发现了一些信息,java不允许用泛型方法重写非泛型方法,但我找不到解释。 http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ8

  • 我有一个有界泛型类,我们称之为泛型,它的参数T扩展了抽象类abstract: 泛型类: 抽象类 泛型类中T引用的类 当尝试引用方法 getMap() 时,该方法来自 T 边界内的类(并且根据抽象类定义,T 的所有可能实例都将具有该方法),我收到以下错误: 不能从静态上下文引用非静态方法getMap() 然而,任何地方都没有静态关键字。我错过了什么?? 谢谢!

  • 我一直在尝试泛型,很快我就遇到了一些我无法解释的事情 例如: 我不明白

  • 问题内容: 我想知道用这样的签名调用静态方法的正确方法是什么: 由于某种原因,我很想这样称呼它: 但是除非我将其更改为:否则它不会编译: 我只是想知道为什么它不需要右侧的提示。而是给了我编译错误。它说它期望在右侧的提示后加上分号。第二个方法是调用该方法的正确方法吗?有人可以给我一些启示吗? 问题答案: 如图所示这里,要调用的方法的方法是: 该方法所在的类的名称在哪里。

  • 因此,我有一些带有这些签名的Java方法(为了简单起见,删除了注释和代码体): 我在Kotlin中有一些代码,它调用了'join'方法: 例如,如果我想用“分隔符”参数调用后一个方法签名,问题就来了: 这段代码无法编译。编译器无法决定调用哪个方法。错误: 错误:(5,13)Kotlin:在未完成类型推断的情况下,无法在以下候选项中进行选择:public open fun join(vararg A