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

Я наткнулся на этот вопрос в викторине,

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

Результатом этой программы будет «Строковая версия». Но я не мог понять, почему передача null перегруженному методу выбрала строковую версию. Является ли 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»


person zakSyed    schedule 23.10.2012    source источник
comment
Вы можете присвоить строку нулевому значению, чтобы она была действительной, а порядок для java и большинства языков программирования соответствовал ближайшему типу, а затем объекту.   -  person JonH    schedule 23.10.2012
comment
stackoverflow.com/a/1572499/231290   -  person Lukasz    schedule 23.10.2012
comment
Очевидно, это было закрыто как дубликат людьми, которые читали только заголовок. Фактический вопрос здесь заключается в том, почему была выбрана конкретная перегрузка, а не в том, что является нулевым.   -  person interjay    schedule 24.10.2012


Ответы (8)


Является ли null переменной String, не указывающей ни на что?

Пустую ссылку можно преобразовать в выражение любого типа класса. Так что в случае String это нормально:

String x = null;

Перегрузка String здесь выбрана потому, что компилятор Java выбирает наиболее конкретную перегрузку согласно раздел 15.12.2.5 JLS. Особенно:

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

Во втором случае оба метода по-прежнему применимы, но ни String, ни StringBuffer не являются более конкретными, чем другой, поэтому ни один метод не является более конкретным, чем другой, отсюда и ошибка компилятора.

person Jon Skeet    schedule 23.10.2012
comment
И чем перегрузка String более специфична, чем перегрузка Object? - person user1610015; 23.10.2012
comment
Потому что Object может принимать любой тип и заключать его в Object, тогда как String может принимать только String. В этом случае String более специфичен для типа по сравнению с типом Object. - person JonH; 23.10.2012
comment
@ user1610015: подробности см. в JLS - я включил неофициальную версию в ответ. - person Jon Skeet; 23.10.2012
comment
Спасибо за ответ. Однако в моем втором сценарии, почему он снова не выбрал строковый метод, поскольку ранее он не выдавал никаких ошибок компиляции. - person zakSyed; 23.10.2012
comment
@zakSyed: Что ты имеешь в виду, если раньше он не выдавал ошибок компиляции? Во втором случае это неоднозначно - ни String, ни StringBuffer не являются более конкретными, чем другие. - person Jon Skeet; 23.10.2012
comment
@zakSyed Если бы вас спросили, что такое более специализированная строка или объект, что бы вы ответили? Очевидно String, не так ли? Если вас спросят: что такое более специализированный String или StringBuffer? Нет ответа, это обе ортогональные специализации, как выбрать между ними? Затем вы должны четко указать, какой из них вы хотите (например, приведя свою нулевую ссылку question.method((String)null)) - person Edwin Dalorzo; 23.10.2012
comment
@zakSyed - Вы можете найти книгу по пониманию типов данных. Вы должны понять, почему ваш второй случай неоднозначен. Это два разных типа, а именно String и StringBuffer, это то же самое, если бы вы перегрузили этот метод с помощью String и Int. - person JonH; 23.10.2012
comment
@JonSkeet: В этом есть смысл. Таким образом, в основном он ищет метод в соответствии с наиболее конкретным правилом, и если он не может решить, какой из них более конкретен, он выдаст ошибку времени компиляции. - person zakSyed; 23.10.2012
comment
@JonH: в случае string и int код компилируется и возвращает версию строки вывода. - person zakSyed; 23.10.2012
comment
@zakSyed - верно, в вашем конкретном случае я хотел сказать, что String и Int - это всего лишь два разных типа, которые специализируются именно на этих своих типах. Если вы передаете int, компилятор просматривает наиболее конкретный метод (учитывая, что вы имеете дело с перегруженными функциями) и использует его. - person JonH; 23.10.2012
comment
@JonH null - это ссылочный тип, если один из методов получает в качестве параметра примитивный тип (т.е. int), он даже не будет учитываться компилятором при выборе правильного метода для вызова для ссылки типа null. Это сбивает с толку, если под Int вы имели в виду java.lang.Integer или если вы имели в виду примитивный тип int. - person Edwin Dalorzo; 23.10.2012
comment
@EdwinDalorzo - потерял рассудок, вы правы, int был очень плохим примером. Используйте ссылочный тип зак. - person JonH; 23.10.2012

Кроме того, JLS 3.10. 7 также объявляет, что «null» является буквальным значением «нулевого типа». Следовательно, существует тип под названием «null».

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

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

Почему компилятор решил расширить его до String, можно объяснить в ответе Джона.

person Edwin Dalorzo    schedule 23.10.2012
comment
Я совершенно уверен, что это Void. - person xavierm02; 23.10.2012
comment
@ xavierm02 Об этом не упоминается в Спецификации языка Java. Не могли бы вы процитировать свой отзыв, чтобы мы все могли проверить ваше утверждение? - person Edwin Dalorzo; 23.10.2012
comment
Я никогда не читал эту вещь. Но этим летом я сделал немного Java, и вы можете перегрузить метод, принимающий Object, методом, принимающим объект Void. - person xavierm02; 23.10.2012
comment
Это больше класс, чем тип. - person xavierm02; 23.10.2012
comment
@ xavierm02 Конечно, можешь. Вы можете перегрузить метод в Java любым другим типом, который хотите. Тем не менее, это не имеет ничего общего с вопросом или моим ответом. - person Edwin Dalorzo; 23.10.2012

Вы можете присвоить string значению null, чтобы оно было действительным и порядок для java и большинства языков программирования соответствовал ближайшему типу, а затем объекту.

person JonH    schedule 23.10.2012

Чтобы ответить на вопрос в заголовке: null не является ни String, ни Object, но ссылка на любой из них может быть присвоена null.

Я действительно удивлен, что этот код даже компилируется. Раньше я пробовал нечто подобное, и у меня возникла ошибка компилятора, в которой говорилось, что вызов был неоднозначным.

Однако в этом случае кажется, что компилятор выбирает метод, который является самым низким в пищевой цепочке. Предполагается, что вам нужна наименее общая версия метода, которая поможет вам.

Мне нужно будет посмотреть, смогу ли я откопать пример, в котором я получил ошибку компилятора в этом (по-видимому) точно таком же сценарии, хотя ...]

РЕДАКТИРОВАТЬ: Понятно. В версии, которую я сделал, у меня было два перегруженных метода, принимающих String и Integer. В этом сценарии нет «наиболее конкретного» параметра (как в Object и String), поэтому он не может выбирать между ними, в отличие от вашего кода.

Очень классный вопрос!

person asteri    schedule 23.10.2012
comment
null не является ни тем, ни другим, но он может быть назначен обоим, в этом разница, и он будет компилировать, почему бы и нет - это абсолютно правильный код. - person JonH; 23.10.2012

Поскольку тип String более конкретен, чем тип объекта. Допустим, вы добавили еще один метод, который принимает целочисленный тип.

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

Тогда вы получите ошибку компилятора о том, что вызов неоднозначен. Теперь у нас есть два одинаково конкретных метода с одинаковым приоритетом.

person BSingh    schedule 02.10.2015

Компилятор 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.

Надеюсь, это поможет лучше разобраться в этом случае.

person Ravi    schedule 24.11.2016

Источник: https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.5
Концепция: Наиболее конкретный метод
Объяснение: Если для вызова метода доступно и применимо более одного метода-члена, необходимо выбрать один, чтобы предоставить дескриптор для диспетчеризации метода времени выполнения. В языке программирования Java используется правило, согласно которому выбирается наиболее конкретный метод. Попробуйте привести значение null к определенному типу, и нужный вам метод будет вызван автоматически.

person M.P    schedule 12.03.2019
comment
В первом примере String расширяет Object, поэтому наиболее конкретным является метод, принимающий String, но во втором примере String и StringBuffer оба расширяют Object, поэтому они одинаково специфичны, и поэтому компилятор не может создать выбор - person David Kerr; 12.03.2019

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

person northpole    schedule 23.10.2012
comment
null - определенно значение, как определено в JLS. - person Sotirios Delimanolis; 17.12.2014