Як вибирається перевантажений метод, коли параметр є буквальним нульовим значенням?


98

Я натрапив на це питання у вікторині,

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);
   }
}

Вихід цієї програми - "String Version". Але я не зміг зрозуміти, чому для передачі нуля перевантаженому методу було обрано версію рядка. Чи null змінна String вказує ні на що?

Однак коли код змінено на,

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);
   }
}

він дає помилку компіляції: "Метод методу (StringBuffer) неоднозначний для типу MoneyCalc"


Ви можете призначити рядку нульове значення, щоб воно було дійсним, і порядок для java та більшості мов програмування підходить до найближчого типу, а потім до об'єкта.
JonH


6
Мабуть, це було закрито як копію людьми, які лише читали заголовок. Актуальне питання полягає в тому, чому було обрано конкретне перевантаження, а не "що є нульовим".
interjay

Відповіді:


102

Чи null змінна String вказує ні на що?

Нульова посилання може бути перетворена у вираз будь-якого типу класу. Так що у випадку з Stringцим все нормально:

String x = null;

StringПеревантаження тут обраний тому , що компілятор Java вибирає найбільш конкретну перевантаження, згідно розділу 15.12.2.5 з JLS . Зокрема:

Неофіційна інтуїція полягає в тому, що один метод більш специфічний, ніж інший, якщо будь-яке виклик, оброблений першим методом, може бути передано іншому без помилки типу компіляції.

У другому випадку обидва способи все ще застосовні, але ні вони, Stringні StringBufferконкретніші за інші, тому жоден метод не є більш конкретним, ніж інший, отже, помилка компілятора.


3
І як перевантаження String більш специфічне, ніж перевантаження об'єкта?
користувач1610015

12
Тому що аніме Objectможе взяти будь-який тип і загорнути його в Object, тоді як Stringможе взяти лише a String. У цьому випадку Stringбільш специфічний тип порівняно з Objectтипом.
JonH

10
@zakSyed Якщо б вас запитали, що є більш спеціалізованим "Рядок" або "Об'єкт", що б ви сказали? Очевидно "Рядок", правда? Якщо вас запитали: що є більш спеціалізованим "String" чи "StringBuffer"? Відповіді немає, вони обидві ортогональні спеціалізації, як ви можете вибрати між ними? Тоді ви повинні бути чіткими щодо того, який саме ви хочете (тобто, question.method((String)null)
давши

1
@JonSkeet: Це має сенс. Таким чином, він шукає метод за найбільш конкретним правилом, і якщо він не зможе вирішити, який є більш конкретним, то він би видав помилку часу компіляції.
zakSyed

3
@JonH "null" - це еталонний тип, якщо один із методів отримує примітивний тип як параметр (тобто int), компілятор навіть не буде врахований при виборі правильного методу для виклику посилання типу null. Це заплутано, якщо під "Int" ви мали на увазі java.lang.Integer або якщо ви мали на увазі примітивний тип int.
Едвін Далорцо

9

Крім того, JLS 3.10.7 також заявляє, що "null" - це буквальне значення "null type". Тому існує тип під назвою "null".

Пізніше JLS 4.1 зазначає, що існує нульовий тип, який неможливо оголосити змінними, але ви можете використовувати його лише через нульовий літерал. Пізніше воно говорить:

Нульова посилання завжди може зазнати розширення перетворення посилань на будь-який тип посилання.

Чому компілятор вирішив розширити його на String, цілком можна пояснити у відповіді Джона .


Я повністю впевнений, що тип - це недійсний.
xavierm02

2
@ xavierm02 У специфікації мови Java це не згадується. Чи можете ви навести свою посилання, щоб ми всі могли перевірити вашу претензію?
Едвін Далорцо

Я ніколи цього не читав. Але я зробив деяку Java цього літа, і ви можете перевантажити метод взяття Об'єкта методом, що приймає об’єкт Void.
xavierm02

Це більше класу, ніж типу.
xavierm02

4
@ xavierm02 Звичайно, можна. Ви можете перевантажувати метод на Java тим чи іншим типом, який хочете. Це, тим не менш, не має нічого спільного з питанням чи моєю відповіддю.
Едвін Далорцо

2

Ви можете призначити значення stringa, nullщоб воно було дійсним, і порядок для java та більшості мов програмування підходить до найближчого типу, а потім до об'єкта.


2

Щоб відповісти на запитання в заголовку: nullне є ні Stringані ані Object, але посилання на будь-яке може бути призначене null.

Я насправді здивований, що цей код навіть компілюється. Я спробував щось подібне раніше, і у мене з’явилася помилка компілятора, яка сказала, що дзвінок був неоднозначним.

Однак у цьому випадку здається, що упорядник вибирає метод, який є найнижчим у харчовому ланцюгу. Передбачається, що вам потрібна найменш загальна версія методу, щоб допомогти вам.

Я повинен побачити, чи можу я викопати приклад, коли в цьому (начебто) точно такому ж сценарії помилка компілятора, хоча ...]

EDIT: Я бачу. У версії, яку я зробив, у мене було два перевантажені способи прийняття a Stringі an Integer. У цьому сценарії немає "найбільш специфічного" параметра (як у Objectі String), тому він не може обирати між ними, на відміну від вашого коду.

Дуже класне питання!


null не є, але він може бути призначений обом, тобто різниця, і він буде компілювати, чому б і ні - це абсолютно дійсний код.
JonH

0

Оскільки тип рядка більш специфічний, ніж тип об'єкта. Скажімо, ви додали ще один метод, який приймає тип Integer.

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

Тоді ви отримаєте помилку компілятора, сказавши, що виклик неоднозначний. Як і зараз, ми два однаково специфічні методи з однаковим пріоритетом.


0

Компілятор Java надає найбільш похідному типу класу, щоб присвоїти null.

Ось приклад, щоб зрозуміти це:

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);
    }
}

вихід: B клас.

з іншої сторони:

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);
    }
}

Результат: Метод fun (C) неоднозначний для типу MyTest

Сподіваюся, це допоможе зрозуміти цю справу краще.


0

Джерело: https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.5
Поняття: Найбільш конкретний метод
Пояснення: Якщо більш ніж один метод-член доступний і застосовно до виклику методу, необхідно вибрати один, щоб надати дескриптор для відправки методу виконання. Мова програмування Java використовує правило, що обирається найбільш конкретний метод. Спробуйте ввести нуль до певного типу, і метод, який ви бажаєте, буде автоматично викликаний.


У першому прикладі String розширює Object, тому "найбільш специфічним" є метод, що приймає String, але у другому прикладі, String і StringBuffer обидва розширюють Object, тому вони однаково конкретні, і тому компілятор не може зробити вибір
David Kerr

-1

Я б не сказав жодного. NULL - стан, а не значення. Перегляньте це посилання, щоб отримати додаткову інформацію про це (стаття стосується SQL, але я думаю, що це допомагає і у вашому питанні).


nullце, безумовно, значення, як визначено в JLS.
Сотіріос Деліманоліс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.