Why can't Kotlin variables assigned from fields change in function? - kotlin

I'm trying to understand why can't a variable that is assigned from a class field change in a function. Consider this example:
class A {
private var a: String? = "a"
fun hello() {
val b = a
if (b != null) {
// The compiler assumes that 'b' is always not null inside this branch
b.length
} else {
// is null
}
}
}
Variable b is going to always be the same throughout the function (later is implicit non null in the if branch). So even if through concurrency I change the field a, the variable b does not change?
How is this possible? does Kotlin make a copy of the variable and assigns it to b? I thought assignments worked with references and such.
This can happen, as far as I'm concerned, with any type (String, Int, custom class, etc)

Strings are immutable, there's no way you can change them after you instantiated them. Thus, if another thread modifies the value of a after b != null was evaluated, it will not affect b's value.
Actually it's not even the real answer because here we're just talking about nullity. b will point to whatever object was pointed by a at the moment of the assignment, but then nothing can change what b is pointing to because b is a val.
I was just mentioning immutability because the content of b could still change through mutation if the type was mutable, but the reference itself will never change.
For more clarity, let's make a scenario:
[thread 1] a = "hello" <state: a => h: String("hello")>
[thread 1] b = a <state: a => h, b => h>
[thread 2] a = "world" <state: a => w: String("world"), b => h>
In short, b points to the same object as a at the time of assignment, but it doesn't point to a itself, so when a changes, b doesn't care.

Related

How to use `when` with 2 sealed classes and getting the inner value?

Consider this extreme simplified code (available on https://pl.kotl.in/bb2Irv8dD):
sealed class Person {
data class A(val i: Int) :
Person()
}
fun main() {
val a = Person.A(i = 0)
val b = Person.A(i = 1)
// Compiles
when (a) {
is Person.A -> print("I have access to {$a.i}")
}
// Does not compile :(
when (a to b) {
is Person.A to is Person.A -> print("I have access to {$a.i} and b {$b.i}")
}
}
Why does the (a to b) code not work? It works for 1 variable, I was hoping I can match on both classes and get both inner values.
The error is:
Incompatible types: Person.A and Pair<Person.A, Person.A> Expecting
'->' Expecting an element Incompatible types: Person.A and
Pair<Person.A, Person.A>
Aside from that syntax not being supported (you can only use is on one thing in a when branch), by using to you're literally creating an instance of the Pair class.
Pair uses generics for the types of its two variables, so this type information is lost at runtime due to type erasure.
So although, you can do this:
when (a to b) {
is Pair<Person.A, Person.A> -> print("I have access to {$a.i} and b {$b.i}")
}
it is only allowed when both a and b are local variables whose types are declared locally, so that the generic types of the Pair are known at compile time. But this makes it mostly useless, because if a and b are local variables with known type at compile time, then you could just replace the above with true or false.
To be able to do something like this in a general way, you must either create local variables to use:
val aIsTypeA = a is Person.A
val bIsTypeA = b is Person.A
when (aIsTypeA to bIsTypeA) {
true to true -> //...
//...
}
or use when without a subject and put the full condition on each branch:
when {
a is Person.A && b is Person.A -> //...
//...
}
The (a to b) returns a Pair<Person.A,Person.A> but what you are checking is Type Person.A to Type Person.A instead of the Type Pair<Person.A,Person.A>.
What you can do instead is:
when (a to b) {
is Pair<Person.A,Person.A> -> print("I have access to {$a.i} and b {$b.i}")
}

Assignment after not null check

I expected that the type of a variable is promoted to a non-null type after a not-null check (like in the Dart language).
val someMap = mapOf("a" to 0L)
val a = someMap['a'] // a is of type Long?
if (a != null) {
val b = a // b is of type Long? and not of type Long. Why?
}
Can someone explain why this is not the case? Just a matter of taste of the language designers?
Since there is smart-casting, it doesn't matter. It will allow you to use members of a or b inside the if statement without null-safe calls (?.) or null assertions (!!). You can also safely declare b to be a Long without the compiler complaining:
if (a != null) {
val b: Long = a
}
It is I think a design choice for how implicit types should be inferred that b's type must be explicitly declared if you want it to be considered non-nullable. This is only relevant if passing it to a function with generics, since there is smart-casting.
What you can do instead of explicit null check is using let{} as follows:
val someMap = mapOf('a' to 0L)
val a = someMap['a'] // a is of type Long?
a?.let {
val b = it // b is of type Long
}
It is called smart casting, basically Kotlin is smart enough to determine that variable can no longer be null after check. More detail and can be found here if you are interested
As to why, only the creators of kotlin can know. But what you can do is this if you want a Long instead of Long? there is this
val b = a!!

Kotlin: Why does val b (Map) change over the iteration even though actions occur over another var a in do-while loop?

Why does such code only occur in one iteration? Why does "b" change simultaneously with "a" after assignment before the end of the iteration?
I made a similar code where (a) and (b) are integers, then (b) does not change until the next iteration. Why does it behave differently with Map?
var a = mutableMapOf("z" to 1)
do {
val b = a
a["x"] = 2
// why here b == a in the first iteration?
} while (a != b)
According to #jsamol comment, it says:
"Similar to Java, Kotlin never implicitly copies objects on assignment. Variables always hold references to objects, and assigning an expression to a variable only copies a reference to the object, not the object itself."
I've changed the condition so I can compare integers, not maps. How it works.
var a = mutableMapOf("z" to 1)
do {
val b = a.size
a["x"] = 2
} while (a.size != b)

How to avoid !! in a function which returns a non-nullable

In the sample below, the function should return a non-null data.
Since the data could be changed in the process, it needs to be var, and can only be nullable to start with.
I can't use lateinit because the first call of if (d == null) will throw.
After the process it will be assigned a non-null data, but the return has to use the !! (double bang or non-null assertion operator).
What is the best approach to avoid the !!?
fun testGetLowest (dataArray: List<Data>) : Data {
var d: Data? = null
for (i in dataArray.indecs) {
if (d == null) {// first run
d = dataArray[i]
} else if {
d.level < dataArray[i].level
d = dataArray[i]
}
}
return d!!
}
If you don't like !! then supply a default value for it. You'll realize you can only supply the default value if the list is not empty, but, as you said, the list is already known to be non-empty. The good part of this story is that the type system doesn't track list size so when you say dataArray[0], it will take your word for it.
fun testGetLowest(dataArray: List<Data>) : Data {
var d: Data = dataArray[0]
for (i in 1 until dataArray.size) {
if (d.level < dataArray[i].level) {
d = dataArray[i]
}
}
return d
}
Normally, you can and should lean on the compiler to infer nullability. This is not always possible, and in the contrived example if the inner loop runs but once d is non-null. This is guaranteed to happen if dataArray has at least one member.
Using this knowledge you could refactor the code slightly using require to check the arguments (for at least one member of the array) and checkNotNull to assert the state of the dataArray as a post-condition.
fun testGetLowest (dataArray: List<Data>) : Data {
require(dataArray.size > 0, { "Expected dataArray to have size of at least 1: $dataArray")
var d: Data? = null
for (i in dataArray.indecs) {
if (d == null) {// first run
d = dataArray[i]
} else if {
d.level < dataArray[i].level
d = dataArray[i]
}
}
return checkNotNull(d, { "Expected d to be non-null through dataArray having at least one element and d being assigned in first iteration of loop" })
}
Remember you can return the result of a checkNotNull (and similar operators):
val checkedD = checkNotNull(d)
See Google Guava's Preconditions for something similar.
Even if you were to convert it to an Option, you would still have to deal with the case when dataArray is empty and so the value returned is undefined.
If you wanted to make this a complete function instead of throwing an exception, you can return an Option<Data> instead of a Data so that the case of an empty dataArray would return a None and leave it up to the caller to deal with how to handle the sad path.
How to do the same check, and cover the empty case
fun testGetLowest(dataArray: List<Data>)
= dataArray.minBy { it.level } ?: throw AssertionError("List was empty")
This uses the ?: operator to either get the minimum, or if the minimum is null (the list is empty) throws an error instead.
The accepted answer is completly fine but just to mentioned another way to solve your problem by changing one line in your code: return d ?: dataArray[0]

How to explain these Kotlin codes about 'Int' type?

var a: Int = 10000
var b: Int = 10000
print(b === a) // Prints 'true'
The official doc says: "a === b evaluates to true if and only if a and b point to the same object."
In the codes above, what's "the same object"?
=== means we have to check the referential equality between the objects.
var a: Int = 10000
var b: Int = 10000
print(b === a)
It prints true because the reference to a and b variables are same. It's actually not an object. As the value of a and b are same that's why their reference is also same. a and b refers to the same memory allocation as their value is same. a and b are primitive type variables. So, you will get a warning
Identity equality for arguments of types Int and Int is deprecated.
var c = Integer(10000)
var d = Integer(10000)
print(d === c)
It prints false because c and d are two different Integer objects. So, the reference to c and d are also different.
How is data stored
If you imagine inside the JVM there are 2 types that are stored directly:
Primitive types [Int, Long]
These are stored as the value
Reference types [String, Object]
These are stored as a reference to the object
How does === work
=== compares the values in memory, so for reference types it checks they refer to the same object, whereas for primitive types it checks that they hold the same value.
Why are Ints primitive objects
Kotlin stores types that can be stored as primitives as primitives as an optimisation, so Int is primitive, whereas Int? would be stored as a reference type.