W jaki sposób wybierana jest metoda przeciążona, gdy parametr jest literalną wartością null?

98

Natknąłem się na to pytanie w quizie,

public class MoneyCalc {

   public void method(Object o) {
      System.out.println("Object Verion");
   }

   public void method(String s) {
      System.out.println("String Version");
   }

   public static void main(String args[]) {
      MoneyCalc question = new MoneyCalc();
      question.method(null);
   }
}

Dane wyjściowe tego programu to „Wersja ciągu”. Ale nie byłem w stanie zrozumieć, dlaczego przekazanie wartości null do przeciążonej metody wybrało wersję łańcuchową. Czy null jest zmienną typu String wskazującą na nic?

Jednak gdy kod zostanie zmieniony na,

public class MoneyCalc {

   public void method(StringBuffer sb) {
      System.out.println("StringBuffer Verion");
   }

   public void method(String s) {
      System.out.println("String Version");
   }

   public static void main(String args[]) {
      MoneyCalc question = new MoneyCalc();
      question.method(null);
   }
}

zwraca błąd kompilacji, mówiąc: „Metoda metody (StringBuffer) jest niejednoznaczna dla typu MoneyCalc”

zakSyed
źródło
Możesz przypisać ciąg do wartości null, aby był poprawny, a kolejność w Javie i większości języków programowania była dopasowana do najbliższego typu, a następnie do obiektu.
JonH
6
Najwyraźniej zostało to zamknięte jako duplikat przez ludzi, którzy przeczytali tylko tytuł. Właściwe pytanie dotyczy tego, dlaczego wybrano określone przeciążenie, a nie „co jest zerowe”.
interjay

Odpowiedzi:

102

Czy null jest zmienną typu String wskazującą na nic?

Odwołanie o wartości null można przekonwertować na wyrażenie dowolnego typu klasy. Więc w przypadku Stringjest to w porządku:

String x = null;

StringPrzeciążenie tutaj jest wybrany, ponieważ kompilator Javy wybiera najbardziej szczegółowych przeciążenia, jak na odcinku 15.12.2.5 z JLS . W szczególności:

Nieformalna intuicja jest taka, że ​​jedna metoda jest bardziej szczegółowa niż inna, jeśli jakiekolwiek wywołanie obsługiwane przez pierwszą metodę może zostać przekazane do drugiej bez błędu typu w czasie kompilacji.

W drugim przypadku obie metody nadal mają zastosowanie, ale żadna z nich Stringnie StringBufferjest bardziej szczegółowa od drugiej, dlatego żadna metoda nie jest bardziej szczegółowa niż druga, stąd błąd kompilatora.

Jon Skeet
źródło
3
A w jaki sposób przeciążenie String jest bardziej szczegółowe niż przeciążenie Object?
user1610015
12
Ponieważ an Objectmoże przyjąć dowolny typ i zawinąć go w plik Object, podczas gdy Stringmoże wziąć tylko plik String. W tym przypadku Stringjest bardziej konkretnym typem w porównaniu z Objecttypem.
JonH
10
@zakSyed Gdyby zapytano Cię, co jest bardziej wyspecjalizowanym „ciągiem” lub „obiektem”, co byś powiedział? Najwyraźniej „String”, prawda? Gdybyś zapytał: co jest bardziej wyspecjalizowanym „String” lub „StringBuffer”? Nie ma odpowiedzi, obie to specjalizacje ortogonalne, jak wybrać między nimi? Następnie musisz jasno określić, który z nich chcesz (np. question.method((String)null)
Przesyłając
1
@JonSkeet: To ma sens. Więc w zasadzie szuka metody zgodnie z najbardziej szczegółową regułą i jeśli nie jest w stanie zdecydować, która jest bardziej szczegółowa, zgłosi błąd w czasie kompilacji.
zakSyed
3
@JonH „null” jest typem referencyjnym, jeśli jedna z metod otrzyma jako parametr typ pierwotny (tj. Int), kompilator nie będzie nawet uwzględniał tego podczas wybierania właściwej metody wywołania dla odwołania typu null. Jest to mylące, jeśli przez "Int" masz na myśli java.lang.Integer lub jeśli miałeś na myśli typ pierwotny int.
Edwin Dalorzo
9

Ponadto JLS 3.10.7 deklaruje również, że „null” jest dosłowną wartością „typu null”. Dlatego istnieje typ zwany „null”.

Później JLS 4.1 stwierdza, że ​​istnieje typ zerowy, którego nie można zadeklarować zmiennych, ale można go używać tylko poprzez literał zerowy. Później mówi:

Odwołanie zerowe może zawsze podlegać rozszerzającej konwersji odwołania do dowolnego typu odwołania.

Dlaczego kompilator zdecydował się rozszerzyć ją do String, można dobrze wyjaśnić w odpowiedzi Jona .

Edwin Dalorzo
źródło
Jestem całkiem pewien, że ten typ to Pustka.
xavierm02
2
@ xavierm02 Nie ma o tym wzmianki w specyfikacji języka Java. Czy możesz zacytować swoje referencje, abyśmy wszyscy mogli zweryfikować Twoje roszczenie?
Edwin Dalorzo
Nigdy tego nie czytałem. Ale zrobiłem tego lata trochę w Javie i możesz przeciążać metodę pobierającą Object metodą pobierającą obiekt Void.
xavierm02
To bardziej klasa niż typ.
xavierm02
4
@ xavierm02 Oczywiście, że możesz. Możesz przeciążać metodę w Javie dowolnym innym typem, który chcesz. To jednak nie ma nic wspólnego z pytaniem ani moją odpowiedzią.
Edwin Dalorzo
2

Możesz przypisać a stringdo nullwartości, aby była poprawna, a kolejność dla języka Java i większości języków programowania była dopasowana do najbliższego typu, a następnie do obiektu.

JonH
źródło
2

Aby odpowiedzieć na pytanie w tytule: nullnie jest ani a, Stringani an Object, ale można przypisać odniesienie do jednego z nich null.

Właściwie jestem zaskoczony, że ten kod nawet się kompiluje. Próbowałem wcześniej czegoś podobnego i otrzymałem błąd kompilatora z informacją, że wywołanie jest niejednoznaczne.

Jednak w tym przypadku wydaje się, że kompilator wybiera metodę znajdującą się najniżej w łańcuchu pokarmowym. Zakłada się, że potrzebujesz najmniej ogólnej wersji metody, aby ci pomóc.

Muszę sprawdzić, czy uda mi się wykopać przykład, w którym wystąpił błąd kompilatora w tym (pozornie) dokładnie tym samym scenariuszu ...]

EDYCJA: Rozumiem. W stworzonej przeze mnie wersji miałem dwie przeciążone metody akceptujące a Stringi an Integer. W tym scenariuszu nie ma parametru „najbardziej szczegółowego” (jak w Objecti String), więc nie można wybrać między nimi, inaczej niż w kodzie.

Bardzo fajne pytanie!

asteri
źródło
null to żadne z nich, ale można je przypisać do obu, to jest różnica i skompiluje się, dlaczego by tego nie zrobił - to absolutnie poprawny kod.
JonH
0

Ponieważ typ String jest bardziej szczegółowy niż typ obiektu. Powiedzmy, że dodajesz jeszcze jedną metodę, która przyjmuje typ Integer.

public void method(Integer i) {
      System.out.println("Integer Version");
   }

Wtedy pojawi się błąd kompilatora mówiący, że wywołanie jest niejednoznaczne. Jak teraz mamy dwie równie specyficzne metody z tym samym priorytetem.

BSingh
źródło
0

Kompilator Java daje większość typów klas pochodnych do przypisania wartości null.

Oto przykład, aby to zrozumieć:

class A{

    public void methodA(){
        System.out.println("Hello methodA");
    }
}

class B extends A{
    public void methodB(){
        System.out.println("Hello methodB");
    }
}

class C{
    public void methodC(){
        System.out.println("Hello methodC");
    }
}

public class MyTest {

     public static void fun(B Obj){
         System.out.println("B Class.");
     }
     public static void fun(A Obj){
         System.out.println("A Class.");
     }

    public static void main(String[] args) {
        fun(null);
    }
}

wyjście: klasa B.

z drugiej strony:

public class MyTest {

     public static void fun(C Obj){
         System.out.println("B Class.");
     }
     public static void fun(A Obj){
         System.out.println("A Class.");
     }

    public static void main(String[] args) {
        fun(null);
    }
}

Wynik: metoda fun (C) jest niejednoznaczna dla typu MyTest

Mam nadzieję, że pomoże to lepiej zrozumieć ten przypadek.

Ravi
źródło
0

Źródło: https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.5
Koncepcja: Najbardziej szczegółowa metoda
Wyjaśnienie: Jeśli więcej niż jedna metoda składowa jest jednocześnie dostępna i ma zastosowanie do wywołania metody, konieczne jest wybranie takiej, która zapewni deskryptor dla wywołania metody w czasie wykonywania. W języku programowania Java obowiązuje zasada, że ​​wybierana jest najbardziej szczegółowa metoda. Spróbuj rzutować wartość null na określony typ, a żądana metoda zostanie automatycznie wywołana.

poseł
źródło
W pierwszym przykładzie, String rozszerza Object, więc „najbardziej specyficzny” to metoda podejmowania String, ale w drugim przykładzie, String i StringBuffer zarówno przedłużyć obiektu, więc są równie specyficzny i dlatego kompilator nie może dokonać wyboru
David Kerr
-1

Ja bym nie powiedział. NULL to stan, a nie wartość. Sprawdź ten link, aby uzyskać więcej informacji na ten temat (artykuł dotyczy SQL, ale myślę, że pomoże to również w rozwiązaniu Twojego pytania).

biegun północny
źródło
nulljest zdecydowanie wartością, zgodnie z definicją zawartą w JLS.
Sotirios Delimanolis