- Getters and Setters in Kotlin
- 1. Introduction
- 2. Getters and Setters for Properties
- 2.1. Accessing the Backing Field
- 2.2. val vs. var
- 2.3. Visibility Modifiers
- 3. Java Interoperability
- 4. Conclusion
- Getters and setters kotlin
- Сеттер
- геттер
- Использование геттеров и сеттеров в классах
- Вычисляемый геттер
- Использование полей для хранения значений
Getters and Setters in Kotlin
As a seasoned developer, you’re likely already familiar with Spring. But Kotlin can take your developer experience with Spring to the next level!
- Add new functionality to existing classes with Kotlin extension functions.
- Use Kotlin bean definition DSL.
- Better configure your application using lateinit.
- Use sequences and default argument values to write more expressive code.
By the end of this talk, you’ll have a deeper understanding of the advanced Kotlin techniques that are available to you as a Spring developer, and be able to use them effectively in your projects.
1. Introduction
In this tutorial, we’re going to look at properties in Kotlin and how to access them. Properties are similar to fields in Java, but there are some important differences.
For example, properties have auto-generated getters and setters. They can also be declared at the top-level package scope – they don’t have to belong to a class.
2. Getters and Setters for Properties
In Kotlin, a property doesn’t require explicit getter or setter methods:
var author: String = "Frank Herbert"
This is the same as defining the following get() and set() methods:
var author: String = "Frank Herbert" get() < return field >set(value)
The default getter and setter is a familiar pattern we see in Java, but in Kotlin, we don’t have to create a private backing field for the property.
We can use dot syntax to call the getter and setter for class properties:
val book = Book() print(book.author) // prints "Frank Herbert" book.author = "Kurt Vonnegut" print(book.author) // prints "Kurt Vonnegut"
Now that we understand the basic functionality of properties let’s look at some ways of modifying them.
2.1. Accessing the Backing Field
Every property we define is backed by a field that can only be accessed within its get() and set() methods using the special field keyword. The field keyword is used to access or modify the property’s value. This allows us to define custom logic within the get() and set() methods:
var rating: Int = 5 get() < if (field < 5) < print("Warning: this is a terrible book") >return field > set(value) < field = when < value >10 -> 10 value < 0 ->0 else -> value > >
Defining a custom getter or setter allows us to perform any number of useful operations like input validation, logging, or data transformations. By adding this business logic directly to the getter or setter, we ensure that it’s always performed when the property is accessed.
Try to avoid or minimize side-effects in the getter and setter methods as much as possible. It makes our code harder to understand.
2.2. val vs. var
If we want to be able to modify a property’s value, we mark it with the var keyword. If we want an immutable property, we mark it with a val keyword. The main difference is that val properties can’t have setters.
A consequence of defining custom getters is that a val property can actually change its value. For example, the isWorthReading property uses the mutable rating property to compute its value:
val isWorthReading get() = this.rating > 5
In this sense, the property acts as a method when using a custom getter. If we need to do an expensive or slow operation to compute the property, it would be better to use a method to provide clarity. Developers may be unaware when using the property that it’s not just retrieving a value from memory.
2.3. Visibility Modifiers
In Java, we often want class fields to have public read access and private write access. Using a public getter method and a private or protected setter method achieves this. Kotlin provides a succinct way to implement this access pattern by allowing visibility modifiers on a property’s set() method:
var inventory: Int = 0 private set
Now any consumers of the book class can read the inventory property, but only the Book class can modify it. Note that the default visibility for properties is public.
The getter will always have the same visibility as the property itself. For example, if the property is private, the getter is private.
3. Java Interoperability
When calling Java code from Kotlin, the compiler will automatically convert Java fields into properties if the getters and setters are properly named. If a Java class has a field accessed by a no-argument method that starts with “get” and modified with a single-argument method that starts with “set”, it becomes a var property in Kotlin.
For example, the getLevel() and setLevel() methods of the Logger class in Java’s logging library:
val logger = Logger.getGlobal() logger.level = Level.SEVERE print(logger.level) // prints "SEVERE"
If the field only has a public getter and no public setter, it becomes a val property in Kotlin. For example, the name field of the Logger class:
print(logger.name) // prints "global" logger.name = "newName" // causes a compiler error
4. Conclusion
In this tutorial, we looked into getters and setters for properties. They provide powerful and concise functionality through optional get() and set() methods. We must be careful not to perform expensive computations in these methods and to minimize side-effects to keep our code clean and understandable.
The code examples used in this tutorial are available over on GitHub.
Getters and setters kotlin
Геттеры (getter) и сеттеры (setter) (еще их называют методами доступа) позволяют управлять доступом к переменной. Их формальный синтаксис:
var имя_свойства[: тип_свойства] [= инициализатор_свойства] [getter] [setter]
Инициализатор, геттер и сеттер свойства необязательны. Указывать тип свойства также необязательно, если он может быть выведен их значения инициализатора или из возвращаемого значения геттера.
Геттеры и сеттеры необязательно определять именно для свойств внутри класса, они могут также применяться к переменным верхнего уровня.
Сеттер
Сеттер определяет логику установки значения переменной. Он определяется с помощью слова set . Например, у нас есть переменная age , которая хранит возраст пользователя и представляет числовое значение.
Но теоретически мы можем установить любой возраст: 2, 6, -200, 100500. И не все эти значения будут корректными. Например, у человека не может быть отрицательного возраста. И для проверки входных значений можно использовать сеттер:
var age: Int = 18 set(value)< if((value>0) and (value <110)) field = value >fun main() < println(age) // 18 age = 45 println(age) // 45 age = -345 println(age) // 45 >
Блок set определяется сразу после свойства, к которому оно относится — в данном случае после свойства age . При этом блок set фактически представляет собой функцию, которая принимает один параметр — value, через этот параметр передается устанавливаемое значение. Например, в выражении age = 45 число 45 и будет представлять тот объект, который будет храниться в value.
В блоке set проверяем, входит ли устанавливаемое значение в диапазон допустимых значений. Если входит, то есть если значение корректно, то передаем его объекту field . Если значение некорректно, то свойство просто сохраняет свое предыдущее значение.
Идентификатор field представляет автоматически генерируемое поле, которое непосредственно хранит значение свойства. То есть свойства фактически представляют надстройку над полями, но напрямую в классе мы не можем определять поля, мы можем работать только со свойствами. Стоит отметить, что к полю через идентификатор field можно обратиться только в геттере или в сеттере, и в каждом конкретном свойстве можно обращаться только к своему полю.
В функции main при втором обращении к сеттеру ( age = -345 ) можно заметить, что значение свойства age не изменилось. Так как новое значение -345 не входит в диапазон от 0 до 110.
геттер
Геттер управляет получением значения свойства и определяется с помощью ключевого слова get :
var age: Int = 18 set(value)< if((value>0) and (value <110)) field = value >get() = field
Справа от выражения get() через знак равно указывается возвращаемое значение. В данном случае возвращается значения поля field , которое хранит значение свойства name. Хотя в таком геттер большого смысла нет, поскольку получить подобное значение мы можем и без геттера.
Если геттер должен содержать больше инструкций, то геттер можно оформить в блок с кодом внутри фигурных скобок:
var age: Int = 18 set(value)< println("Call setter") if((value>0) and (value <110)) field = value >get()
Если геттер оформлен в блок кода, то для возвращения значения необходимо использовать оператор return . И, таким образом, каждый раз, когда мы будем получать значение переменной age (например, в случае с вызовом println(age) ), будет срабатывать геттер, когда возвращает значение. Например:
Консольный вывод программы:
Call getter 18 Call setter Call getter 45
Использование геттеров и сеттеров в классах
Хотя геттеры и сеттеры могут использоваться к глобальным переменным, как правило, они применяются для опосредования доступа к свойствам класса.
fun main() < val bob: Person = Person("Bob") bob.age = 25 // вызываем сеттер println(bob.age) // 25 bob.age = -8 // вызываем сеттер println(bob.age) // 25 >class Person(val name: String)< var age: Int = 1 set(value)< if((value>0) and (value <110)) field = value >>
При втором обращении к сеттеру ( bob.age = -8 ) можно заметить, что значение свойства age не изменилось. Так как новое значение -8 не входит в диапазон от 0 до 110.
Вычисляемый геттер
Геттер может возвращать вычисляемые значения, которые могут задействовать несколько свойств:
fun main() < val tom = Person("Tom", "Smith") println(tom.fullname) // Tom Smith tom.lastname = "Simpson" println(tom.fullname) // Tom Simpson >class Person(var firstname: String, var lastname: String)
Здесь свойство fullname определяет блок get, который возвращает полное имя пользователя, созданное на основе его свойств firstname и lastname. При этом значение самого свойства fullname напрямую мы изменить не можем — оно определено доступно только для чтения. Однако если изменятся значения составляющих его свойств — firstname и lastname, то также изменится значение, возвращаемое из fullname.
Использование полей для хранения значений
Выше уже рассматривалось, что с помощью специального поля field в сеттере и геттере можно обращаться к непосредственному значению свойства, которое хранится в специальном поле. Однако мы сами можем явным образом определить подобное поле. Нередко это приватное поле:
Можно использовать одновременно и геттер, и сеттер:
fun main() < val tom = Person("Tom") println(tom.age) // 1 tom.age = 37 println(tom.age) // 37 tom.age = 156 println(tom.age) // 37 >class Person(val name: String) < private var _age = 1 var age: Int set(value)< if((value >0) and (value < 110)) _age = value >get()= _age >
Здесь для свойства age добавлены геттер и сеттер, которые фактически являются надстройкой над полей _age , которое собственно хранит значение.