- Ключевое слово data
- toString()
- equals()
- hashCode()
- Деструктурирующее присваивание
- Несколько конструкторов
- Классы
- Блок инициализации init
- Конструкторы
- Вторичные конструкторы
- Геттеры/сеттеры
- Импорт Java-классов
- Получить имя класса
- Переопределяем метод toString()
- Расширение классов
- Абстрактные классы
- Модификаторы видимости
- Внутренние (inner) и вложенные классы
Ключевое слово data
Если у класса указать ключевое слово data, то автоматически будут созданы и переопределены методы toString(), equals(), hashCode(), copy(). Скорее всего вы будете использовать этот вариант для создания полноценного класса-модели с геттерами и сеттерами.
В конструкторе класса у параметров следует указывать val или var.
Подобные классы часто используются при работе с JSON.
Классы данных не могут объявляться абстрактными или открытыми, так что класс данных не может использоваться в качестве суперкласса. Однако классы данных могут реализовать интерфейсы, а также могут наследоваться от других классов.
toString()
Если нужно получить информацию о классе, то достаточно вызвать имя переменной класса. Вы получите строку со всеми значениями всех свойств на основе конструктора вместо непонятных символов @Cat5edea как в Java. Такой подход удобен при тестировании и отладке. Сразу понятно, о чём идёт речь.
Можно сразу определить значение по умолчанию у поля класса. При инициализации объекта можно не указывать поле, но оно будет доступно для вычислений.
equals()
При определении класса данных функция equals() (и оператор ==) по-прежнему возвращает true, если ссылки указывают на один объект. Но она также возвращает true, если объекты имеют одинаковые значения свойств, определённых в конструкторе:
Если вы переопределяете функцию equals(), также необходимо переопределить функцию hashCode().
Кстати, если вам нужно проверить, что две переменные ссылаются на один объект, то используйте оператор ===. В отличие от оператора ==, поведение оператора === не зависит от функции equals(), которое в разных классах может вести себя по разному. Оператор === всегда ведёт себя одинаково независимо от разновидности класса.
hashCode()
Если два объекта данных считаются равными (имеют одинаковые значения свойств), функция hashCode() возвращает для этих объектов одно и то же значение:
Если вам потребуется создать копию объекта данных, изменяя некоторые из его свойств, но оставить другие свойства в исходном состоянии, воспользуйтесь функцией copy(). Для этого функция вызывается для того объекта, который нужно скопировать, и ей передаются имена всех изменяемых свойств с новыми значениями.
Фактически мы создаём копию объекта, меняем значение нужного свойства и присваиваем новый объект переменной с новым именем. При этом исходный объект остаётся без изменений.
Деструктурирующее присваивание
При создании data-классов компилятор автоматически добавляет набор функций, с помощью которых можно обратиться к свойствам. Они известны как componentN-функции, где N — это номер свойства. Подсчёт номера идёт по порядку в объявлении класса.
Мы могли бы обратиться и привычным способом.
Деструктуризация позволяет разбить объект на несколько переменных.
Можно пропустить через цикл.
Можно пропустить какую-то переменную через символ подчёркивания.
Несколько конструкторов
Добавить второй конструктор к классу можно через ключевое слово constructor.
Источник
Классы
Kotlin пытается сократить количество кода при создании новых классов. В Kotlin решили реализовать другой подход к классам. Например, объявления по умолчанию получают модификаторы final и public. Вложенные классы по умолчанию не являются внутренними и другие особенности.
В Kotlin нет жёсткой привязки классов и файлов. Вы можете создать несколько классов в одном файле и выбрать любое имя для этого файла. При этом также не важно размещение файлов с классами на диске, можно даже не обращать внимание на имена пакетов и соблюдать точную иерархию вложенности. Но лучше придерживаться правил Java, особенно, если у вас уже большой опыт и вы привыкли к стандартному расположению.
Для класса используется ключевое слово class. С ним могут использоваться также ключевые слова: data, open, internal, abstract, public.
Начнём с самых основ. Класс представляет собой шаблон, определяющий свойства и функции, связанные с объектами этого типа. Android уже содержит множество готовых классов, но всё предусмотреть невозможно и вам придётся создавать собственные классы. Например, мы можем создать класс для кота. Класс!
Если ваше приложение предназначено для хранения информации о котах, вы можете определить класс Cat для создания ваших собственных объектов Cat. В таких объектах скорее всего вам понадобится хранить имя кота (name), его вес (weight) и порода (breed). Вы можете придумать свои варианты для ваших конкретных задач.
В большинстве случаев класс содержит свойства и функции. Хотя Kotlin позволяет создать пустой бессмысленный класс.
Создадим простейший класс для понимания.
В Kotlin ключевое слово public при создании класса не требуется, по умолчанию класс имеет данный модификатор и указывать его не нужно.
Вызываем класс в коде.
Никаких new. Одна строчка кода для создания класса, одна строчка для вызова.
В Java для классов используются поля, Kotlin оперирует свойствами. Свойство — это то, что объект знает о себе. Уважающий себя кот знает, как его зовут, сколько он весит и какой он породы. Класс Car (машина) будет иметь свой набор свойств — марка, цвет, скорость и т.п.
То, что объект может сделать, — это его функции (в Java методы). Они определяют поведение объекта и могут использовать свойства объекта. Класс Cat может содержать функцию sleep().
Создадим полноценный класс.
Мы сейчас не только создали класс, но и задали ему три свойства, используя ключевые слова val и var (это важно).
Функции пишутся внутри фигурных скобок. Создадим функцию sleep(). Если это ещё маленький котёнок, то во сне он сопит, а солидный кот уже храпит.
Создаём объект Барсик, используя все свойства класса.
Доступ к любому свойству достигается через оператор . (точка):
Если свойство задано ключевым словом var, то мы можем не только прочитать свойство, но и установить новое значение. В нашем случае таким свойством является weight. Давайте накормим кота, чтобы увеличить его вес.
Если вы захотите изменить свойства с ключевым словом val, то у вас ничего не получится. Компилятор сообщит об ошибке (error: val cannot be reassigned). Попробуйте это сделать самостоятельно, чтобы увидеть своими глазами.
Функция вызывается также через оператор «точка», но не забывайте про круглые скобки.
Мы можем создать массив объектов из нашего класса.
Создание объекта очень похоже на вызов функции — используются круглые скобки, только вместо названия функции пишем имя класса. На самом деле — это конструктор. Подробнее о конструкторах поговорим позже. Пока нужно запомнить — конструктор содержит код, необходимый для инициализации объекта. Часто используют конструкторы для определения свойств объекта и присваивания им значений. Объект иногда называют экземпляром класса, а его свойства — переменными экземпляров.
Создание класса с готовыми свойствами в виде параметров очень удобно и часто используемый приём. Kotlin помогает писать код в удобном компактном виде. Но иногда нужно со свойством что-то сделать — проверить на условие, присвоить значение по умолчанию и т.д. В этом случае вы можете использовать стиль, который принят в Java.
В конструкторе мы используем ключевые слова val и var — в данном случае name, weight и breed уже не являются свойствами класса, а являются обычными параметрами конструктора. А свойства задаются уже внутри фигурных скобок. Не очень красиво, что имена свойств совпадают с именами параметров, код становится нечитаемым. Некоторые программисты предпочитают избегать такой схожести и используют следующий подход.
Так легче различать, где свойство и где параметр.
Другой популярный способ — знак подчёркивания.
Если вы привыкли использовать одно имя, то добавьте this.
Определение свойств в теле класса обеспечивает большую гибкость, чем простое добавление их в конструктор, так как в этом случае не нужно инициализировать каждое свойство значением параметра конструктора.
Предположим, вы хотите определить для нового свойства значение по умолчанию, не включая его в конструктор. Добавим в класс свойство activities и инициализируем его массивом, который по умолчанию содержит значение «Play».
Другой вариант — нужно изменить значение параметра конструктора перед тем, как присваивать его свойству. Например, мы хотим, чтобы свойство breed выводило строку в верхнем регистре. В параметре конструктора мы задаём обычную строку, а затем создаём новую версию строки через функцию toUpperCase().
Обратите внимание, что мы убрали из конструктора свойство breed (нет ключевого слова val) и заменили параметром breed_param. В теле класса создали свойство breed, которое принимает параметр конструктора и преобразовывает его.
Блок инициализации init
Указанный способ инициализации свойств хорошо работает, если вы хотите присвоить простое значение или выражение. А если понадобится сделать что-то более сложное? Или нужно выполнить дополнительный код, который сложно добавить в конструктор?
Здесь нам поможет блоки инициализации, которые выполняются при инициализации объекта сразу же после вызова конструктора и снабжаются префиксом init. Блоков init может быть несколько. Они выполняются в том порядке, в котором определяются в теле класса, чередуясь с инициализаторами свойств.
Важный момент — свойства, задаваемые в теле класса должны инициализироваться перед их использованием в коде. Вы не сможете пропустить этот шаг, так как компилятор откажется компилировать ваш код. Попробуйте придумать самостоятельно новое свойство для кота, но не инициализировать его.
Фактически при создании свойства и его инициализации, вы назначаете свойствам значения по умолчанию. Например, для строковых свойств можно использовать пустую строку, для чисел 0 и т.п.
Конструкторы
Мы уже ранее сталкивались с конструктором. Познакомимся с ним поближе.
Можно создать класс без конструктора — после имени класса не используем круглые скобки. Тогда нужно добавить только фигурные скобки для блока.
Когда вы определяете класс без конструктора, компилятор генерирует его за вас. Он добавляет пустой конструктор (конструктор без параметров) в откомпилированный код, который ничего не делает. Ваш код будет равносилен коду:
И создавать объект вам всё-равно придётся с использованием круглых скобок.
Если вы будете наследоваться от подобного класса, не создавая своих конструкторов, то следует явно вызвать конструктор суперкласса, несмотря на то, что он не имеет параметров.
Можно обойтись без фигурных скобок.
Подобный конструктор считается основным или первичным (primary) в Kotlin. Он объявляется вне тела класса. Дополнительные вторичные конструкторы объявляются уже в теле класса.
Предыдущий пример создания класс можно было переписать чуть длиннее.
Напомню, что знак подчёркивания используют, чтобы отличить параметр конструктора от свойства. Если вы привыкли использовать одно имя, то добавьте this.
Параметрам конструктора можно назначать значения по умолчанию, как в параметрах функций.
Тогда этот параметр можно не указывать при создании экземпляра класса.
Если класс имеет суперкласс, то основной конструктор должен инициализировать свойства от суперкласса.
Вторичные конструкторы
При создании дополнительных конструкторов используется ключевое слово constructor (впрочем для первичного конструктора тоже можно). Вторичный конструктор делегирует первичный конструктор с помощью ключевого слова this. Добавим в конструктор новое свойство email:
Если требуется несколько конструкторов, то можно объявить столько вторичных конструкторов, сколько вам требуется.
Геттеры/сеттеры
У свойств есть проблема — их нельзя контролировать. Никто не мешает присвоить свойству weight отрицательное значение. Котик в опасности!
Для решения проблемы существуют get- и set-методы (иногда их называют геттеры и сеттеры). Get- и set-методы предназначены для чтения и записи значений свойств. Цель get-метода — вернуть значение, запрошенное для данного свойства. А set-методы получают значение аргумента и используют его для записи значения в свойство. Таким образом get- и set-методы позволяют защитить значения свойств и управлять тем, какие значения читаются или записываются в свойства.
В очередной раз переделаем класс. Добавим в класс новое свойство weightInGramms и напишем для него пользовательский get-метод, который будет возвращать соответствующее значение.
Сразу после объявления свойства мы создаём get-метод, который представляет собой функцию без параметров. Тип возвращаемого значения должен совпадать с типом свойства, значение которого должен возвращать get-метод, в противном случае код не будет компилироваться.
Проверим, как работает свойство.
Дополним свойство weight пользовательским set-методом, чтобы свойство могло обновляться только значениями больше 0. Для этого необходимо переместить определение свойства weight из конструктора в тело класса, а затем добавить set-метод к свойству.
Set-метод представляет собой функцию с именем set, которая записывается под объявлением свойства. Set-метод имеет один параметр (обычно с именем value), который содержит новое значение свойства. В нашем случае значение свойства weight обновляется только в том случае, если значение параметра value больше 0. Если попытаться обновить свойство weight значением, меньшим либо равным 0, set-метод проигнорирует обновление свойства. Внимание! Для обновления свойства weight set-метод использует идентификатор field, обозначающий поле данных для свойства. Очень важно использовать именно field вместо имени свойства, потому что так вы предотвращаете зацикливание. Просто поверьте на слово.
Мы создавали get- и set- методы вручную, чтобы контролировать данные для свойства. Но вдобавок компилятор незаметно генерирует get- и set-методы для всех свойств, у которых пользовательских методов нет. Если свойство определяется с ключевым словом val, то компилятор добавляет get-метод, а если свойство определяется с ключевым словом var, то компилятор добавляет и get-, и set-метод.
Рассмотрим примеры. По ключевому слову val можно догадаться, что у name есть только геттер, так как этот тип переменной отвечает за неизменяемые данные. У isMale есть и геттер и сеттер.
Вызываем экземпляр класса, присваиваем коту имя и пол. Результат выводим на экран.
Если вы хотите открыть доступ к свойству для чтения и закрыть для записи, то объявите область видимости записи приватным.
Теперь изменять свойство может только сам экземпляр класса. А предыдущий пример не станет работать. Это полезно, если требуется запретить изменение свойства другими частями вашего приложения.
Другой пример — у нас есть класс прямоугольника и мы хотим предоставить метод, который сообщит, является ли фигура квадратом (частный случай). Если у класса уже есть параметры для ширины и высоты, то нам не нужно создавать дополнительную переменную, мы можем динамически проверить размеры прямоугольника.
Вызываем объект класса и проверяем.
Мы получили вычисляемое свойство, у которого значение меняется. У него нет начального значения, значения по умолчанию и нет поля, которое могло бы хранить значение.
Импорт Java-классов
Можно импортировать стандартный Java-класс и вызывать его методы.
Получить имя класса
Вместо стандартного метода Java getClass() можно вызвать javaClass.
Иногда в логах используют имя текущего класса. Проверим на стандартной активности
В Kotlin есть новое ключевое слово internal, применимое к пакетам. Позволяет указать, что классы доступны внутри модуля.
Переопределяем метод toString()
Можно переопределять метод toString():
Расширение классов
Можно расширять классы не изменяя код самого класса при помощи функций-расширений. Добавим функцию isBlack() к классу Cat вне самого класса:
Таким удобным образом вы можете даже расширить классы, которые не принадлежат к коду вашего проекта.
Абстрактные классы
Как и в Java, класс можно объявить абстрактным, добавив ключевое слово abstract. Создать экземпляр такого класса нельзя. Абстрактные методы всегда открыты, поэтому использование модификатора open необязательно.
Абстрактный класс может содержать абстрактные свойства и функции. Абстрактный класс также может содержать и неабстрактные свойства и функции. А ещё абстрактный класс может не содержать ни одного абстрактного свойства или функции. Если класс содержит какие-либо свойства и функции, помеченные как абстрактные, весь класс должен быть абстрактным.
Свойства в абстрактных свойствах нельзя инициализировать.
Абстрактные функции не могут иметь блок с фигурными скобками.
Расширяемся от абстрактного класса при помощи двоеточия, как и с с суперклассом.
Вы должны реализовать все абстрактные свойства и функции — переопределите их и предоставьте реализации. Все абстрактные свойства необходимо инициализировать, а для любых абстрактных функций необходимо предоставить тело в фигурных скобках.
В абстрактных подклассах у вас есть выбор: либо реализовать абстрактные свойства и функции, либо передать эстафету их подклассам.
Модификаторы видимости
По умолчанию класс имеет модификатор public, который можно не указывать. Члены класса и объявления верхнего уровня доступны повсюду.
В Kotlin добавлен новый модификатор internal, обеспечивающий видимость в границах модуля. Модуль — это набор файлов, компилируемых вместе (модуль в IDEA, проект Eclipse, Maven, Gradle и т.д.). Члены класса и объявления верхнего уровня доступны в пределах модуля.
Члены класса с модификатором protected доступны в подклассах.
Члены класса с модификатором private доступны в самом классе, а объявления верхнего уровня с этим модификатором доступны в файле.
Внутренние (inner) и вложенные классы
Можно объявлять один класс внутри другого. Но в Kotlin вложенные классы не имеют доступа к экземпляру внешнего класса, если не запросить его явно.
В Kotlin вложенный класс без модификаторов является аналогом статического вложенного класса в Java (фактически независимый класс).
Чтобы создать внутренний класс со ссылкой на внешний класс, нужно добавить модификатор inner.
Чтобы получить доступ к внешнему классу из внутреннего, нужно использовать конструкцию this@Outer, где Outer — название внешнего класса.
Класс Any является предком (суперклассом) всех классов. Каждый класс, который вы определяете, является подклассом Any, и вам не нужно указывать на это в программе. Когда вы создаёте класс следующим образом:
Компилятор незаметно для вас преобразует ваш в класс в подкласс Any.
Подобный подход гарантирует, что каждый класс наследует общее поведение. В частности, любой класс будет содержать функцию equals().
Также вы можете создать функцию с параметрами Any или возвращаемый тип Any, который будет работать с объектами любых типов. Например, можно создать массив для хранения объектов любого типа:
Класс содержит несколько функций, наследуемых каждым классом.
- equals(any: Any): Boolean — проверяет, считаются ли два объекта «равными» (одним фактическим объектом). По умолчанию функция возвращает true, если используется для проверки одного объекта, или false — для разных объектов. Функция equals() вызывается каждый раз, когда используется оператор ==
- hashCode(): Int — возвращает хеш-код для объекта. Хеш-коды часто используются некоторыми структурами данных для эффективного хранения и выборки значений
- toString(): String — возвращает описание класса. По умолчанию сообщение содержит имя класса и «непонятное» число по определённому правилу
Вы можете переопределить реализации всех этих функций, чтобы изменить поведение по умолчанию.
Не забывайте, что переменная с типом Any не может хранить значения null. Если вам нужна переменная, которая должна хранить объекты любых типов и null, то используйте Any?.
Источник