Аномалия перегрузки функции Scala

#scala #overloading #overload-resolution

#scala #перегрузка #перегрузка-разрешение

Вопрос:

Почему в Scala разрешена такая перегрузка?

 class log {
  def LogInfo(m: String, properties: Map[String, String]): Unit = {
    println(m)
  }

  def LogInfo(m: String, properties: Map[String, String], c: UUID = null): Unit = {
    println(m   c.toString())
  }
}
  

Во втором определении функции LogInfo я установил для дополнительного параметра значение по умолчанию null. Когда я выполняю следующий вызов, он вызовет первую перегрузку.

 val l: log = new log()
val props: Map[String, String] = Map("a" -> "1")
l.LogInfo("message", props)
  

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

Комментарии:

1. » При значении по умолчанию я бы подумал, что оба определения могут выглядеть одинаково». У вас есть какие-либо доказательства в поддержку этой идеи, или это просто предположение?

2. Почему это должно вызывать исключение?

3. Я должен был сказать ошибка компиляции, а не исключение.

Ответ №1:

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

В противном случае пусть CC будет набором применимых альтернатив, которые не используют какой-либо аргумент по умолчанию в приложении to e1,…,em .

Это связано с тем, как компилятор Scala выдает байтовый код JVM для этих двух методов. Если мы скомпилируем их и заглянем за кулисы, мы увидим (опуская фактический байт-код для краткости):

 public class testing.ReadingFile$log$1 {
  public void LogInfo(java.lang.String,
                      scala.collection.immutable.Map<java.lang.String, java.lang.String>);
    Code:

  public void LogInfo(java.lang.String,
                      scala.collection.immutable.Map<java.lang.String, java.lang.String>,
                      java.util.UUID);
    Code:

  public java.util.UUID LogInfo$default$3();
    Code:
       0: aconst_null
       1: areturn
}
  

Вы видите, что сгенерированный код фактически выдал два метода, один из которых принимает два аргумента, а другой — три. Кроме того, компилятор добавил дополнительный метод called LogInfo$default$3 (имя на самом деле имеет значение, где $3 означает «параметр по умолчанию для третьего аргумента»), который возвращает значение по умолчанию для c переменной второй перегрузки. Если должен был быть вызван метод с аргументом по умолчанию, LogInfo$default$3 он будет использоваться для введения новой переменной с заданным значением.

Ответ №2:

Оба метода применимы, но разрешение перегрузки, в частности, выбрасывает приложение, которое требует аргументов по умолчанию:

применимые альтернативы, которые не используют какой-либо аргумент по умолчанию

http://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html#overloading-resolution

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