- Писать класс с первичным конструктором, свойствами и методами
- Пользоваться возможностями data-класса:
toString,==иcopy - Объяснять, зачем нужны
objectи объекты-компаньоны - Строить наследование через
open/overrideи реализовывать интерфейс
Ты пишешь электронный журнал: у каждого ученика есть имя, класс и оценки, нужно добавлять новые оценки и считать средний балл. Для 30 учеников отдельные переменные быстро превращаются в путаницу. Объектно-ориентированное программирование предлагает другой путь: объединить данные и функции, работающие с ними, в одном месте — объекте. Kotlin делает это очень коротко.
Класс, конструктор и свойства
Класс — чертёж объектов, а объект — конкретный экземпляр, созданный по нему. В Kotlin первичный конструктор пишется в скобках сразу после имени класса: параметры, объявленные там через val или var, сразу становятся свойствами класса. Функции внутри класса называются методами. Объект создаётся без слова new, как вызов функции: Student("Aysel", 8).
class Student(val name: String, var grade: Int) {
val scores = mutableListOf<Int>()
fun addScore(score: Int) {
scores.add(score)
}
fun average() = scores.average()
fun introduce() = "I am $name, grade $grade"
}
fun main() {
val aysel = Student("Aysel", 8)
aysel.addScore(5)
aysel.addScore(4)
println(aysel.introduce())
println(aysel.scores)
println(aysel.average())
aysel.grade = 9
println(aysel.introduce())
}I am Aysel, grade 8 [5, 4] 4.5 I am Aysel, grade 9
grade — это var, поэтому его можно менять, а name — val и не меняется.Data-классы
Если вывести объект обычного класса, увидишь непонятный текст вроде Student@1b6d3586, а два объекта с одинаковыми данными не будут равны по ==. Очень часто класс нужен только для хранения данных — пользователь, товар, заказ. Тогда перед классом пишут слово data, и Kotlin автоматически создаёт читаемый toString, ==, сравнивающий содержимое, метод copy и разложение на компоненты.
data class User(val name: String, val age: Int)
fun main() {
val u1 = User("Aysel", 15)
val u2 = User("Aysel", 15)
println(u1)
println(u1 == u2)
val older = u1.copy(age = 16)
println(older)
val (name, age) = older
println("$name is $age")
}User(name=Aysel, age=15) true User(name=Aysel, age=16) Aysel is 16
object и объекты-компаньоны
Иногда в программе должен быть ровно один экземпляр чего-то: настройки приложения, подключение к базе данных. Для этого вместо class пишут object — так одновременно объявляется класс и создаётся его единственный объект. А companion object внутри класса хранит функции и константы, которые вызываются прямо по имени класса, без создания объекта, например «фабричные» функции, создающие объекты.
object Settings {
var language = "az"
fun describe() = "Language: $language"
}
class Temperature(val celsius: Double) {
companion object {
const val WATER_BOILS = 100.0
fun fromFahrenheit(f: Double) = Temperature((f - 32) * 5 / 9)
}
}
fun main() {
println(Settings.describe())
Settings.language = "en"
println(Settings.describe())
val t = Temperature.fromFahrenheit(212.0)
println(t.celsius)
println(t.celsius == Temperature.WATER_BOILS)
}Language: az Language: en 100.0 true
Наследование: open и override
Наследование позволяет создать новый класс на основе существующего: дочерний класс получает свойства и методы родителя. В Kotlin классы по умолчанию закрыты — чтобы от класса можно было наследоваться, его нужно пометить open. Метод, который дочерний класс может изменить, тоже должен быть open, а в дочернем классе его новая версия пишется с override. Родитель указывается после двоеточия: class Dog(name: String) : Animal(name).
open class Animal(val name: String) {
open fun sound() = "..."
fun introduce() = "$name says ${sound()}"
}
class Dog(name: String) : Animal(name) {
override fun sound() = "Woof!"
}
class Cat(name: String) : Animal(name) {
override fun sound() = "Meow!"
}
fun main() {
val pets = listOf(Dog("Rex"), Cat("Mestan"), Animal("Nemo"))
for (pet in pets) {
println(pet.introduce())
}
}Rex says Woof! Mestan says Meow! Nemo says ...
introduce() вызывает у каждого объекта его собственную версию sound() — это называется полиморфизмом.Интерфейсы
Интерфейс — это контракт, описывающий, что должны уметь классы: каждый класс, реализующий его свойства и методы, относится к типу этого интерфейса. Интерфейс может содержать и готовую реализацию метода. Класс может наследоваться только от одного класса, но реализовывать любое количество интерфейсов.
interface Shape {
val name: String
fun area(): Double
fun describe() = "$name: area ${area()}"
}
class Rectangle(val width: Double, val height: Double) : Shape {
override val name = "Rectangle"
override fun area() = width * height
}
class Circle(val radius: Double) : Shape {
override val name = "Circle"
override fun area() = 3.14 * radius * radius
}
fun main() {
val shapes: List<Shape> = listOf(Rectangle(3.0, 4.0), Circle(2.0))
shapes.forEach { println(it.describe()) }
println(shapes.maxBy { it.area() }.name)
}Rectangle: area 12.0 Circle: area 12.56 Circle
kotlin.math.PI.| Объявление | Когда использовать |
|---|---|
class | обычные объекты с данными и поведением |
data class | объекты, которые в основном хранят данные |
object | то, что существует в единственном экземпляре |
open class | база для дочерних классов |
interface | общее умение нескольких классов (контракт) |
Главное
class Student(val name: String)— первичный конструктор сразу объявляет свойство; объект создаётся безnew.data classавтоматически получаетtoString,==по содержимому,copyи разложение на компоненты.objectсоздаёт единственный экземпляр, аcompanion objectхранит члены, вызываемые по имени класса.- Классы и методы по умолчанию закрыты: для наследования нужен
open, для переопределения —override. - Класс наследуется от одного родителя, но может реализовывать несколько интерфейсов.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.
println(Point(1, 2)) для data class Point(val x: Int, val y: Int)?