Переменные в Kotlin

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

Однако любые действия производятся над чем-то или самим чем-то. Действия не могут совершаться пустотой или выполняться над пустотой. Таким образом, в программе кроме действий-операций должны быть какие-то объекты, над которыми эти операции выполняются.

Что это за объекты? По большому счету – все что угодно. Числа, строки, коллекции данных, файлы, базы данных и много чего еще. Даже кусок программного кода может выступать в роли объекта, с которым можно что-то сделать.

Объекты одного типа принадлежат одному классу. Например, все целые числа принадлежат классу Int, а все строки – классу String. Так числа 10 и 55 – это разные объекты, но они принадлежать одному классу.

Понятию класса эквивалентно понятие "тип данных". В Kotlin это почти одно и то же. А вот в других языках – не всегда. Бывает, что примитивные данные (числа, символы) не являются экземплярами (объектами) какого-либо класса. Они встроены в язык именно как типы данных, которые сами ничего делать "не умеют", то есть не имеют собственных методов-действий. Хотя над ними самими можно совершать действия.

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

Чтобы выполнять над данными действия, к ним надо как-то обращаться, знать, "где они лежат". Лежат они в памяти компьютера, в ее ячейках. Ячейки пронумерованы, но нам было бы неудобно обращаться к ним по номерам. Вместо этого в программировании используют переменные – словесные имена, связываемые с экземплярами данных.

В разных языках программирования внутренний механизм связывания может отличаться. Нас лишь интересует, что почти любое данное (объект) в программе связывается с именем. Это связывание называется присваиванием, или присвоением. В подавляющем большинстве языков операция присваивания обозначается знаком равенства – =.

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

fun main() {
    var welcome = "hello - привет"
    println(welcome)
}

Новая версия программы будет давать тот же самый результат, что и старая, – выводить на экран строку hello - привет. (Примечание: чтобы разделить рабочую область IntelliJ IDEA на две части, надо кликнуть по вкладке файла правой кнопкой мыши и выбрать Split Right или Split Down.)

В программе появилось еще одна команда – ключевое слово var (variable – переменная). Это команда объявления переменной. После нее указывается имя переменной, которое может быть любым за некоторыми исключениями (в имени переменной нельзя использовать пробел, начинать с цифр и определенных знаков).

В данном случае переменной сразу присваивается значение, поэтому после нее идет знак равно, а после него – связываемое с переменной значение – строка "hello - привет".

Теперь вместо самой строки мы можем передать в функцию println() переменную. Вместо имени переменной будет подставлено связанное с ней значение.

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

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

(Примечание: чтобы дублировать строку, установите в нее курсор и нажмите Ctrl + D.)

Итак, первое свойство переменной – связь с объектом, она не дает его потерять. В качестве второй особенности следует назвать ее изменчивость. Само слово "переменная" говорит, что она может меняться. Под этим понимается, что сначала переменная может быть связана с одним объектом, а потом с другим (но в Kotlin того же типа данных). Под изменчивостью переменной не следует понимать изменение самого объекта. А так бывает, если объект относится к изменяемым типам данных. Например, в массиве можно изменить один элемент. Массив изменится, но останется прежним экземпляром.

Снова внесем коррективы в нашу программу:

fun main() {
    var welcome = "hello - привет"
    println(welcome)
    welcome = "world - мир"
    println(welcome)
}

В четвертой строчке кода переменной welcome было присвоено другое значение. Заметьте, что здесь нет ключевого слова var, которое используется только при объявлении переменной, то есть при первом ее появлении в программе.

Что происходит, когда мы присваиваем переменной во второй раз? Связь между ней и первым значением разрывается. Вместо этого переменная связывается со вторым объектом. Первый же объект, в отсутствии других указателей на него, автоматически объявляется мусором. В память, которую он занимает, может быть записано что-то новое.

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

fun main() {
    var welcome = "Hello"
    var place = "World"
    println(welcome + place)
}

В приведенной программе помимо второй переменной у нас появляется новая операция, обозначенная знаком плюс. По отношению к строкам она выполняет не сложение, а так называемую конкатенацию – соединение строк. При этом в выражении println(welcome + place) последовательность действий такова:

  1. Вместо welcome берется "Hello". Вместо place берется "World".

  2. Выполняется конкатенация строк. Получается строка "HelloWorld".

  3. Вызывается функция println(), которой передается строка "HelloWorld".

  4. Поток выполнения программы уходит в функцию println.

  5. Функция println выводит переданную ей строку на экран, после чего возвращает поток выполнения туда, откуда ее вызвали. В данном случае – в тело функции main в конец четвертой строки кода.

Операция + – это действие. В Kotlin оно совершается объектом, с помощью метода, который у него есть. Мы можем переписать вышеприведенную программу так:

fun main() {
    var welcome = "Hello"
    var place = "World"
    println(welcome.plus(place))
}

Здесь вызывается метод plus() объекта, связанного с переменной welcome. Метод – это то же самое, что и функция, за исключением того, что метод вызывается через объект, а не сам по себе.

В функцию plus() передается другая строка, связанная с переменной place. В теле plus происходит конкатенация строк, и в программу возвращается готовая соединенная строка. Чтобы было понятнее, можно переписать программу так:

fun main() {
    var welcome = "Hello"
    var place = "World"
    var welcomePlace = welcome.plus(place)
    println(welcomePlace)
}

Здесь результат конкатенации присваивается отдельной переменной, после чего она передается в println().

Использование знака + вместо вызова метода делает программу проще и нагляднее. Такие операции можно считать сокращенным вариантом записи вызовов методов.

Обратите внимание, что значение переменных welcome и place после конкатенации не меняется. Создается третья строка. Если она не присваивается никакой переменной, то после вывода на экран сразу теряется.

IntelliJ IDEA подсвечивает ключевое слово var желтоватым оттенком. Подобное выделение – сигнал о каком-то недочете в коде. Недочет – не значит ошибка. Иногда программисту виднее, чем среде. Если мы наведем мышь на метку справа в данной строке, сможем увидеть в чем дело.

Variable is never modified so it can be declared using 'val' – переменная никогда не изменяется, поэтому может быть объявлена с помощью val. Данное сообщение означает, что поскольку в нашей программе переменной, например welcome, значение присваивается единожды и больше ни разу не меняется, то было бы правильнее ее объявить не с помощью var, а с помощью val (value – значение).

Мы можем сделать это, либо нажав Alt + Shift + Enter, когда читаем сообщение, либо самостоятельно исправить var на val.

Зачем нужны неизменяемые переменные? Иногда надо защитить объект от его потери. Связав его с неизменяемой переменной, мы уже не сможем присвоить ей другое значение.

fun main() {
    val welcome = "Hello"
    var place = "World"
    println(welcome + place)
    place = "Earth"
    // welcome = "Hi"
    println(welcome + place)
}

Выражение welcome = "Hi" закомментировано, а значит не выполняется. Комментарии в программах используются для пояснения кода или отключения его части. Если раскомментировать эту строку, то в программе возникнет ошибка, потому что значение переменной welcome менять нельзя. В то же время place – изменяемая переменная.

(Примечание. В IntelliJ IDEA чтобы закомментировать отдельную строку, установите в нее курсор и нажмите Ctrl + /. Повторное нажатие – раскомментировать. Чтобы закомментировать несколько строк, выделите их и также нажмите Ctrl + /.)

До этого мы задавали значения переменным жестко, в программном коде. Но что если значение будет известно только в процессе выполнения программы, например, поступать с клавиатуры. В Kotlin есть функция readln, которая считывает строку с ввода и передает ее в программу. Эту функцию можно рассматривать антагонистом функции println, которая, наоборот, выводит на экран.

fun main() {
    val welcome = "Hello"
    var place = readln()
 
    println(welcome + place)
}

При выполнении программы надо кликнуть в рабочую область вкладки Run (туда где выполняется программа) и написать что-нибудь. Вашу строку заберет функция readLine() и передаст ее в функцию main(), где строка будет присвоена переменной place.

На первый взгляд может показаться, что place должна быть объявлена как изменяемая переменная. Ведь ее значение может быть абсолютно любой строкой. Однако вспомним отличие val от var. Неизменяемой переменной можно присвоить только однажды. Но это не значит, что ей можно присваивать только программно жестко заданное значение. В программе выше мы присваиваем place всего один раз, а значит правильнее было бы ее определить через val.

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

Практическая работа:

  1. Измените программу с переменными welcome и place так, чтобы на экран выводилась строка Hello World!, а не HelloWorld. Значения переменных менять нельзя.

  2. Напишите программу, в которой тело функции main состоит всего из одной строки-выражения, в котором с ввода запрашиваются две строки, соединяются в одну и выводятся на экран. Потребуются ли вам переменные?

  3. Присвойте двум переменным числа, выведите на экран их сумму. Проверьте, допустимо ли в Kotlin складывать строку и число. Если да, то как это происходит?

PDF-версия курса с ответами к практическим работам


Kotlin с нуля. Курс для начинающих




Все разделы сайта