如果函数参数类型相同,为什么将函数调用的两个参数都转换为 Java 是多余的?

Why is it redundant to cast both arguments of a function call in Java if the function parameters are the same type?

我很想知道转换在函数调用中是如何工作的。我的 class 中有两个特定函数的重载,称为 execute。第一个重载是基本实现并接受 double 类型参数。第二个重载接受 int 类型参数。第二个重载也调用基本重载,并通过将参数转换为 double.

来实现

基本实现如下所示:

public double execute(double leftVal, double rightVal) throws IOException {
    ...
    return solution;
}

重载版本如下所示:

public int execute(int leftVal, int rightVal) throws IOException {
    return (int) execute((double) leftVal, (double) rightVal);
}

为什么上面的内容,特别是 (double) leftVal, (double) rightVal 部分是冗余的,为什么它在删除其中一个演员表后仍然有效?无论转换顺序如何,对第一个重载的调用都会起作用。execute(leftVal, (double)rightVal)execute((double) leftVal, rightVal) 都正常执行,不会产生任何错误。我一直认为 Java 对显式标识类型非常严格。我期待一些警告或错误,即不存在接受像 execute(double, int) 这样的调用的函数。不过,我有一个想法,即第一个转换帮助编译器确定选择哪个重载,因此第二个转换是隐式的。也许是因为类型是原始的,很容易转换?还有一个小问题,这两个函数都称为重载吗?还是第一个定义之后的每个定义都是原始的重载?

你可以理解为重载的本质是早期绑定,编译器验证绑定是编译器中合适的函数

删除其中任何一个转换仍然有效,因为在调用上下文中,允许扩大原始转换。所以不,Java 对类型没有你想的那么严格:)

根据 Java 语言规范 §5.3 Invocation Contexts

Invocation contexts allow an argument value in a method or constructor invocation (§8.8.7.1, §15.9, §15.12) to be assigned to a corresponding formal parameter.

Strict invocation contexts allow the use of one of the following:

  • an identity conversion (§5.1.1)

  • a widening primitive conversion (§5.1.2)

  • a widening reference conversion (§5.1.5)

A "widening primitive conversion" 是... (§5.1.2)

19 specific conversions on primitive types are called the widening primitive conversions:

  • byte to short, int, long, float, or double

  • short to int, long, float, or double

  • char to int, long, float, or double

  • int to long, float, or double

  • long to float or double

  • float to double

intdouble 是上述转换之一,因此在您调用方法时是允许的。另一方面,doubleint 是一种缩小原始转换,这在调用上下文中是不允许的。这解释了为什么 (double, double) 方法被 明确地 调用。

Why is the above, specifically the (double) leftVal, (double) rightVal part, a redundancy?

您在上面进行的转换对于明确声明您要调用该基层重载函数是必要的。否则,代码将假设您想要在同一方法中递归。所以是的,你说得对 "the first cast helps the compiler determine which overload to choose"

So the second cast is implicit. Maybe because the types are primitive easily cast-able?

因此,如果您要从需要较少位数的原始类型(int 是 32 位)到容纳更多位数的原始类型(double 是 64 位)位),Java 知道在 运行 时执行此操作。但是,如果您从 double(64 位)转到 int(32 位),作为开发人员,您 需要显式转换,因为您可以 运行此处信息丢失。这就是为什么在 return 上需要进行 (int) 强制转换的原因;您将从 double 变为 int,这可能是有损的。

一些示例代码:

public class Test {

    public int intMethod(int intArg) {
        return intArg;
    }

    public long longMethod(long longArg) {
        // will NOT compile, since we didn't explicitly cast to narrower int type (potentially lossy)
        return intMethod(longArg);
    }
}
public class Test {

    public int intMethod(int intArg) {
        return intArg;
    }

    public long longMethod(long longArg) {
        // WILL compile, since we explicitly cast to narrower int type. Also note we do not need to cast result, since result is going from int --> long, which is not lossy
        return intMethod((int) longArg); 
    }
}
public class Test {

    public int intMethod(int intArg) {
        // WILL compile. don't need to specify arg since its not lossy, but we do specify return type since its lossy
        return (int) longMethod(intArg);
    }

    public long longMethod(long longArg) {
        return 3L;
    }
}

I have always thought Java was very strict on explicitly identifying types

原语是 Java 语言的一个更微妙的特性。我认为按类型,您隐含地指的是 Java 中的 Object。你是对的,Java 在对象转换方面要严格得多。如果我们采用上面的示例并使用 LongInteger 对象,它们将不会编译,并且会标记警告 Cannot case java.lang.Long to java.lang.Integer;

由于无效转换而无法编译的示例

public class Test {

    public Integer intMethod(Integer intArg) {
        return intArg;
    }

    public Long longMethod(Long longArg) {
        return intMethod((Integer) longArg);
    }
}
public class Test {

    public Integer intMethod(Integer intArg) {
        return longMethod((Long) intArg);
    }

    public Long longMethod(Long longArg) {
        return 3L;
    }
}

ps:引用所有具有相同方法名称的函数作为重载是标准的;将一种方法称为 "base" 并说其余方法重载该方法并不常见