I needed to dig into the specifics of method invocation in Java, and while reading the section Choosing the Most Specific Method in The Java Language Specification (Java SE 12 Edition), I found that (1) during invocation multiple methods can be maximally specific and that (2) having multiple maximally specific methods doesn't always result in a compile-time error.
I was able to think of an example where two methods are both maximally specific:
interface A {}
interface B {}
class C implements A, B {
<T extends A> void foo(T t) {};
<T extends B> void foo(T t) {};
}
class Main {
public static void main(String[] args) {
new C().<C>foo(null);
}
}
This example results in a compile-time error: error: reference to foo is ambiguous
This makes sense to me, but what doesn't make sense to me is when there are multiple maximally specific methods and it doesn't result in a compile-time error.
The section Choosing the Most Specific Method in The Java Language Specification (Java SE 12 Edition) mentions two scenarios where the compiler is able to select a method when there are multiple maximally specific methods:
If all the maximally specific methods have override-equivalent signatures (§8.4.2), and exactly one of the maximally specific methods is concrete (that is, neither abstract nor default), then it is the most specific method.
Otherwise, if all the maximally specific methods have override-equivalent signatures, and all the maximally specific methods are abstract or default, and the declarations of these methods have the same erased parameter types, and at least one maximally specific method is preferred according to the rules below, then the most specific method is chosen arbitrarily among the subset of the maximally specific methods that are preferred. The most specific method is then considered to be abstract.
First, how is it possible to invoke a method that is abstract
? Why would an abstract
method ever be considered for method invocation?
Second, can someone provide an example for each of these two scenarios that don't result in compile-time errors?
I found that (1) during invocation multiple methods can have the same signature and that (2) having multiple methods with the same signature doesn't always result in a compile-time error.
A class can't contain two methods with the same signature.
8.4.2. Method Signature
Two methods or constructors, M and N, have the same signature if they have the same name, the same type parameters (if any) (§8.4.4), and, after adapting the formal parameter types of N to the the type parameters of M, the same formal parameter types.
The signature of a method m1 is a subsignature of the signature of a method m2 if either:
m2 has the same signature as m1, or
the signature of m1 is the same as the erasure (§4.6) of the signature of m2.
Two method signatures m1 and m2 are override-equivalent iff either m1 is a subsignature of m2 or m2 is a subsignature of m1.
It is a compile-time error to declare two methods with override-equivalent signatures in a class.
In your example, there are two methods with two different signatures. It compiles and works fine unless you introduce ambiguity like new C().<C>foo(null);
. The compile-time error "reference to foo is ambiguous" doesn't mean <T extends A> void foo(T t)
and <T extends B> void foo(T t)
can't coexist. They actually can, and do.
As mentioned in the comments, after type erasure, the methods will look like
void foo(A t);
void foo(B t);
How is it possible to invoke a method that is abstract? Why would an abstract method ever be considered for method invocation?
Invoking an abstract method within an abstract context (e.g. inside an abstract class) is absolutely fine.
Can someone provide an example for each of these two scenarios that don't result in compile-time errors?
I can think of an example where there are two "maximally specific methods with override-equivalent signatures" for the invocation new C().foo();
and it's being successfully resolved in favour of A
's method.
abstract class A {
public void foo() {
System.out.println("a");
}
}
interface B {
default void foo() {
System.out.println("b");
}
}
class C extends A implements B {
public static void main(String[] args) {
new C().foo(); // prints a
}
}