Does a Method’s Signature Include the Return Type in Java? – 在Java中,一个方法的签名是否包括返回类型?

最后修改: 2020年 9月 22日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

The method signature is only a subset of the entire method definition in Java. Thus, the exact anatomy of the signature may cause confusion.

方法签名只是Java中整个方法定义的一个子集。因此,对签名的确切解剖可能会引起混淆。

In this tutorial, we’ll learn the elements of the method signature and its implications in Java programming.

在本教程中,我们将学习方法签名的要素以及它在Java编程中的意义。

2. Method Signature

2.方法签名

Methods in Java support overloading, meaning that multiple methods with the same name can be defined in the same class or hierarchy of classes. Hence, the compiler must be able to statically bind the method the client code refers to. For this reason, the method signature uniquely identifies each method.

Java中的方法支持重载,这意味着在同一个类或类的层次结构中可以定义多个同名的方法。因此,编译器必须能够静态地绑定客户端代码所引用的方法。出于这个原因,方法签名可以唯一地识别每个方法

According to Oracle, the method signature is comprised of the name and parameter types. Therefore, all the other elements of the method’s declaration, such as modifiers, return type, parameter names, exception list, and body are not part of the signature.

根据Oracle,方法签名是由名称和参数类型组成的。因此,方法声明中的所有其他元素,如修饰语、返回类型、参数名称、异常列表和正文都不是签名的一部分。

Let’s take a closer look at method overloading and how it relates to method signatures.

让我们仔细看看方法重载以及它与方法签名的关系。

3. Overloading Errors

3.重载错误

Let’s consider the following code:

让我们考虑以下代码:

public void print() {
    System.out.println("Signature is: print()");
}

public void print(int parameter) {
    System.out.println("Signature is: print(int)");
}

As we can see, the code compiles as the methods have different parameter type lists. In effect, the compiler can deterministically bind any call to one or the other.

正如我们所看到的,这段代码可以编译,因为这些方法有不同的参数类型列表。实际上,编译器可以确定地将任何调用绑定到一个或另一个。

Now let’s test if it’s legal to overload by adding the following method:

现在让我们通过添加以下方法来测试它的重载是否合法。

public int print() { 
    System.out.println("Signature is: print()"); 
    return 0; 
}

When we compile, we get a “method is already defined in class” error. That proves the method return type is not part of the method signature.

当我们编译时,我们得到一个 “方法已经在类中定义 “的错误。这证明该方法的返回类型不是方法签名的一部分

Let’s try the same with modifiers:

让我们用修饰语试一试。

private final void print() { 
    System.out.println("Signature is: print()");
}

We still see the same “method is already defined in class” error. Therefore, the method signature is not dependent on modifiers.

我们仍然看到同样的 “方法已经在类中定义 “的错误。因此,方法signature不依赖于修改器

Overloading by changing thrown exceptions can be tested by adding:

通过改变抛出的异常来进行重载,可以通过添加。

public void print() throws IllegalStateException { 
    System.out.println("Signature is: print()");
    throw new IllegalStateException();
}

Again we see the “method is already defined in class” error, indicating the throw declaration cannot be part of the signature.

我们再次看到 “方法已经在类中定义 “的错误,表明throw声明不能成为签名的一部分

The last thing we can test is whether changing the parameter names allow overloading. Let’s add the following method:

我们可以测试的最后一件事是改变参数名称是否允许重载。让我们添加以下方法。

public void print(int anotherParameter) { 
    System.out.println("Signature is: print(int)");
}

As expected, we get the same compilation error. This means that parameter names don’t influence the method signature.

正如所料,我们得到了同样的编译错误。这意味着参数名不影响方法签名

3. Generics and Type Erasure

3.泛型和类型清除

With generic parameterstype erasure changes the effective signature. In effect, it may cause a collision with another method that uses the upper bound of the generic type instead of the generic token.

对于泛型参数类型清除改变了有效签名。实际上,它可能会与另一个使用泛型类型的上界而不是泛型标记的方法发生碰撞。

Let’s consider the following code:

让我们考虑一下下面的代码。

public class OverloadingErrors<T extends Serializable> {

    public void printElement(T t) {
        System.out.println("Signature is: printElement(T)");
    }

    public void printElement(Serializable o) {
        System.out.println("Signature is: printElement(Serializable)");
    }
}

Even though the signatures appear different, the compiler cannot statically bind the correct method after type erasure.

即使签名看起来不同,编译器也无法在类型擦除后静态地绑定正确的方法。

We can see the compiler replacing T with the upper bound, Serializable, due to type erasure. Thus, it clashes with the method explicitly using Serializable.

我们可以看到编译器将T替换为上界,Serializable,由于类型擦除。因此,它与明确使用Serializable的方法发生冲突。

We would see the same result with the base type Object when the generic type has no bound.

当泛型没有约束时,我们会看到与基型Object相同的结果。

4. Parameter Lists and Polymorphism

4.参数列表和多态性

The method signature takes into account the exact types. That means we can overload a method whose parameter type is a subclass or superclass.

方法签名考虑到了确切的类型。这意味着我们可以重载一个参数类型为子类或超类的方法。

However, we must pay special attention as static binding will attempt to match using polymorphism, auto-boxing, and type promotion.

然而,我们必须特别注意,因为静态绑定将试图使用多态性、自动装箱和类型推广进行匹配

Let’s take a look at the following code:

让我们看一下下面的代码。

public Number sum(Integer term1, Integer term2) {
    System.out.println("Adding integers");
    return term1 + term2;
}

public Number sum(Number term1, Number term2) {
    System.out.println("Adding numbers");
    return term1.doubleValue() + term2.doubleValue();
}

public Number sum(Object term1, Object term2) {
    System.out.println("Adding objects");
    return term1.hashCode() + term2.hashCode();
}

The code above is perfectly legal and will compile. Confusion may arise when calling these methods as we not only need to know the exact method signature we are calling but also how Java statically binds based on the actual values.

上面的代码是完全合法的,可以编译。在调用这些方法时,可能会出现混乱,因为我们不仅需要知道我们正在调用的确切方法签名,还需要知道Java如何根据实际值进行静态绑定。

Let’s explore a few method calls that end up bound to sum(Integer, Integer):

让我们来探讨几个最终与sum(Integer, Integer)绑定的方法调用。

StaticBinding obj = new StaticBinding(); 
obj.sum(Integer.valueOf(2), Integer.valueOf(3)); 
obj.sum(2, 3); 
obj.sum(2, 0x1);

For the first call, we have the exact parameter types Integer, Integer. On the second call, Java will auto-box int to Integer for us. Lastly, Java will transform the byte value 0x1 to int by means of type promotion and then auto-box it to Integer. 

对于第一次调用,我们有确切的参数类型Integer, Integer。在第二次调用时,Java将自动将int转换为Integer为我们最后,Java将通过类型推广将字节值0x1转换为int,然后将其自动装箱为Integer。

Similarly, we have the following calls that bind to sum(Number, Number):

同样地,我们有以下调用与sum(Number, Number)相绑定。

obj.sum(2.0d, 3.0d);
obj.sum(Float.valueOf(2), Float.valueOf(3));

On the first call, we have double values that get auto-boxed to Double. And then, by means of polymorphism, Double matches Number. Identically, Float matches Number for the second call.

在第一次调用时,我们有double值被自动装箱为Double.,然后,通过多态性,DoubleNumber.相匹配,FloatNumber的第二次调用相同。

Let’s observe the fact that both Float and Double inherit from Number and Object. However, the default binding is to Number. This is due to the fact that Java will automatically match to the nearest super-types that match a method signature.

让我们观察一下,FloatDouble都继承自NumberObject。然而,默认的绑定是与Number。这是由于Java会自动匹配到与方法签名匹配的最近的超类型。

Now let’s consider the following method call:

现在让我们考虑一下下面的方法调用。

obj.sum(2, "John");

In this example, we have an int to Integer auto-box for the first parameter. However, there is no sum(Integer, String) overload for this method name. Consequentially, Java will run through all the parameter super-types to cast from the nearest parent to Object until it finds a match. In this case, it binds to sum(Object, Object). 

在这个例子中,我们有一个intInteger的自动框,用于第一个参数。然而,这个方法名称没有sum(Integer, String)重载。因此,Java将运行所有的参数超类型,从最近的父类到Object进行转换,直到找到一个匹配。在这种情况下,它会绑定到sum(Object, Object)。

To change the default binding, we can use explicit parameter casting as follows:

为了改变默认绑定,我们可以使用显式参数铸造,如下。

obj.sum((Object) 2, (Object) 3);
obj.sum((Number) 2, (Number) 3);

5. Vararg Parameters

5.Vararg参数

Now let’s turn our attention over to how varargs impact the method’s effective signature and static binding.

现在让我们把注意力转移到varargs如何影响方法的有效签名和静态绑定。

Here we have an overloaded method using varargs:

这里我们有一个使用varargs的重载方法。

public Number sum(Object term1, Object term2) {
    System.out.println("Adding objects");
    return term1.hashCode() + term2.hashCode();
}

public Number sum(Object term1, Object... term2) {
    System.out.println("Adding variable arguments: " + term2.length);
    int result = term1.hashCode();
    for (Object o : term2) {
        result += o.hashCode();
    }
    return result;
}

So what are the effective signatures of the methods? We’ve already seen that sum(Object, Object) is the signature for the first. Variable arguments are essentially arrays, so the effective signature for the second after compilation is sum(Object, Object[]). 

那么,这些方法的有效签名是什么?我们已经看到,sum(Object, Object)是第一个方法的签名。变量参数本质上是数组,所以编译后第二个方法的有效签名是sum(Object, Object[])。

A tricky question is how can we choose the method binding when we have just two parameters?

一个棘手的问题是,当我们只有两个参数时,如何选择方法绑定?

Let’s consider the following calls:

让我们考虑一下下面的调用。

obj.sum(new Object(), new Object());
obj.sum(new Object(), new Object(), new Object());
obj.sum(new Object(), new Object[]{new Object()});

Obviously, the first call will bind to sum(Object, Object) and the second to sum(Object, Object[]). To force Java to call the second method with two objects, we must wrap it in an array as in the third call.

很明显,第一次调用将绑定到sum(Object, Object),第二次调用绑定到sum(Object, Object[])。为了迫使Java用两个对象来调用第二个方法,我们必须像第三个调用那样把它包在一个数组中。

The last thing to note here is that declaring the following method will clash with the vararg version:

这里需要注意的最后一点是,声明以下方法会与vararg版本发生冲突。

public Number sum(Object term1, Object[] term2) {
    // ...
}

6. Conclusion

6.结语

In this tutorial, we learned that the method signatures are comprised of the name and the parameter types’ list. The modifiers, return type, parameter names, and exception list cannot differentiate between overloaded methods and, thus, are not part of the signature.

在本教程中,我们了解到方法签名是由名称和参数类型列表组成的。修饰语、返回类型、参数名称和异常列表不能区分重载方法,因此,不是签名的一部分。

We’ve also looked at how type erasure and varargs hide the effective method signature and how we can override Java’s static method binding.

我们还研究了类型擦除和varargs如何隐藏有效的方法签名,以及我们如何覆盖Java的静态方法绑定。

As usual, all the code samples shown in this article are available over on GitHub.

像往常一样,本文中显示的所有代码样本都可以在GitHub上找到