As per documentation of When in Kotlin, else is not mandatory if the compiler knows all the values are covered. This is very in case of emums or sealed class but how to do it in case of arrays for numbers 1 to 5 (startRating).
private fun starMapping(startRating: Int): String {
return when (startRating) {
1 -> "Perfect"
2 -> "Great"
3-> "Okay"
4-> "Bad"
5-> "Terrible"
// don't want to add else as I believe it is prone to errors.
}
}
Something similar to this
return when (AutoCompleteRowType.values()[viewType]) {
AutoCompleteRowType.ITEM -> ItemView(
LayoutInflater.from(parent.context).inflate(R.layout.item_venue_autocomplete_item_info, parent, false))
AutoCompleteRowType.SECTION -> SectionView(
LayoutInflater.from(parent.context).inflate(R.layout.item_venue_autocomplete_section, parent, false)
)
}
Using when statement it is impossible to exclude else clause in case of using ints, because compiler doesn't know what to return if startRating is not in 1..5 range. You can, for example, throw an IllegalStateException if the value is not in the required range:
private fun starMapping(startRating: Int): String {
return when (startRating) {
1 -> "Perfect"
2 -> "Great"
3-> "Okay"
4-> "Bad"
5 -> "Terrible"
else -> throw IllegalStateException("Invalid rating param value")
}
}
Or you can do something like this:
return when {
startRating <= 1 -> "Perfect"
startRating == 2 -> "Great"
startRating == 3 -> "Okay"
startRating == 4 -> "Bad"
else -> "Terrible"
}
But else clause is required.
You may not want to use when for this at all. Here is what I would suggest:
You could create an enum class like so:
enum class Rating(val score: Int) {
Perfect(1),
Great(2),
Okay(3),
Bad(4),
Terrible(5)
}
And utilise it like that:
fun ratingForScore(score: Int) = Rating.values().firstOrNull {
it.score == score
}?.toString()
ratingForScore(1) // "Perfect"
I have the following block of code and want to reduce it using Kotlin. How can I do that?
if (name == nameArray[0]) {
// The statement
} else if(name == nameArray[1]) {
// The statement
} else if(name == nameArray[2]) {
// The statement
} else if(name == nameArray[3]) {
// The statement
} else {
// The statement
}
If the array is small and you want to map an action to each index:
You could use indexOfFirst to determine the smallest index which meats your condition. Then you can use a when statement to decide what to do.
when(nameArray.indexOfFirst{ it == name }) {
0 -> // do something
1 -> // do something else
//...
else -> // do something different
}
In case you might want to do the same thing for multiple indices you can use comma separated values. In case the indices are consecutive, you can use ranges:
when(nameArray.indexOfFirst{ it == name }) {
0 -> // do something
1, 2 -> // do the same thing for 1 and 2
in 3..6 -> // do the same thing for 3, 4, 5 and 6
//...
else -> // do something different
}
In order to use this syntax it is a good idea to do index retrieval (like shown) first.
If the array is big and you really only want to check for specific elements:
when(name) {
nameArray[0] -> // do something
nameArray[1] -> // do something
nameArray[2] -> // do something
nameArray[3] -> // do something
else -> // other action
}
You can use when to simplify it as below
when(name) {
nameArray[0] -> //statement
nameArray[1] -> //statement
nameArray[2] -> //statement
nameArray[3] -> //statement
else -> //statement
}
Alternatively, if you can use an enum instead of the nameArray as below
enum class Names {
NAME_1, NAME_2, NAME_3
}
And have name of the Names enum type, you can then use the when clause as below, which is a cleaner way and is more readable
when(name) {
Names.NAME_1 -> //statement
Names.NAME_2 -> //statement
Names.NAME_3 -> //statement
}
You can use a better and more powerful Kotlin construct, when.
It works similarly to switch-case constructs but you can use expression too, check here for more.
Specific to your example you may write:
when (name) {
nameArray[0] -> {
//The statement
}
nameArray[1] -> {
//The statement
}
nameArray[2] -> {
//The statement
}
nameArray[3] -> {
//The statement
}
else -> {
//Executes when conditions are not met
}
}
Maybe I'm misinterpreting what you want to do, but in my opinion a when statement is over-complicating this. In your original code you just want to determine if the array contains the name value in any of the indices from 0 to 3 and respond accordingly.
if ((nameArray.indexOfFirst(name::equals) in 0..3) {
// The statement
} else {
// The else branch
}
Shorter way: Iterate the array instead of if-else every possible index of the array:
fun main(args: Array<String>) {
val nameArray: Array<String> = arrayOf("naam", "nombre", "name", "Name")
val name: String = "name";
for (i in 0..(nameArray.size - 1)) {
if (name == nameArray[i]) {
println("The statement should be executed on index " + i)
}
}
}
Output:
The statement should be executed on index 2
I often stumble upon this problem but don't see a common implementation: how do I idiomatically (functionally) find an element, stop search after the match, and also return a different type (i.e. map whatever matched to another type)?
I've been able to do a workaround with
fun <F,T> Sequence<F>.mapFirst(block: (F) -> T?): T? =
fold(AtomicReference<T>()) { ref, from ->
if (ref.get() != null) return#fold ref
ref.set(block(from))
ref
}.get()
fun main() {
Files.list(someDir).asSequence().map { it.toFile() }.mapFirst { file ->
file.useLines { lines ->
lines.mapFirst { line ->
if (line == "123") line.toInt() else null
}
}
}?.let { num ->
println("num is $num") // will print 123 as an Int
} ?: println("not a single file had a line eq to '123'")
}
But that doesn't stop on the match (when block() returns non-null) and goes to consume all files and all their lines.
A simple for loop is enough to implement mapFirst:
fun <F,T> Sequence<F>.mapFirst(block: (F) -> T?): T? {
for (e in this) {
block(e)?.let { return it }
}
return null
}
If you need a solution without introducing your own extensions (though there's nothing wrong with it), you can use mapNotNull + firstOrNull combination:
files.asSequence()
.mapNotNull { /* read the first line and return not null if it's ok */ }
.firstOrNull()
I would not map the values you discard then, instead do it like this:
sequenceOf(1, 2, 3)
.firstOrNull() { it == 2 }
?.let { it * 2 } ?: 6
First you find the value that matches your condition, then you transform it too whatever you want. In case you don't find a matching element, you assign a default value (in this case 6).
The compiler is showing error Kotlin: Variable result must be initialized.
Here is the code.
fun main(args: Array<String>) {
print("Enter two numbers: ")
// nextDouble() reads the next double from the keyboard
var first= readLine()!!.toDouble()
var second = readLine()!!.toInt()
print("Enter an choice(1-4)): ")
val operator = readLine()!!.toInt()
var result: Double
when (operator) {
1 -> result = first + second
2 -> result = first - second
3 -> result = first * second
4 -> result = first / second
else -> {
println("Error.")
}
}
println("The result is :- " +result)
}
The problem is that when you read the value of result here:
println("The result is :- " +result)
result might not be initialized, because here:
var result: Double
when (operator) {
1 -> result = first + second
2 -> result = first - second
3 -> result = first * second
4 -> result = first / second
else -> {
println("Error.")
}
You are NOT assigning a value to result in the else branch. You have several options, one could be to make result nullable. For instance:
var result = when (operator) {
1 -> first + second
2 -> first - second
3 -> first * second
4 -> first / second
else -> null
}
if (result != null) {
println("The result is :- " + result)
} else {
println("Error.")
}
Notice that if the operator is not (1-4) the value of result will be null. Also, in your code you're printing "Error" and then again you're attempting to print the result.
To add something more, you could make the thing a bit nicer if you defined your operator with the when statement as a method reference:
print("Enter an choice(1-4)): ")
val operatorCode = readLine()!!.toInt()
val operator: ((Int) -> Double)? = when (operatorCode) {
1 -> first::plus
2 -> first::minus
3 -> first::times
4 -> first::div
else -> null
}
if (operator != null) {
val result = operator.invoke(second)
println("The result is :- " + result)
} else {
println("Error.")
}
As jrtapsell points out the main issue is that you declare result to be a Double but you since you don't cover every possible value in the when statement, you are accessing an un-initialized variable.
Another option, if you really operators other than 1-4 want it to be an error condition and don't want result to end up with a valid Double is to use Optionals.
fun main(args: Array<String>) {
print("Enter two numbers: ")
// nextDouble() reads the next double from the keyboard
var first= readLine()!!.toDouble()
var second = readLine()!!.toInt()
print("Enter an choice(1-4)): ")
val operator = readLine()!!.toInt()
var result: Double? = null
when (operator) {
1 -> result = first + second
2 -> result = first - second
3 -> result = first * second
4 -> result = first / second
else -> {
println("Error.")
}
}
// only print the result if not null
result?.let {
println("The result is :- " +result)
}
}
In this case result will be null if the operator is not one of 1-4.
What is the error message trying to tell you
Variable 'result' must be initialized.
This means there are paths through your code where you access result before you have given it a value.
This is a problem because it means you have probably missed off handling a condition.
How do you get through your code without setting result
If the user inputs 5 result is not set
How to fix
Either set result in the else branch, or give it a default value in the declaration
In Kotlin is there an equivalent to the Swift code below?
if let a = b.val {
} else {
}
You can use the let-function like this:
val a = b?.let {
// If b is not null.
} ?: run {
// If b is null.
}
Note that you need to call the run function only if you need a block of code. You can remove the run-block if you only have a oneliner after the elvis-operator (?:).
Be aware that the run block will be evaluated either if b is null, or if the let-block evaluates to null.
Because of this, you usually want just an if expression.
val a = if (b == null) {
// ...
} else {
// ...
}
In this case, the else-block will only be evaluated if b is not null.
Let's first ensure we understand the semantics of the provided Swift idiom:
if let a = <expr> {
// then-block
}
else {
// else-block
}
It means this: "if the <expr> results in a non-nil optional, enter the then-block with the symbol a bound to the unwrapped value. Otherwise enter the else block.
Especially note that a is bound only within the then-block. In Kotlin you can easily get this by calling
<expr>?.also { a ->
// then-block
}
and you can add an else-block like this:
<expr>?.also { a ->
// then-block
} ?: run {
// else-block
}
This results in the same semantics as the Swift idiom.
My answer is totally a copy cat from the others. However, I cannot understand their expression easily. So I guess it would be nice to provide an more understandable answer.
In swift:
if let a = b.val {
//use "a" as unwrapped
}
else {
}
In Kotlin:
b.val?.let{a ->
//use "a" as unwrapped
} ?: run{
//else case
}
if let statement
Swift if let Optional Binding (so called if-let statement) is used to extract a non-optional value if one exists, or to do nothing if a value is nil.
Swift's if-let statement:
let b: Int? = 50
if let a: Int = b {
print("Good news!")
} else {
print("Equal to 'nil' or not set")
}
/* RESULT: Good news! */
In Kotlin, like in Swift, to avoid crashes caused by trying to access a null value when it’s not expected, a specific syntax (like b.let { } in second example) is provided for properly unwrapping nullable types:
Kotlin's equivalent1 of Swift's if-let statement:
val b: Int? = null
val a = b
if (a != null) {
println("Good news!")
} else {
println("Equal to 'null' or not set")
}
/* RESULT: Equal to 'null' or not set */
Kotlin’s let method, when used in combination with the safe-call operator ?:, provides a concise way to handle nullable expressions.
Kotlin's inline let function and Elvis Operator of Swift's nil coalescing operator:
val b: Int? = null
val a = b.let { nonNullable -> nonNullable } ?: "Equal to 'null' or not set"
println(a)
/* RESULT: Equal to 'null' or not set */
guard let statement
guard-let statement in Swift is simple and powerful. It checks for some condition and if it evaluates to be false, then the else statement executes which normally will exit a method.
Let's explore a Swift's guard-let statement:
let b: Int? = nil
func method() {
guard let a: Int = b else {
print("Equal to 'nil' or not set")
return
}
print("Good news!")
}
method()
/* RESULT: Equal to 'nil' or not set */
Kotlin's similar effect of Swift's guard-let statement:
Unlike Swift, in Kotlin, there is no guard statement at all. However, you can use the Elvis Operator – ?: for getting a similar effect.
val b: Int? = 50
fun method() {
val a = b ?: return println("Equal to 'null' or not set")
return println("Good news!")
}
method()
/* RESULT: Good news! */
there are two answers above, both got a lot acceptances:
str?.let{ } ?: run { }
str?.also{ } ?: run { }
Both seem to work in most of the usages, but #1 would fail in the following test:
#2 seems better.
Unlike Swift, Its not necessary to unwrap the optional before using it in Kotlin. We could just check if the value is non null and the compiler tracks the information about the check you performed and allows to use it as unwrapped.
In Swift:
if let a = b.val {
//use "a" as unwrapped
} else {
}
In Kotlin:
if b.val != null {
//use "b.val" as unwrapped
} else {
}
Refer Documentation: (null-safety) for more such use cases
Here's how to only execute code when name is not null:
var name: String? = null
name?.let { nameUnwrapp ->
println(nameUnwrapp) // not printed because name was null
}
name = "Alex"
name?.let { nameUnwrapp ->
println(nameUnwrapp) // printed "Alex"
}
Here's my variant, limited to the very common "if not null" case.
First of all, define this somewhere:
inline fun <T> ifNotNull(obj: T?, block: (T) -> Unit) {
if (obj != null) {
block(obj)
}
}
It should probably be internal, to avoid conflicts.
Now, convert this Swift code:
if let item = obj.item {
doSomething(item)
}
To this Kotlin code:
ifNotNull(obj.item) { item ->
doSomething(item)
}
Note that as always with blocks in Kotlin, you can drop the argument and use it:
ifNotNull(obj.item) {
doSomething(it)
}
But if the block is more than 1-2 lines, it's probably best to be explicit.
This is as similar to Swift as I could find.
There is a similar way in kotlin to achieve Swift's style if-let
if (val a = b) {
a.doFirst()
a.doSecond()
}
You can also assigned multiple nullable values
if (val name = nullableName, val age = nullableAge) {
doSomething(name, age)
}
This kind of approach will be more suitable if the nullable values is used for more than 1 times. In my opinion, it helps from the performance aspect because the nullable value will be checked only once.
source: Kotlin Discussion
I'm adding this answer to clarify the accepted answer because it's too big for a comment.
The general pattern here is that you can use any combination of the Scope Functions available in Kotlin separated by the Elvis Operator like this:
<nullable>?.<scope function> {
// code if not null
} :? <scope function> {
// code if null
}
For example:
val gradedStudent = student?.apply {
grade = newGrade
} :? with(newGrade) {
Student().apply { grade = newGrade }
}
The cleanest option in my opinion is this
Swift:
if let a = b.val {
} else {
}
Kotlin
b.val.also { a ->
} ?: run {
}
Swift if let statement in Kotlin
The short answer is use simple IF-ELSE as by the time of this comment there is no equivalent in Kotlin LET,
if(A.isNull()){
// A is null
}else{
// A is not null
}
we can get the same Unwraping syntax like Swift if let using inline fun
inline fun <T:Any?> T?.unwrap(callback: (T)-> Unit) : Boolean {
return if (this != null) {
this?.let(callback)
true
}else {
false
}
}
Uses: :
val name : String? = null
val rollNo : String? = ""
var namesList: ArrayList<String>? = null
if (name.unwrap { name ->
Log.i("Dhiru", "Name have value on it $name")
})else if ( rollNo.unwrap {
Log.i("Dhiru","Roll have value on it")
}) else if (namesList.unwrap { namesList ->
Log.i("Dhiru","This is Called when names list have value ")
}) {
Log.i("Dhiru","No Field have value on it ")
}
The problem with the Any?.let {} ?: run {} constructions is that:
It only allows for one non-null check per statement
If the let block returns null the run block is evaluated anyway
It's not possible to perform multiple checks in a switch/when style
A solution which tackles most of these problems is to define functions like the following:
private inline fun <A> ifNotNull(p1: A?, block: (A) -> Unit): Unit? {
if (p1 != null) {
return block.invoke(p1)
}
return null
}
private inline fun <A, B> ifNotNull(p1: A?, p2: B?, block: (A, B) -> Unit): Unit? {
if (p1 != null && p2 != null) {
return block.invoke(p1, p2)
}
return null
}
private inline fun <A, B, C> ifNotNull(p1: A?, p2: B?, p3: C?, block: (A, B, C) -> Unit): Unit? {
if (p1 != null && p2 != null && p3 != null) {
return block.invoke(p1, p2, p3)
}
return null
}
This would allow for a statement like:
ifNotNull(a, b) { a, b ->
// code when a, b are not null
} ?:
ifNotNull(c) { c ->
// code when a, b are null and c not null
} ?:
ifNotNull(d, e, f) { d, e, f ->
// code when a, b, c are null and d, e, f not null
} ?: run {
// code which should be performed if a, b, c, d, e and f are null
}
The only caveat is that continue and break statements are not supported if executed within a loop compared to Swift's if let equivalent.
Probably I am very late however the easiest way to unwrap and option is
yourOptionalString ?: return
this was all the following lines will have unwrapped string
If b is a member variable then this approach seems most readable to me:
val b = this.b
if (b == null) {
return
}
println("non nullable : ${b}")
This is also consistent with how it works in swift, where a new local variable shadows the member variable.