Contents
Miscellaneous
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:
SOURCE
BINARY
RUNTIME
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) {
// 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()
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) {
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
0
if 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 Comparator.reversed(): Comparator
- then: Combines two comparators. The second comparator is only used when the first comparator considers the two values to be equal.
infix fun Comparator.then(comparator: Comparator): Comparator
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 {
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 {
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
Comparable
and compares them using the transformed values.
fun Comparator.thenBy(selector: (T) -> Comparable<*>?): Comparator
- thenByDescending: Similar to
thenBy
, but sorts the instances in descending order.
inline fun Comparator.thenByDescending(crossinline selector: (T) -> Comparable<*>?): Comparator
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 { 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 { 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 Comparator.thenComparator(comparison: (a: T, b: T) -> Int): Comparator
- 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 Comparator.thenDescending(comparator: Comparator): Comparator
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> { 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> { 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 : 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
Triple
into 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 : 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
Pair
into 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 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
apply
is invoked on an object and runs within its context, requiring the object reference.with
does not require an explicit object reference and simply passes the object as an argument.apply
returns the object itself, whilewith
can return a result of the block’s execution.