in kotlin ~ read.

Неочевидные заметки #1

Знатокам Котлина вопрос, а остальным просто наподумать. Что будет выведено в результате работы этого небольшого куска кода:

greeterWrap("hello", "hi", "bonjour")()

fun greeterWrap(first: String, vararg vars: String, second: String = "ha ha") = {  
    Greeter(first, *vars, second).greet()
}

class Greeter (val first: String, vararg val vars: String, val second: String = "") {  
    fun greet() {
        println("first  = $first")
        println("vars   = ${vars.map { it }.joinToString()}")
        println("second = $second")
    }
}

Очевидно, что первой строкой будет:

first  = hello  

а вот дальше уже не так очевидно. Если ваш ответ не был таким:

first  = hello  
vars   = hi, bonjour, ha ha  
second =  

то самое время разобраться почему именно так.

Начнём с того, что в котлине есть две интересные вещи.

Во-первых, vararg может быть не только последним аргументом, но и первым или посередине. Это достигается за счёт присутствия именованных параметров. Единственное что несколько их быть не может.

Во-вторых, в котлине есть дефолтные значения для аргументов функции, или конструктора. И second: String = "ha ha" можно трактовать как если параметр не передан, то используй по-умолчанию.

Именно сочетание этих двух классных возможностей дают нам шанс выстрелить себе в ногу.

Итак, у нас есть функция:

fun greeterWrap(first: String, vararg vars: String, second: String = "ha ha")  

которую мы вызываем с двумя аргументами:

"hello"           -> first
["hi", "bonjour"] -> vars

поэтому значением параметра second будет значение по-умолчанию: ha ha.

Теперь внутри функции мы фактически делаем то же самое:

Greeter(first, *vars, second).greet()  

где *vars это набор строк, и second с точки зрения компилятора это ещё одна строка, потому что в объявлении класса:

class Greeter (val first: String, vararg val vars: String, val second: String = "")  

последний параметр не является обязательным.

В итоге, всё компилируется и на первый взгляд кажется нормальным, но работает не так как ты ожидаешь (а ожидаем мы проброс значения ha ha в параметр second).

Чтобы избежать таких конфузов стоит или объявлять vararg последним аргументом, как это сделано в Java:

Greeter(first, second, *vars).greet()

class Greeter (val first: String, val second: String = "", vararg val vars: String) {  
    fun greet() {
       ...
    }
}

или использовать явное именование параметров:

Greeter(first, *vars, second = second).greet()  

или не указывать дефолтные значения:

class Greeter (val first: String, vararg val vars: String, val second: String)  

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

comments powered by Disqus