Kotlin Annotations Overview
In Kotlin, annotations provide a way to attach metadata to code. This metadata can then be used by development tools, libraries, or frameworks to process the code without altering its behavior. Annotations are applied to code elements such as classes, functions, properties, or parameters and are typically evaluated at compile-time.
Annotations frequently contain the following parameters, which must be compile-time constants:
1. Primitive types (e.g., Int, Long, etc.)
2. Strings
3. Enumerations
4. Classes
5. Other annotations
6. Arrays of the types mentioned above
Applying Annotations
To apply an annotation, simply use the annotation name prefixed with the @ symbol before the code element you wish to annotate. For example:
@Positive val number: Int
If an annotation accepts parameters, these can be passed inside parentheses, much like a function call:
@AllowedLanguage("Kotlin")
When passing another annotation as a parameter to an annotation, omit the @ symbol. For instance:
@Deprecated("Use === instead", ReplaceWith("this === other"))
When using class objects as parameters, use ::class:
@Throws(IOException::class)
An annotation that requires parameters looks similar to a class with a primary constructor:
annotation class Prefix(val prefix: String)
Annotating Specific Elements
1. Annotating a Constructor : You can annotate class constructors by using the constructor keyword:
class MyClass @Inject constructor(dependency: MyDependency) {
// ...
}
2. Annotating a Property : Annotations can be applied to properties within a class. For example:
class Language(
@AllowedLanguages(["Java", "Kotlin"]) val name: String
)
Built-in Annotations in Kotlin
Kotlin provides several built-in annotations that offer additional functionality. These annotations are often used to annotate other annotations.
1. @Target: The @Target annotation specifies where an annotation can be applied, such as classes, functions, or parameters. For example:
@Target(AnnotationTarget.CONSTRUCTOR, AnnotationTarget.LOCAL_VARIABLE)
annotation class CustomAnnotation
class Example @CustomAnnotation constructor(val number: Int) {
fun show() {
println("Constructor annotated with @CustomAnnotation")
println("Number: $number")
}
}
fun main() {
val example = Example(5)
example.show()
@CustomAnnotation val message: String
message = "Hello Kotlin"
println("Local variable annotated")
println(message)
}
Output:
Constructor annotated with @CustomAnnotation
Number: 5
Local variable annotated
Hello Kotlin
2. @Retention: The @Retention annotation controls how long the annotation is retained. It can be retained in the source code, in the compiled class files, or even at runtime. The parameter for this annotation is an instance of the AnnotationRetention enum:
SOURCEBINARYRUNTIME
Example:
@Retention(AnnotationRetention.RUNTIME)
annotation class RuntimeAnnotation
@RuntimeAnnotation
fun main() {
println("Function annotated with @RuntimeAnnotation")
}
Output:
Function annotated with @RuntimeAnnotation
3. @Repeatable: The @Repeatable annotation allows multiple annotations of the same type to be applied to an element. This is currently limited to source retention annotations in Kotlin.
Example:
@Repeatable
@Retention(AnnotationRetention.SOURCE)
annotation class RepeatableAnnotation(val value: Int)
@RepeatableAnnotation(1)
@RepeatableAnnotation(2)
fun main() {
println("Multiple @RepeatableAnnotation applied")
}
Output:
Multiple @RepeatableAnnotation applied
Kotlin Reflection
Reflection is a powerful feature that allows a program to inspect and modify its structure and behavior at runtime. Kotlin provides reflection through its kotlin.reflect package, allowing developers to work with class metadata, access members, and use features like functions and property references. Kotlin reflection is built on top of the Java reflection API but extends it with additional features, making it more functional and flexible.
Key Features of Kotlin Reflection
- Access to Properties and Nullable Types: Kotlin reflection enables access to both properties and nullable types.
- Enhanced Features: Kotlin reflection offers more features than Java reflection.
- Interoperability with JVM: Kotlin reflection can seamlessly access and interact with JVM code written in other languages.
Class References in Kotlin Reflection
To obtain a class reference in Kotlin, you can use the class reference operator ::class. Class references can be obtained both statically from the class itself or dynamically from an instance. When acquired from an instance, these are known as bounded class references, which point to the exact runtime type of the object.
Example: Class References
// Sample class
class ReflectionSample
fun main() {
// Reference obtained using class name
val classRef = ReflectionSample::class
println("Static class reference: $classRef")
// Reference obtained using an instance
val instance = ReflectionSample()
println("Bounded class reference: ${instance::class}")
}
Output:
Static class reference: class ReflectionSample
Bounded class reference: class ReflectionSample
Function References
In Kotlin, you can obtain a reference to any named function by using the :: operator. Function references can be passed as parameters or stored in variables. When dealing with overloaded functions, you may need to specify the function type explicitly.
Example: Function References
fun sum(a: Int, b: Int): Int = a + b
fun concat(a: String, b: String): String = "$a$b"
fun isEven(a: Int): Boolean = a % 2 == 0
fun main() {
// Function reference for a single function
val isEvenRef = ::isEven
val numbers = listOf(1, 2, 3, 4, 5, 6)
println(numbers.filter(isEvenRef))
// Function reference for an overloaded function (explicit type)
val concatRef: (String, String) -> String = ::concat
println(concatRef("Hello, ", "Kotlin!"))
// Implicit function reference usage
val result = sum(3, 7)
println(result)
}
Output:
[2, 4, 6]
Hello, Kotlin!
10
Property References
Property references allow you to work with properties just like you do with functions. You can retrieve the property value using the get function, and you can modify it using set if it’s mutable.
Example: Property References
class SampleProperty(var value: Double)
val x = 42
fun main() {
// Property reference for a top-level property
val propRef = ::x
println(propRef.get()) // Output: 42
println(propRef.name) // Output: x
// Property reference for a class property
val classPropRef = SampleProperty::value
val instance = SampleProperty(12.34)
println(classPropRef.get(instance)) // Output: 12.34
}
Output:
42
x
12.34
Nested Class Property - Function Executed
Constructor References
Constructor references in Kotlin allow you to reference the constructor of a class in a similar manner to functions and properties. These references can be used to invoke constructors dynamically.
Example: Constructor References
class SampleClass(val value: Int)
fun main() {
// Constructor reference
val constructorRef = ::SampleClass
val instance = constructorRef(10)
println("Value: ${instance.value}") // Output: Value: 10
}
Output:
Value: 10
Operator Overloading
In Kotlin, you have the flexibility to overload standard operators to work seamlessly with user-defined types. This means that you can provide custom behavior for operators like +, -, *, and more, making code that uses your custom types more intuitive. Kotlin allows overloading for unary, binary, relational, and other operators by defining specific functions using the operator keyword.
Unary Operators
Unary operators modify a single operand. The corresponding functions for unary operators must be defined in the class that they will operate on.
| Operator Expression | Corresponding Function |
|---|---|
+x, -x | x.unaryPlus(), x.unaryMinus() |
!x | x.not() |
Here, x is the instance on which the operator is applied.
Example: Unary Operator Overloading
class UnaryExample(var message: String) {
// Overloading the unaryMinus operator
operator fun unaryMinus() {
message = message.reversed()
}
}
fun main() {
val obj = UnaryExample("KOTLIN")
println("Original message: ${obj.message}")
// Using the overloaded unaryMinus function
-obj
println("After applying unary operator: ${obj.message}")
}
Output:
Original message: KOTLIN
After applying unary operator: NILTOK
Increment and Decrement Operators
Increment (++) and decrement (--) operators can be overloaded using the following functions. These functions typically return a new instance after performing the operation.
| Operator Expression | Corresponding Function |
|---|---|
++x or x++ | x.inc() |
--x or x-- | x.dec() |
Example: Increment and Decrement Operator Overloading
class IncDecExample(var text: String) {
// Overloading the increment function
operator fun inc(): IncDecExample {
return IncDecExample(text + "!")
}
// Overloading the decrement function
operator fun dec(): IncDecExample {
return IncDecExample(text.dropLast(1))
}
override fun toString(): String {
return text
}
}
fun main() {
var obj = IncDecExample("Hello")
println(obj++) // Output: Hello
println(obj) // Output: Hello!
println(obj--) // Output: Hello
println(obj) // Output: Hello
}
Output:
Hello
Hello!
Hello
Hello
Binary Operators
Binary operators operate on two operands. The following table shows how to define functions for common binary operators.
| Operator Expression | Corresponding Function |
|---|---|
x1 + x2 | x1.plus(x2) |
x1 - x2 | x1.minus(x2) |
x1 * x2 | x1.times(x2) |
x1 / x2 | x1.div(x2) |
x1 % x2 | x1.rem(x2) |
Example: Overloading the + Operator
class DataHolder(var name: String) {
// Overloading the plus operator
operator fun plus(number: Int) {
name = "Data: $name, Number: $number"
}
override fun toString(): String {
return name
}
}
fun main() {
val obj = DataHolder("Info")
obj + 42 // Calling the overloaded plus operator
println(obj) // Output: Data: Info, Number: 42
}
Output:
Data: Info, Number: 42
Other Operators
Kotlin provides the flexibility to overload a wide variety of operators, some of which include range, contains, indexing, and invocation.
| Operator Expression | Corresponding Function |
|---|---|
x1 in x2 | x2.contains(x1) |
x[i] | x.get(i) |
x[i] = value | x.set(i, value) |
x() | x.invoke() |
x1 += x2 | x1.plusAssign(x2) |
Example: Overloading the get Operator for Indexing
class CustomList(val items: List<String>) {
// Overloading the get operator to access list items
operator fun get(index: Int): String {
return items[index]
}
}
fun main() {
val myList = CustomList(listOf("Kotlin", "Java", "Python"))
println(myList[0]) // Output: Kotlin
println(myList[2]) // Output: Python
}
Output:
Kotlin
Python
Destructuring Declarations in Kotlin
Kotlin offers a distinctive way of handling instances of a class through destructuring declarations. A destructuring declaration lets you break down an object into multiple variables at once, making it easier to work with data.
Example:
val (id, pay) = employee
In this example, id and pay are initialized using the properties of the employee object. These variables can then be used independently in the code:
println("$id $pay")
Destructuring declarations rely on component() functions. For each variable in a destructuring declaration, the corresponding class must provide a componentN() function, where N represents the variable’s position (starting from 1). In Kotlin, data classes automatically generate these component functions.
Destructuring Declaration Compiles to:
val id = employee.component1()
val pay = employee.component2()
Example: Returning Two Values from a Function
// Data class example
data class Info(val title: String, val year: Int)
// Function returning a data class
fun getInfo(): Info {
return Info("Inception", 2010)
}
fun main() {
val infoObj = getInfo()
// Accessing properties using the object
println("Title: ${infoObj.title}")
println("Year: ${infoObj.year}")
// Using destructuring declaration
val (title, year) = getInfo()
println("Title: $title")
println("Year: $year")
}
Output:
Title: Inception
Year: 2010
Title: Inception
Year: 2010
Underscore for Unused Variables
Sometimes you may not need all the variables in a destructuring declaration. To skip a variable, you can replace its name with an underscore (_). In this case, the corresponding component function is not called.
Destructuring in Lambdas
As of Kotlin 1.1, destructuring declarations can also be used within lambda functions. If a lambda parameter is of type Pair or any type that provides component functions, you can destructure it within the lambda.
Example: Destructuring in Lambda Parameters
fun main() {
val people = mutableMapOf<Int, String>()
people[1] = "Alice"
people[2] = "Bob"
people[3] = "Charlie"
println("Original map:")
println(people)
// Destructuring map entry into key and value
val updatedMap = people.mapValues { (_, name) -> "Hello $name" }
println("Updated map:")
println(updatedMap)
}
Output:
Original map:
{1=Alice, 2=Bob, 3=Charlie}
Updated map:
{1=Hello Alice, 2=Hello Bob, 3=Hello Charlie}
In this example, the mapValues function uses destructuring to extract the value and update it. The underscore (_) is used for the key, as it is not needed.
Equality evaluation
Kotlin offers a distinct feature that allows comparison of instances of a particular type in two different ways. This feature sets Kotlin apart from other programming languages. The two types of equality in Kotlin are:
Structural Equality
Structural equality is checked using the == operator and its inverse, the != operator. By default, when you use x == y, it is translated to a call of the equals() function for that type. The expression:
x?.equals(y) ?: (y === null)
It means that if x is not null, it calls the equals(y) function. If x is null, it checks whether y is also referentially equal to null. Note: When x == null, the code automatically defaults to referential equality (x === null), so there’s no need to optimize the code in this case. To use == on instances, the type must override the equals() function. For example, when comparing strings, the structural equality compares their contents.
Referential Equality
Referential equality in Kotlin is checked using the === operator and its inverse !==. This form of equality returns true only when both instances refer to the same location in memory. When used with types that are converted to primitive types at runtime, the === check is transformed into ==, and the !== check is transformed into !=.
Here is a Kotlin program to demonstrate structural and referential equality:
class Circle(val radius: Int) {
override fun equals(other: Any?): Boolean {
if (other is Circle) {
return other.radius == radius
}
return false
}
}
// main function
fun main(args: Array<String>) {
val circle1 = Circle(7)
val circle2 = Circle(7)
// Structural equality
if (circle1 == circle2) {
println("Two circles are structurally equal")
}
// Referential equality
if (circle1 !== circle2) {
println("Two circles are not referentially equal")
}
}
Output:
Two circles are structurally equal
Two circles are not referentially equal
Comparator
In programming, when defining a new type, there’s often a need to establish an order for its instances. To compare instances, Kotlin provides the Comparable interface. However, for more flexible and customizable ordering based on different parameters, Kotlin offers the Comparator interface. This interface compares two objects of the same type and arranges them in a defined order.
Functions
- compare: This method compares two instances of a type. It returns
0if both are equal, a negative number if the second instance is greater, or a positive number if the first instance is greater.
abstract fun compare(a: T, b: T): Int
Extension Functions
- reversed: This function takes a comparator and reverses its sorting order.
fun <T> Comparator<T>.reversed(): Comparator<T>
- then: Combines two comparators. The second comparator is only used when the first comparator considers the two values to be equal.
infix fun <T> Comparator<T>.then(comparator: Comparator<in T>): Comparator<T>
Example demonstrating compare, then, and reversed functions:
// A simple class representing a car
class Car(val make: String, val year: Int) {
override fun toString(): String {
return "$make ($year)"
}
}
// Comparator to compare cars by make
class MakeComparator : Comparator<Car> {
override fun compare(o1: Car?, o2: Car?): Int {
if (o1 == null || o2 == null) return 0
return o1.make.compareTo(o2.make)
}
}
// Comparator to compare cars by year
class YearComparator : Comparator<Car> {
override fun compare(o1: Car?, o2: Car?): Int {
if (o1 == null || o2 == null) return 0
return o1.year.compareTo(o2.year)
}
}
fun main() {
val cars = arrayListOf(
Car("Toyota", 2020),
Car("Ford", 2018),
Car("Toyota", 2015),
Car("Ford", 2022),
Car("Tesla", 2021)
)
println("Original list:")
println(cars)
val makeComparator = MakeComparator()
// Sorting cars by make
cars.sortWith(makeComparator)
println("List sorted by make:")
println(cars)
val yearComparator = YearComparator()
val combinedComparator = makeComparator.then(yearComparator)
// Sorting cars by make, then by year
cars.sortWith(combinedComparator)
println("List sorted by make and year:")
println(cars)
val reverseComparator = combinedComparator.reversed()
// Reverse sorting the cars
cars.sortWith(reverseComparator)
println("List reverse sorted:")
println(cars)
}
Output:
Original list:
[Toyota (2020), Ford (2018), Toyota (2015), Ford (2022), Tesla (2021)]
List sorted by make:
[Ford (2018), Ford (2022), Tesla (2021), Toyota (2015), Toyota (2020)]
List sorted by make and year:
[Ford (2018), Ford (2022), Tesla (2021), Toyota (2015), Toyota (2020)]
List reverse sorted:
[Toyota (2020), Toyota (2015), Tesla (2021), Ford (2022), Ford (2018)]
Additional Extension Functions
- thenBy: This function converts the instances of a type to a
Comparableand compares them using the transformed values.
fun <T> Comparator<T>.thenBy(selector: (T) -> Comparable<*>?): Comparator<T>
- thenByDescending: Similar to
thenBy, but sorts the instances in descending order.
inline fun <T> Comparator<T>.thenByDescending(crossinline selector: (T) -> Comparable<*>?): Comparator<T>
Example demonstrating thenBy and thenByDescending functions:
class Product(val price: Int, val rating: Int) {
override fun toString(): String {
return "Price = $price, Rating = $rating"
}
}
fun main() {
val comparator = compareBy<Product> { it.price }
val products = listOf(
Product(100, 4),
Product(200, 5),
Product(150, 3),
Product(100, 3),
Product(200, 4)
)
println("Sorted first by price, then by rating:")
val priceThenRatingComparator = comparator.thenBy { it.rating }
println(products.sortedWith(priceThenRatingComparator))
println("Sorted by rating, then by descending price:")
val ratingThenPriceDescComparator = compareBy<Product> { it.rating }
.thenByDescending { it.price }
println(products.sortedWith(ratingThenPriceDescComparator))
}
Output:
Sorted first by price, then by rating:
[Price = 100, Rating = 3, Price = 100, Rating = 4, Price = 150, Rating = 3, Price = 200, Rating = 4, Price = 200, Rating = 5]
Sorted by rating, then by descending price:
[Price = 150, Rating = 3, Price = 100, Rating = 3, Price = 100, Rating = 4, Price = 200, Rating = 4, Price = 200, Rating = 5]
Additional Functions
- thenComparator: Combines a primary comparator with a custom comparison function.
fun <T> Comparator<T>.thenComparator(comparison: (a: T, b: T) -> Int): Comparator<T>
- thenDescending: Combines two comparators and sorts the elements in descending order based on the second comparator if the values are equal according to the first.
infix fun <T> Comparator<T>.thenDescending(comparator: Comparator<in T>): Comparator<T>
Example demonstrating thenComparator and thenDescending functions:
fun main() {
val pairs = listOf(
Pair("Apple", 5),
Pair("Banana", 2),
Pair("Apple", 3),
Pair("Orange", 2),
Pair("Banana", 5)
)
val comparator = compareBy<Pair<String, Int>> { it.first }
.thenComparator { a, b -> compareValues(a.second, b.second) }
println("Pairs sorted by first element, then by second:")
println(pairs.sortedWith(comparator))
val descendingComparator = compareBy<Pair<String, Int>> { it.second }
.thenDescending(compareBy { it.first })
println("Pairs sorted by second element, then by first in descending order:")
println(pairs.sortedWith(descendingComparator))
}
Output:
Pairs sorted by first element, then by second:
[(Apple, 3), (Apple, 5), (Banana, 2), (Banana, 5), (Orange, 2)]
Pairs sorted by second element, then by first in descending order:
[(Banana, 5), (Apple, 5), (Banana, 2), (Orange, 2), (Apple, 3)]
Triple
In programming, functions are invoked to perform specific tasks. A key benefit of using functions is their ability to return values after computation. For instance, an add() function consistently returns the sum of the input numbers. However, a limitation of functions is that they typically return only one value at a time. When there’s a need to return multiple values of different types, one approach is to define a class with the desired variables and then return an object of that class. This method, though effective, can lead to increased verbosity, especially when dealing with multiple functions requiring multiple return values.
To simplify this process, Kotlin provides a more elegant solution through the use of Pair and Triple.
What is Triple?
Kotlin offers a simple way to store three values in a single object using the Triple class. This is a generic data class that can hold any three values. The values in a Triple have no inherent relationship beyond being stored together. Two Triple objects are considered equal if all three of their components are identical.
Class Definition:
data class Triple<out A, out B, out C> : Serializable
Parameters:
- A: The type of the first value.
- B: The type of the second value.
- C: The type of the third value.
Constructor:
In Kotlin, constructors initialize variables or properties of a class. To create an instance of Triple, you use the following syntax:
Triple(first: A, second: B, third: C)
Example: Creating a Triple
fun main() {
val (a, b, c) = Triple(42, "Hello", true)
println(a)
println(b)
println(c)
}
Output:
42
Hello
true
Properties:
You can either deconstruct the values of a Triple into separate variables (as shown above), or you can access them using the properties first, second, and third:
- first: Holds the first value.
- second: Holds the second value.
- third: Holds the third value.
Example: Accessing Triple Values Using Properties
fun main() {
val triple = Triple("Kotlin", 1.6, listOf(100, 200, 300))
println(triple.first)
println(triple.second)
println(triple.third)
}
Output:
Kotlin
1.6
[100, 200, 300]
Functions:
- toString(): This function returns a string representation of the
Triple.Example: UsingtoString()
fun main() {
val triple1 = Triple(10, 20, 30)
println("Triple as string: " + triple1.toString())
val triple2 = Triple("A", listOf("X", "Y", "Z"), 99)
println("Another Triple as string: " + triple2.toString())
}
Output:
Triple as string: (10, 20, 30)
Another Triple as string: (A, [X, Y, Z], 99)
Extension Functions:
Kotlin also allows you to extend existing classes with new functionality through extension functions.
- toList(): This extension function converts the
Tripleinto a list. Example: UsingtoList()
fun main() {
val triple1 = Triple(1, 2, 3)
val list1 = triple1.toList()
println(list1)
val triple2 = Triple("Apple", 3.1415, listOf(7, 8, 9))
val list2 = triple2.toList()
println(list2)
}
Output:
[1, 2, 3]
[Apple, 3.1415, [7, 8, 9]]
Pair
In programming, we often use functions to perform specific tasks. One of the advantages of functions is their ability to be called multiple times, consistently returning a result after computation. For example, an add() function always returns the sum of two given numbers.
However, functions typically return only one value at a time. When there’s a need to return multiple values of different data types, one common approach is to create a class containing the required variables, then instantiate an object of that class to hold the returned values. While effective, this approach can make the code verbose and complex, especially when many functions return multiple values.
To simplify this, Kotlin provides the Pair and Triple data classes.
What is Pair?
Kotlin offers a simple way to store two values in a single object using the Pair class. This generic class can hold two values, which can be of the same or different data types. The two values may or may not have a relationship. Comparison between two Pair objects is based on their values: two Pair objects are considered equal if both of their values are identical.
Class Definition:
data class Pair<out A, out B> : Serializable
Parameters:
- A: The type of the first value.
- B: The type of the second value.
Constructor:
Kotlin constructors are special functions that are called when an object is created, primarily to initialize variables or properties. To create an instance of Pair, use the following syntax:
Pair(first: A, second: B)
Example: Creating a Pair
fun main() {
val (a, b) = Pair(42, "World")
println(a)
println(b)
}
Output:
42
World
Properties:
You can either destructure a Pair into separate variables (as shown above), or access the values using the properties first and second:
- first: Holds the first value.
- second: Holds the second value.
Example: Accessing Pair Values Using Properties
fun main() {
val pair = Pair("Hello Kotlin", "This is a tutorial")
println(pair.first)
println(pair.second)
}
Output:
Hello Kotlin
This is a tutorial
Functions:
- toString(): This function returns a string representation of the
Pair. Example: UsingtoString()
fun main() {
val pair1 = Pair(10, 20)
println("Pair as string: " + pair1.toString())
val pair2 = Pair("Alpha", listOf("Beta", "Gamma", "Delta"))
println("Another Pair as string: " + pair2.toString())
}
Output:
Pair as string: (10, 20)
Another Pair as string: (Alpha, [Beta, Gamma, Delta])
Extension Functions:
Kotlin allows extending existing classes with new functionality using extension functions.
- toList(): This extension function converts the
Pairinto a list.
Example: Using toList()
fun main() {
val pair1 = Pair(3, 4)
val list1 = pair1.toList()
println(list1)
val pair2 = Pair("Apple", "Orange")
val list2 = pair2.toList()
println(list2)
}
Output:
[3, 4]
[Apple, Orange]
apply vs with
In Kotlin, apply is an extension function that operates within the context of the object it is invoked on. It allows you to configure or manipulate the object’s properties within its scope and returns the same object after performing the desired changes. The primary use of apply is not limited to just setting properties; it can execute more complex logic before returning the modified object.
Key characteristics of apply:
- It is an extension function on a type.
- It requires an object reference to execute within an expression.
- After completing its operation, it returns the modified object.
Definition of apply:
inline fun T.apply(block: T.() -> Unit): T {
block()
return this
}
Example of apply:
fun main() {
data class Example(var value1: String, var value2: String, var value3: String)
// Creating an instance of Example class
var example = Example("Hello", "World", "Before")
// Using apply to change the value3
example.apply { this.value3 = "After" }
println(example)
}
Example Demonstrating Interface Implementation:
interface Machine {
fun powerOn()
fun powerOff()
}
class Computer : Machine {
override fun powerOn() {
println("Computer is powered on.")
}
override fun powerOff() {
println("Computer is shutting down.")
}
}
fun main() {
val myComputer = Computer()
myComputer.powerOn()
myComputer.powerOff()
}
Output:
Example(value1=Hello, value2=World, value3=After)
In this example, the third property value3 of the Example class is modified from "Before" to "After" using apply.
Kotlin: with
Similar to apply, the with function in Kotlin is used to modify properties of an object. However, unlike apply, with does not require the object reference explicitly. Instead, the object is passed as an argument, and the operations are performed without using the dot operator for the object reference.
Definition of with:
inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
Example of with:
fun main() {
data class Example(var value1: String, var value2: String, var value3: String)
var example = Example("Hello", "World", "Before")
// Using with to modify value1 and value3
with(example) {
value1 = "Updated"
value3 = "After"
}
println(example)
}
Output:
Example(value1=Updated, value2=World, value3=After)
In this case, using with, we update the values of value1 and value3 without needing to reference the object with a dot operator.
Difference Between apply and with
applyis invoked on an object and runs within its context, requiring the object reference.withdoes not require an explicit object reference and simply passes the object as an argument.applyreturns the object itself, whilewithcan return a result of the block’s execution.
Leave a Reply