Java: выбор между перегруженными конструкторами
Per этот вопрос, Java будет выбирать "наиболее конкретную" опцию при попытке выбора между неоднозначными перегруженными конструкторами. В этом примере:
public class Test{
private Test(Map map){
System.out.println("Map");
}
private Test(Object o){
System.out.println("Object");
}
public static void main(String[] args){
new Test(null);
}
}
он напечатает
"Карта"
Однако я пытался выяснить, что именно означает "наиболее конкретный". Я предполагал, что это означает "наименее двусмысленный", поскольку "может относиться к наименее возможным типам". В этом контексте Object
может быть любым, что не является примитивным, а Map
может быть только Map
или ? extends Map
. В принципе, я предположил, что какой бы класс был ближе к листу дерева наследования. Это работает, когда один класс является подклассом другого:
public class Test{
private Test(A a){
System.out.println("A");
}
private Test(B b){
System.out.println("B");
}
public static void main(String[] args){
new Test(null);
}
}
class A{}
class B extends A{}
"В"
Затем я придумал следующее:
public class Test{
private Test(A a){
System.out.println("A");
}
private Test(E e){
System.out.println("E");
}
public static void main(String[] args){
new Test(null);
}
}
class A{}
class B extends A{}
class C{}
class D extends C{}
class E extends D{}
Я думаю, что он должен печатать E
, так как E
может ссылаться только на один известный тип, тогда как A
может ссылаться на два (A
и B
). Но это дает неоднозначную ошибку ссылки.
Как он на самом деле выбирает конструктор? Я прочитал документы, но, честно говоря, я не мог точно следить за тем, как он определяет специфику. Я надеюсь на точное описание того, почему он не может определить, что E
более конкретный, чем A
.
Ответы
Ответ 1
Он не основан на количестве типов, которые конвертируются в тип параметра - независимо от того, допустимо ли какое-либо значение, допустимое для одной перегрузки, для другого, из-за неявных преобразований.
Например, существует неявное преобразование от String
до Object
, но обратное неверно, поэтому String
более специфично, чем Object
.
Также существует неявное преобразование от B
до A
, но обратное неверно, поэтому B
более специфично, чем A
.
С A
и E
, однако, ни один из них не является более конкретным, чем другой - нет преобразования от A
до E
, и никакого преобразования от E
до A
. Вот почему сбой при перегрузке не работает.
Соответствующий бит JLS фактически 15.12.2.5, который включает в себя это, что может облегчить вам понимание:
Неформальная интуиция заключается в том, что один метод более конкретный, чем другой, если любой вызов, обработанный первым методом, может быть передан другому, без ошибки времени компиляции.
Итак, если у вас есть:
void foo(String x)
void foo(Object x)
каждый вызов, обработанный foo(String)
, может обрабатываться foo(Object)
, но обратное не так. (Например, вы можете вызвать foo(new Object())
, и это невозможно обработать с помощью foo(String)
.)
Ответ 2
Следующее утверждение JSL§15.12.2.5 отвечает, что
Неформальная интуиция заключается в том, что один метод более конкретный, чем другой, если любой вызов, обработанный первым методом, может быть передан на другой, без ошибки времени компиляции.
Случай 1
- Вы можете передать что-либо в конструкторе, который принимает
Object
, в то время как мы не можем передать ничего, кроме Map
в первом конструкторе. Таким образом, любой, что передается в конструкторе Map
, может обрабатываться конструктором Object
, и поэтому Test(Map map)
становится специфичным для mote.
Случай 2
- Так как
B
extends A
, здесь конструктор Test(B b)
становится более конкретным. Поскольку мы можем передать B
в Test(A a)
благодаря наследованию.
Случай 3
- В этом случае нет прямого преобразования, чтобы изобразить более конкретный метод, и это приводит к двусмысленности.
Ответ 3
Это поведение объясняется тем, что E не является более конкретным, чем A, поскольку они принадлежат к разным иерархиям, которые они не могут сравниться. Таким образом, когда вы передаете нуль, Java не может знать, какую иерархию использовать.