Найти тему

Val, var, def, обращение по имени и по значению - как не запутаться?

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

Начнём с самого простого - val и var, в чём различие между ними? Поможет понять "продолжение слова", VALue и VARiable - как не трудно после этого догадаться, первый - как значение - неизменный, а второй - изменяемый. По-хорошему старайтесь избегать использования var, ведь в функциональном подходе мутабельность(изменяемость) является отрицательным качеством, по многим причинам, но не об этом сегодня.

Теперь давайте посмотрим на def и val, тут уже может быть не столь ясно. Чего же тут сложного, def - метод, val - значение, - скажете Вы. Так то оно так, но понимание скрыто чуть глубже, посмотрим на наглядный пример:

val func : Int => String = "Number is " + _.toString

def func2(a:Int) = "Number is " + a.toString

Вот два "метода", скажем так, которые делают абсолютно одно и то же, даже вызов будет одинаков: func(2) или func2(2) - при выводе вы получите одно и то же, если не верите, то можете проверить. Так в чём же разница? Давайте добавим в тело методов небольшой сайд-эффект, и обратимся к ним по 2 раза?

val func : Int => String = {
println("initialized func")
"Number is " + _.toString
}
def func2(a:Int) = {
println("initialized func2")
"Number is " + a.toString
}

println(func(2))
println(func(3))
println(func2(2))
println(func2(3))

Тут вы будете немного удивленны, на экране мы увидим следующее:

initialized func

Number is 2

Number is 3

initialized func2

Number is 2

initialized func2

Number is 3

Дело в том, что к def при обращении мы "запускаем" метод полностью каждый раз, а к val - только единожды, и далее храним значение функции, и последующего высвобождения сайд-эффектов не происходит!

Точно такой же логикой обладают обращение по имени и по значению:

def func1(a: => Int) = "Number is " + a.toString
def func2(a:Int) = "Number is " + a.toString

В первом случае "а" не будет инициализирована до того момента, пока к ней не обратятся, то есть "ленивым" методом, а во втором случае - она будет инициализирована, или должна быть уже инициализирована, во время вызова метода!

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