I'm trying to make a class Box<T>. It should have two public immutable(val) properties, isContentMandatory: Boolean, content: T?. This is which combination of values I want to accept:
isContentMandatory
content
Should allow?
false
null
YES
false
non-null
YES
true
null
NO
true
non-null
YES
I want to make sure that the constructor(s) I provide won't allow the illegal state of an object. Also, I want multiple constructors(or use default values) so that creation of an object is straight-forward for the client. Following are examples of instantiations:
Box() // OK -> isContentMandatory = false, content = null
Box("some-content") // OK -> isContentMandatory = false, content = "some-content"
Box(false, "some-content") // OK -> isContentMandatory = false, content = "some-content"
Box(true, "some-content") // OK -> isContentMandatory = true, content = "some-content"
Box(true, null) // DON'T ALLOW
Box(true) // DON'T ALLOW
The DON'T ALLOWs from above should preferably be forbidden at compile-time(no constructor available for that combination) if it's possible. Otherwise, fail with the exception during creation.
I'm coming from Java world so all those primary/secondary constructors, default values and etc. are a bit fuzzy so please explain along with a solution. I'm also open to different class design if it supports the same business logic.
EDIT: this is how it would look in Java.
public class Box<T> {
private final boolean isContentMandatory;
private final T content;
public Box() {
this(null);
}
public Box(T content) {
this(false, content);
}
public Box(boolean isContentMandatory, T content) {
if (isContentMandatory && content == null) {
throw new IllegalArgumentException("Invalid combination of parameters");
}
this.isContentMandatory = isContentMandatory;
this.content = content;
}
...getters...
}
Whether or not this is a good approach for the problem is hard to answer without actual domain knowledge of your use case, but it feels to me like it makes little sense that you would make a single class to model those cases which carries around an (otherwise pointless?) boolean to separate the cases.
You could just have 2 classes BoxWithOptionalcontent<T?> and BoxWithContent<T> and you wouldn't need more than the default constructor for either afaict.
sealed interface Box<T: Any?> {
abstract val content: T?
}
data class BoxWithContent<T: Any>(override val content: T): Box<T>
data class BoxWithOptionalContent<T: Any?>(override val content: T? = null): Box<T?>
This shouldn't change much on the initialization site, on the side of the usage you will probably need to add a case statement to decide which case it is and handle appropriately. But you probably already have some similar logic there anyway, and this will probably be a bit more typesafe and readable.
From your Java approach it seems you just want a runtime check, not really preventing bad calls. Just having default arguments and one init block to validate should work:
class Box(val content: T? = null, val isContentMandatory: Boolean = false)
init {
if(content == null && isContentMandatory)
throw RuntimeException("Cannot have no content if content is mandatory")
}
}
Any content given though as a first argument will be valid, so to make this break you have to try harder using Box(isContentMandatory=true) or Box(null, true) explicitly.
Related
we received a crash on Firebase for a kotlin method:
Fatal Exception: java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter code
at [redacted].DeliveryMethod.<init>(:2)
at [redacted].DeliveryMethodsUpdater$addSingleDMInAd$clientCall$1.invokeSuspend(DeliveryMethodsUpdater.kt:121)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
the model is this one:
class DeliveryMethod() {
lateinit var code: String
lateinit var name: String
lateinit var description: String
var isAddressRequired: Boolean? = null
var image: JSONObject? = null
var isDefault: Boolean = false
constructor(code: String) : this() {
this.code = code
}
constructor(code: String, name: String, description: String, image: JSONObject? = null) : this() {
this.code = code
this.name = name
this.description = description
this.image = image
}
}
and the method:
private suspend fun addSingleDMInAd(
adId: Long,
deliveryMethodCode: String
): JoinAdDeliveryMethod? {
var addedDeliveryMethod: JoinAdDeliveryMethod? = null
val clientCall = GlobalScope.async(Dispatchers.IO) {
val cd = CountDownLatch(1)
Client.getInstance().addDeliveryMethodInAd(
adId,
DeliveryMethod(deliveryMethodCode),
object : NetworkCallback<JoinAdDeliveryMethod> {
override fun onSuccess(result: JoinAdDeliveryMethod) {
addedDeliveryMethod = result
cd.countDown()
}
override fun onFailure(err: NetworkError?) {
addedDeliveryMethod = null
cd.countDown()
}
}
)
cd.await()
}
clientCall.await()
return addedDeliveryMethod
}
now, I understand that that the constructor for DeliveryMethod is being called with a null value for code, but I don't understand why the exception only come up at this point. As you can see, the method param is also marked as non-null, and so are previous methods. Shouldn't the exception be thrown way before getting to the constructor call for DeliveryMethod?
EDIT:
this is the caller of addSingleDMinAd():
fun addDeliveryMethodsInAd(
adId: Long,
deliveryMethodCodesToAdd: List<String>,
completionListener: (List<JoinAdDeliveryMethod?>) -> Unit
) {
GlobalScope.launch {
val updatedDms: MutableList<JoinAdDeliveryMethod?> = mutableListOf()
for (deliveryCode in deliveryMethodCodesToAdd) {
addSingleDMInAd(adId = adId, deliveryMethodCode = deliveryCode).run {
updatedDms.add(this)
}
}
completionListener.invoke(updatedDms)
}
}
this is the java caller of the addDeliveryMethodsInAd() (this is inside an Android Service):
new DeliveryMethodsUpdater().addDeliveryMethodsInAd(
result.getId(),
deliveryMethodCodesToAdd,
updatedDMs -> {
// on failed delivery method request
for (JoinAdDeliveryMethod updatedDm : updatedDMs) {
if (updatedDm == null) {
//show error
break;
}
}
AdDetailUpdater
.getInstance()
.updateSubscribersWithDeliveryMethods(result.getId(), updatedDMs);
return null;
}
);
Shouldn't the exception be thrown way before getting to the constructor call for DeliveryMethod?
Within Kotlin, it's not possible for a non-null parameter to be given a null value at runtime accidentally (because the code wouldn't have compiled in the first place). However, this can happen if the value is passed from Java. This is why the Kotlin compiler tries to protect you from Java's null unsafety by generating null-checks at the beginning of some methods (with the intrinsic checkNotNullParameter you're seeing fail here).
However, there is no point in doing that in private or suspend methods since they can only be called from Kotlin (usually), and it would add some overhead that might not be acceptable in performance-sensitive code. That is why these checks are only generated for non-suspend public/protected/internal methods (because their goal is to prevent misuse from Java).
This is why, if you manage to call addSingleDMInAd with a null argument, it doesn't fail with this error. That said, it would be interesting to see how you're getting the null here, because usually the checks at the public API surface are enough. Is some reflection or unsafe cast involved here?
EDIT: with the addition of the calling code, this clears up the problem. You're calling a method that takes a List<String> from Java, with a list that contains nulls. Unfortunately Kotlin only checks the parameters themselves (in this case, it checks that the list itself is not null), it doesn't iterate your list to check for nulls inside. This is why it didn't fail at the public API surface in this case.
Also, the way your model is setup is quite strange. It seems the lateinit is lying because depending on which constructor is used, the properties may actually not be set at all. It would be safer to mark them as nullable to account for when users of that class don't set the value of these properties. Doing this, you won't even need all secondary constructors, and you can just use default values:
class DeliveryMethod() {
var code: String? = null,
var name: String? = null,
var description: String? = null,
var image: JSONObject? = null,
) {
var isAddressRequired: Boolean? = null
var isDefault: Boolean = false
}
Other things of note about addSingleDMInAd:
don't use GlobalScope in this case. If you need to run short-lived coroutines, provide them with a smaller scope that is cancelled when the work is not needed anymore - it ensures no coroutines are leaked. You can read more about the potential pitfalls of GlobalScope and possible replacements in its own doc. That said, you probably shouldn't be starting a coroutine at all anyway here, see next point.
don't use async {} if you use await() right after - it's pointless to start something asynchronous if you wait for it right there. If you want to switch the context to IO, use withContext(Dispatchers.IO) { ... } instead. That said, you shouldn't even need to use the IO dispatcher here, see next point.
don't use CountDownLatch for this purpose. The proper way to encapsulate an async API as a suspend function for coroutines is to use suspendCancellableCoroutine (check its doc, it provides an example on how to use it). Once you use this, there is no need anymore for Dispatchers.IO because it will not be blocking the current thread anymore.
I'm new to Kotlin and I'm trying to understand it, I've just written a simple example that shows how using data classes with maps is a bit tricky, because it seems to me that data classes have a strange behaviour. By default, they define hashCode() based on every property of the class. But they don't define a default equals() method.
This caused to me a lot of confusion because I created a HashMap with a Data Class as a key, but I didn't override hashCode() and equals(). My data class has a MutableList member. When I put an element in the map, I retrieved it using map.get(dataObject) as long as I didn't add an element to the MutableList. After that, even if the data object was still the same, and I found it using map.keys (map.keys.indexOf(dataObject) works), map.get(dataObject) failed, due to the hashCode().
I can fix it using a normal class or adding hashCode() and equals(), removing the MutableList from hashCode(), but I'm wondering if, due to the default behaviour, overriding hashCode() and equals() should be "mandatory" with data classes because otherwise using them with Maps can lead to errors.
Is there something else I can do to avoid this problem?
package cards
data class Player(val name: String, var cards: MutableList<Card>) {
constructor(name: String): this(name, mutableListOf())
//I don't need to define equals, so pointers are checked. But if I don't override hashCode, as it's based
//on every property, the hashCode is calculated considering the content of the MutableList!
// override fun hashCode(): Int {
// return name.hashCode()
// }
}
data class Card(val name: String, val suite: String)
class Game(val players: List<Player>) {
val cardMap: MutableMap<Player, MutableList<Card>> = mutableMapOf()
fun putIntoMapAndGiveCards() {
val newCards = cardMap.getOrDefault(players[0], mutableListOf())
newCards.add(Card(name = "Four", suite = "Clubs"))
cardMap[players[0]] = newCards
//This changes the default hashCode - I can use data classes in a list, but not in a map, because maps are
//based on it.
players[0].cards.add(Card(name = "Five", suite = "Clubs"))
}
fun getFromMap(): MutableList<Card>? {
val player = players[0]
assert(player != null, { "Player from list failure" })
val indexOfPlayer = cardMap.keys.indexOf(player)
assert(indexOfPlayer == 0, { "Player is in the map" })
//Without overriding hashCode, cards is null!
val cards = cardMap.get(players[0])
assert(cards != null, { "Cards from map failure" })
return cards
}
}
fun main() {
val player1 = Player(name = "John")
val game = Game(mutableListOf(player1))
game.putIntoMapAndGiveCards()
game.getFromMap()
?: throw Exception( """Map.get() failure because Player is a data class.
| A data class by default builds its hashCode with every property. As it contains a MutableList,
| the hashCode changes when I add elements to the list. This means that I can't find the element using get()
""".trimMargin())
println("Test finished!")
}
By default, they define hashCode() based on every property of the class. But they don't define a default equals() method
This is not correct. Data classes generate both equals() and hashCode() consistently based on the properties declared in the data class's primary constructor (same goes for toString() btw).
Here is the decompiled code for equals and hashCode of your Player class:
public int hashCode() {
String var10000 = this.name;
int var1 = (var10000 != null ? var10000.hashCode() : 0) * 31;
List var10001 = this.cards;
return var1 + (var10001 != null ? var10001.hashCode() : 0);
}
public boolean equals(#Nullable Object var1) {
if (this != var1) {
if (var1 instanceof Player) {
Player var2 = (Player)var1;
if (Intrinsics.areEqual(this.name, var2.name) && Intrinsics.areEqual(this.cards, var2.cards)) {
return true;
}
}
return false;
} else {
return true;
}
}
Your problem is that you declare your cards mutable list in the primary constructor so it's part of the generated equals and hashCode.
The solution is to move this cards property to the body of your class instead (since it's not part of the player's "core data", but rather part of the state):
data class Player(val name: String) {
val cards: MutableList<Card> = mutableListOf()
}
This way, the generated equals/hashCode pair will only be based on the name property.
Another option obviously is to override both equals and hashCode manually to take only the name into account, but that's tedious and not very idiomatic.
I'm wondering if, due to the default behaviour, overriding hashCode() and equals() should be "mandatory" with data classes because otherwise using them with Maps can lead to errors.
I think you have misdiagnosed the default behaviour. So I'd say on the contrary overriding equals/hashCode is actually not very idiomatic for data classes, and should in general be avoided.
Using data classes is usually safe in maps, as long as the data in the primary constructor is not mutable.
Side notes
you really should not mix var with mutable collections. It creates 2 ways of changing the collection, which is pretty unexpected and error-prone. You should instead either use a val MutableList or a var List, so you can only change the list via mutation, or only change it via assignment, but not both.
if you want to insert the new value into the map, you shouldn't use getOrDefault + assign the value to the key. Instead, use getOrPut directly, so the default value will be inserted without extra work.
why are you both using a cards property on the Player and a Map<Player, List<Card>>? Looks like you have 2 states that can change independently now because those card lists are independent.
I am new to kotlin.
I am reading a key and value from properties file, in a kotlin program. But I don't know how to directly return the value of a key.
Please find the application.yml and abc.class(this is a kotlin class) below.
application.yml
abcconfig:
isabcEnabled:
default: false
xyz: true
def: true
abc.class
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.stereotype.Component
#Component
#ConfigurationProperties(prefix = "abcconfig")
class AbcConfig {
private var abcEnabled: Map<String, Boolean> = mutableMapOf()
fun getabcEnabledValue(siteId: String?): Boolean {
val abc: Boolean
val key: String? = if (abcEnabled.containsKey(key)) key else "default"
abc = abcEnabled[key]
return abc
}
fun setAbcEnabled(abcEnabled: Map<String, Boolean>) {
this.abcEnabled = abcEnabled
}
}
This is about nullability. The Kotlin compiler keeps track of whether each value could be null or not, and prevents you from doing things that would be unsafe.
The code in this question has one particular nullability issue. (It also has some confusion, including two references to key before it's set. I'll assume those should be siteId.)
The issue is what happens when the abcEnabled map doesn't contain the requested key. If the key is present, then the […] operator will return the corresponding Boolean value; but if the key is not present (which could happen if the map doesn't contain a "default" key), it returns null. However, the variable you're trying to assign it to is of type Boolean, which doesn't allow nulls. That's why the compiler complains.
So you'll have to decide what you want to happen if the map contains no "default" key. (Or find a way to ensure it always does; but that's a lot harder, especially if the method could be called before the object is fully initialised, or while another thread is setting or updating the map. So it's much safer to handle the case gracefully.)
If you want to return false in that case, the code could boil down to:
fun getabcEnabledValue(siteId: String?): Boolean {
val key: String? = if (abcEnabled.containsKey(siteId)) siteId else "default"
return abcEnabled[key] ?: false
}
or even (for better thread-safety as well as brevity and clarity):
fun getabcEnabledValue(siteId: String?)
= abcEnabled[siteId] ?: abcEnabled["default"] ?: false
Or if you want to return null in that case, simply declare the function as returning Boolean? (which allows null) — or leave off the ?: false in the last example.
(Also, as a matter of style, I'm not sure why you've made abcEnabled a private property and then added your own setter. Is it really necessary to hide the getter? If not, a public property would be simpler. And it's probably worth making the capitalisation of abc in the method names consistent.)
I have read that using !! should generally be avoided.
Is there a way to write the following code in a more elegant way without having to add something like obsolete null checks and duplicated or dead blocks of code?
class A(var field: Thing?) {
fun getField(): Thing {
if (field == null) {
field = Thing()
}
return field!!
}
}
Also I don't understand why the compiler requires the !!-'pray-this-isn't-null-operator' to be satisfied in this scenario.
EDIT: Consider that it is important to me that a potential solution uses lazy initialization if the field is null!
Problem
As Enzokie already mentioned in the comments, another thread could have changed field after the null check. The compiler has no way of knowing that, so you have to tell it.
class A(var field: Thing?) {
fun getField(): Thing {
if (field == null) {
field = Thing()
}
// another thread could have assigned null to field
return field!! // tell the compiler: I am sure that did not happen
}
}
Solution (Eager)
In you particular case it would be a good idea to use a parameter f (you could name it "field" too, but I avoided that for clarity) in the constructor (without val/var) and afterwards assign it to a property field to which you assign either f or a new instance of Thing.
This can be expressed really concise with the Elvis operator :? which takes the left hand side if not null and the right hand side of the expression otherwise. So, in the end field will be of type Thing.
class A(f: Thing?) {
val field = f ?: Thing() // inferred type Thing
}
Solution (Lazy)
Since it was mentioned by gidds, if you need to initialize field lazyly you could do it like this using delegated properties:
class A(f: Thing?) {
val field by lazy {
f ?: Thing() // inferred type Thing
}
}
The call site does not change:
val a = A(null) // field won't be initialized after this line...
a.field // ... but after this
How about this?
class A(field: Thing?) {
private lateinit var field: Thing
init {
field?.let { this.field = it }
}
fun getField(): Thing {
if (!this::field.isInitialized) {
field = Thing()
}
return field
}
}
When you define a field, you actually define a variable plus two accessor methods:
val counter: Integer = 0
It is possible to customize the accessor methods by writing this instead:
val n = 0
val counter: Integer
get() = n++
This will execute the n++ each time you access the counter field, which therefore returns different values on each access. It is uncommon and unexpected but technically possible.
Therefore the Kotlin compiler cannot assume that two accesses to the same field return the same value twice. Usually they do, but it is not guaranteed.
To work around this, you can read the field once by copying it into a local variable:
fun count() {
val counter = counter
println("The counter is $counter, and it is still $counter.")
}
I wish to have a good example for each function run, let, apply, also, with
I have read this article but still lack of an example
All these functions are used for switching the scope of the current function / the variable. They are used to keep things that belong together in one place (mostly initializations).
Here are some examples:
run - returns anything you want and re-scopes the variable it's used on to this
val password: Password = PasswordGenerator().run {
seed = "someString"
hash = {s -> someHash(s)}
hashRepetitions = 1000
generate()
}
The password generator is now rescoped as this and we can therefore set seed, hash and hashRepetitions without using a variable.
generate() will return an instance of Password.
apply is similar, but it will return this:
val generator = PasswordGenerator().apply {
seed = "someString"
hash = {s -> someHash(s)}
hashRepetitions = 1000
}
val pasword = generator.generate()
That's particularly useful as a replacement for the Builder pattern, and if you want to re-use certain configurations.
let - mostly used to avoid null checks, but can also be used as a replacement for run. The difference is, that this will still be the same as before and you access the re-scoped variable using it:
val fruitBasket = ...
apple?.let {
println("adding a ${it.color} apple!")
fruitBasket.add(it)
}
The code above will add the apple to the basket only if it's not null. Also notice that it is now not optional anymore so you won't run into a NullPointerException here (aka. you don't need to use ?. to access its attributes)
also - use it when you want to use apply, but don't want to shadow this
class FruitBasket {
private var weight = 0
fun addFrom(appleTree: AppleTree) {
val apple = appleTree.pick().also { apple ->
this.weight += apple.weight
add(apple)
}
...
}
...
fun add(fruit: Fruit) = ...
}
Using apply here would shadow this, so that this.weight would refer to the apple, and not to the fruit basket.
Note: I shamelessly took the examples from my blog
There are a few more articles like here, and here that are worth to take a look.
I think it is down to when you need a shorter, more concise within a few lines, and to avoid branching or conditional statement checking (such as if not null, then do this).
I love this simple chart, so I linked it here. You can see it from this as written by Sebastiano Gottardo.
Please also look at the chart accompanying my explanation below.
Concept
I think it as a role playing way inside your code block when you call those functions + whether you want yourself back (to chain call functions, or set to result variable, etc).
Above is what I think.
Concept Example
Let's see examples for all of them here
1.) myComputer.apply { } means you want to act as a main actor (you want to think that you're computer), and you want yourself back (computer) so you can do
var crashedComputer = myComputer.apply {
// you're the computer, you yourself install the apps
// note: installFancyApps is one of methods of computer
installFancyApps()
}.crash()
Yup, you yourself just install the apps, crash yourself, and saved yourself as reference to allow others to see and do something with it.
2.) myComputer.also {} means you're completely sure you aren't computer, you're outsider that wants to do something with it, and also wants it computer as a returned result.
var crashedComputer = myComputer.also {
// now your grandpa does something with it
myGrandpa.installVirusOn(it)
}.crash()
3.) with(myComputer) { } means you're main actor (computer), and you don't want yourself as a result back.
with(myComputer) {
// you're the computer, you yourself install the apps
installFancyApps()
}
4.) myComputer.run { } means you're main actor (computer), and you don't want yourself as a result back.
myComputer.run {
// you're the computer, you yourself install the apps
installFancyApps()
}
but it's different from with { } in a very subtle sense that you can chain call run { } like the following
myComputer.run {
installFancyApps()
}.run {
// computer object isn't passed through here. So you cannot call installFancyApps() here again.
println("woop!")
}
This is due to run {} is extension function, but with { } is not. So you call run { } and this inside the code block will be reflected to the caller type of object. You can see this for an excellent explanation for the difference between run {} and with {}.
5.) myComputer.let { } means you're outsider that looks at the computer, and want to do something about it without any care for computer instance to be returned back to you again.
myComputer.let {
myGrandpa.installVirusOn(it)
}
The Way to Look At It
I tend to look at also and let as something which is external, outside. Whenever you say these two words, it's like you try to act up on something. let install virus on this computer, and also crash it. So this nails down the part of whether you're an actor or not.
For the result part, it's clearly there. also expresses that it's also another thing, so you still retain the availability of object itself. Thus it returns it as a result.
Everything else associates with this. Additionally run/with clearly doesn't interest in return object-self back. Now you can differentiate all of them.
I think sometimes when we step away from 100% programming/logic-based of examples, then we are in better position to conceptualize things. But that depends right :)
There are 6 different scoping functions:
T.run
T.let
T.apply
T.also
with
run
I prepared a visual note as the below to show the differences :
data class Citizen(var name: String, var age: Int, var residence: String)
Decision depends on your needs. The use cases of different functions overlap, so that you can choose the functions based on the specific conventions used in your project or team.
Although the scope functions are a way of making the code more concise, avoid overusing them: it can decrease your code readability and lead to errors. Avoid nesting scope functions and be careful when chaining them: it's easy to get confused about the current context object and the value of this or it.
Here is another diagram for deciding which one to use from https://medium.com/#elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84
Some conventions are as the following :
Use also for additional actions that don't alter the object, such as logging or printing debug information.
val numbers = mutableListOf("one", "two", "three")
numbers
.also { println("The list elements before adding new one: $it") }
.add("four")
The common case for apply is the object configuration.
val adam = Person("Adam").apply {
age = 32
city = "London"
}
println(adam)
If you need shadowing, use run
fun test() {
var mood = "I am sad"
run {
val mood = "I am happy"
println(mood) // I am happy
}
println(mood) // I am sad
}
If you need to return receiver object itself, use apply or also
let, also, apply, takeIf, takeUnless are extension functions in Kotlin.
To understand these function you have to understand Extension functions and Lambda functions in Kotlin.
Extension Function:
By the use of extension function, we can create a function for a class without inheriting a class.
Kotlin, similar to C# and Gosu, provides the ability to extend a class
with new functionality without having to inherit from the class or use
any type of design pattern such as Decorator. This is done via special
declarations called extensions. Kotlin supports extension functions
and extension properties.
So, to find if only numbers in the String, you can create a method like below without inheriting String class.
fun String.isNumber(): Boolean = this.matches("[0-9]+".toRegex())
you can use the above extension function like this,
val phoneNumber = "8899665544"
println(phoneNumber.isNumber)
which is prints true.
Lambda Functions:
Lambda functions are just like Interface in Java. But in Kotlin, lambda functions can be passed as a parameter in functions.
Example:
fun String.isNumber(block: () -> Unit): Boolean {
return if (this.matches("[0-9]+".toRegex())) {
block()
true
} else false
}
You can see, the block is a lambda function and it is passed as a parameter. You can use the above function like this,
val phoneNumber = "8899665544"
println(phoneNumber.isNumber {
println("Block executed")
})
The above function will print like this,
Block executed
true
I hope, now you got an idea about Extension functions and Lambda functions. Now we can go to Extension functions one by one.
let
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
Two Types T and R used in the above function.
T.let
T could be any object like String class. so you can invoke this function with any objects.
block: (T) -> R
In parameter of let, you can see the above lambda function. Also, the invoking object is passed as a parameter of the function. So you can use the invoking class object inside the function. then it returns the R (another object).
Example:
val phoneNumber = "8899665544"
val numberAndCount: Pair<Int, Int> = phoneNumber.let { it.toInt() to it.count() }
In above example let takes String as a parameter of its lambda function and it returns Pair in return.
In the same way, other extension function works.
also
public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
extension function also takes the invoking class as a lambda function parameter and returns nothing.
Example:
val phoneNumber = "8899665544"
phoneNumber.also { number ->
println(number.contains("8"))
println(number.length)
}
apply
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
Same as also but the same invoking object passed as the function so you can use the functions and other properties without calling it or parameter name.
Example:
val phoneNumber = "8899665544"
phoneNumber.apply {
println(contains("8"))
println(length)
}
You can see in the above example the functions of String class directly invoked inside the lambda funtion.
takeIf
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null
Example:
val phoneNumber = "8899665544"
val number = phoneNumber.takeIf { it.matches("[0-9]+".toRegex()) }
In above example number will have a string of phoneNumber only it matches the regex. Otherwise, it will be null.
takeUnless
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null
It is the reverse of takeIf.
Example:
val phoneNumber = "8899665544"
val number = phoneNumber.takeUnless { it.matches("[0-9]+".toRegex()) }
number will have a string of phoneNumber only if not matches the regex. Otherwise, it will be null.
You can view similar answers which is usefull here difference between kotlin also, apply, let, use, takeIf and takeUnless in Kotlin
According to my experience, since such functions are inline syntactic sugar with no performance difference, you should always choose the one that requires writing the least amount of code in the lamda.
To do this, first determine whether you want the lambda to return its result (choose run/let) or the object itself (choose apply/also); then in most cases when the lambda is a single expression, choose the ones with the same block function type as that expression, because when it's a receiver expression, this can be omitted, when it's a parameter expression, it is shorter than this:
val a: Type = ...
fun Type.receiverFunction(...): ReturnType { ... }
a.run/*apply*/ { receiverFunction(...) } // shorter because "this" can be omitted
a.let/*also*/ { it.receiverFunction(...) } // longer
fun parameterFunction(parameter: Type, ...): ReturnType { ... }
a.run/*apply*/ { parameterFunction(this, ...) } // longer
a.let/*also*/ { parameterFunction(it, ...) } // shorter because "it" is shorter than "this"
However, when the lambda consists of a mix of them, it's up to you then to choose the one that fits better into the context or you feel more comfortable with.
Also, use the ones with parameter block function when deconstruction is needed:
val pair: Pair<TypeA, TypeB> = ...
pair.run/*apply*/ {
val (first, second) = this
...
} // longer
pair.let/*also*/ { (first, second) -> ... } // shorter
Here is a brief comparison among all these functions from JetBrains's official Kotlin course on Coursera Kotlin for Java Developers:
I must admit that the difference is not so obvious at first glance, among other things because these 5 functions are often interchangeable. Here is my understanding :
APPLY -> Initialize an object with theses properties and wait for the object
val paint = Paint().apply {
this.style = Paint.Style.FILL
this.color = Color.WHITE
}
LET -> Isolate a piece of code and wait for the result
val result = let {
val b = 3
val c = 2
b + c
}
or
val a = 1
val result = a.let {
val b = 3
val c = 2
it + b + c
}
or
val paint: Paint? = Paint()
paint?.let {
// here, paint is always NOT NULL
// paint is "Paint", not "Paint?"
}
ALSO -> Execute 2 operations at the same time and wait for the result
var a = 1
var b = 3
a = b.also { b = a }
WITH -> Do something with this variable/object and don't wait for a result (chaining NOT allowed )
with(canvas) {
this.draw(x)
this.draw(y)
}
RUN -> Do something with this variable/object and don't wait for a result (chaining allowed)
canvas.run {
this.draw(x)
this.draw(y)
}
or
canvas.run {this.draw(x)}.run {this.draw(x)}