I'm on learning basics of Kotlin language for Android Development :), i'm looking on some Kotlin syntax examples and i found out this example below.. the thing that i don't know what is purpose with the ? code.. i'm trying to find on Google but i can't understand completely what they explain, maybe you can help me with your explanation / your example on this one ? I'll appreciate that :)
Example :
fun main() {
val myName :String? = "Maura"
println("Your name is $myName")
val myAge :Int? = 18
println("Your age is $myAge")
}
Its null safety. Basically Int? represents nullable Int, while Int on its own is non-null.
var a: Int = 5
a = null // error
var b: Int? = 5
a = null // :)
In case you have null in your code, you cannot use them directly, if you want to for example call any function in them you have to follow null-safety: Use ?. safecall operator, or !!. null assertion operator (usually shouldn't use).
val c = a.plus(4) // :)
val d = b.plus(4) // Hold on, I'm null you can't use "." on me :P
val e = b?.plus(4) // Ok, if b is not null, add 4 and store that to e, otherwise store null to e
val f = b!!.plus(4) // Hmm, if b was not null I'll add 4 to it and store to f, otherwise I'll crash your program throwing NPE
In contrast to this the type of e would be Int? as you've already read the thing. But what if you want to assign it a default value, easy use the elvis operator (?:):
val g = b?.plus(4) ?: 4 // if b is not null add 4 to it and store otherwise store 4 :)
Edit: The code in your sample works because there's String template calls .toString() and it is defined as fun Any?.toString() i.e. defined on a nullable receiver. So b.toString is valid though can be confusing.
println("Your age is $myAge")
// is same as
println("Your age is" + myAge.toString())
// toString is an extension function valid for nullable types, though member functions aren't :)
well, this is null safety feature of Kotlin lang (awesome btw.)
in short: ? next to type of variable/value means that this variable/value may be null. don't use it so often, it kind-of protects you from NullPointerExceptions, pretty often bug-cause in Java. also in your simple case it is unnecessary
var myName :String? = null // this is possible
var mySecondName :String = null // this isn't possible, build error
var myThirdName :String = "never null"
myThirdName = null // build error, this variable can't be null
myName = myName.replace("a", "b")
// build error above, trying to access nullable variable straightly
myName = myName?.replace("a", "b")
// null safety with ?, if myName is null it won't be executed and myName stay null
myThirdName = myThirdName.replace("a", "b") // this is possible
myThirdName = myThirdName?.replace("a", "b")
// this is also possible, but you will get a warning that ? is unnecessary
Related
I want to know the difference between String? and String! in Kotlin.
I search on kotlinlang.org but did not find any information.
Kotlin's type system differentiates between nullable and non-nullable types. In that context, String? is a nullable type while String would be the corresponding non-nullable type.
When working with Java libraries, the compiler is not always able to identify whether a type is nullable or not, since Java does not have that differentiation. Such types will then show up as "platform type" String!, meaning (basically): "I have no idea if this can be null but I'll treat it as non-nullable for now".
If you have control over the corresponding Java library, Kotlin supports various annotations to help distinguish between types, otherwise it is up to you as developer to explicitly assign either a nullable or a non-nullable type e.g. upon variable declaration to avoid running into NullPointerExceptions at runtime.
I'll try to answer with some sample code.
1. String?
This means this string is nullable.
Example 1: Use it in the type definition.
fun testStringTypes() {
// When initializing stringA, we can set null as the value
var stringA: String? = null
// And we can also set it to a meaningful string
stringA = "Hello"
// Then we can still set it back to null
stringA = null
}
Example 2: a variance of String?
fun testStringTypes() {
var stringA: String? = null
stringA = "Hello"
stringA = null
val lenOfStringA = stringA?.length ?: 0
}
So here is a brief description about what this val lenOfStringA = stringA?.length ?: 0 means:
Because stringA is defined as nullable;
stringA?.length means, access to the length property only if stringA is not null;
Because if, when stringA is null and if the code still tries to access to length (like in Java), the program will throw a NullPointerException. stringA? a question mark here, is to avoid this, which is called SafeCalls.
2. String!
This is platform types.
Copy from the link above:
As mentioned above, platform types can't be mentioned explicitly in the program, so there's no syntax for them in the language. Nevertheless, the compiler and IDE need to display them sometimes (for example, in error messages or parameter info), so there is a mnemonic notation for them:
I think (correct me if I was wrong), this makes sense when working with Java, because String in Java can be null, in other words, when accessing it from Kotlin, we don't know it is null or not. So String! is kind of a reminder to developer: Hey! Attention, this variable could be null.
Example 3, work with Java method from Kotlin:
// In Java:
public class PlatformTypeTest {
public String returnSomeStringCouldBeNull() {
return null;
}
}
And let's call this method in Kotlin.
fun testStringTypes() {
val someStringFromJava = PlatformTypeTest().returnSomeStringCouldBeNull()
}
fun testStringTypes() {
val someStringFromJava = PlatformTypeTest().returnSomeStringCouldBeNull()
someStringFromJav
}
As we can see from above two screenshots, IDE is reminding us this String from Java can be null.
And for String!, we can access it in different ways:
fun main() {
val someStringFromJava = PlatformTypeTest().returnSomeStringCouldBeNull()
var lenOfString = someStringFromJava?.length ?: 0
// lenOfString = someStringFromJava.length // NullPointerException
// lenOfString = someStringFromJava!!.length // NullPointerException
println(lenOfString)
}
With code snippet above, it works fine with var lenOfString = someStringFromJava?.length ?: 0, but the other two ways will cause NPE, as explained at above.
String? is a nullable type.
String! is a platform type platform type.
From Kotlin website:
Nullable Types Example:
val nullable: String? = item // allowed, always works
val notNull: String = item // allowed, may fail at runtime
Platform Types Example:
- T! means "T or T?",
- (Mutable)Collection<T>! means "Java collection of T may be mutable or not, may be nullable or not",
- Array<(out) T>! means "Java array of T (or a subtype of T), nullable or not"
while building a simple currency converter app in android kotlin I got the following error at * operator ? what could be reason for the error.
You cannot use round() function with nullable Any and Float variables. Also you need to convert Any to Float.
Try to convert them with this example:
var rate : Any? = 5
var fromAmount : Float? = 3.5f
val result = round(rate!!.toString().toFloat() * fromAmount!!)
Seems that rate variable is nullable (float? I suppose), while multiplying operation work with non null values only.
Also smart cast wasn't used here, so rate variable seems to be changeable. So I recommend to make it unchangeable (val variable), or use buffer variable like following:
val bufferRate = rate
if(bufferRate == null) {
...
} else {
val convertedCurrency = fromAmount * bufferRate //here `bufferRate` should be smart casted to non nullable type
}
Recently I read about the smart cast performed by the is operator and also about the as or the better as?operators in that is used for explicit casting.
The kotlin docs species their difference of usage as follows:-
Note that smart casts do not work when the compiler cannot guarantee that the variable cannot change between the check and the usage. More specifically, smart casts are applicable according to the following rules:
val local variables - always except for local delegated properties;
val properties - if the property is private or internal or the check
is performed in the same module where the property is declared. Smart
casts aren't applicable to open properties or properties that have
custom getters;
var local variables - if the variable is not modified between the
check and the usage, is not captured in a lambda that modifies it,
and is not a local delegated property;
var properties - never (because the variable can be modified at any
time by other code).
Note that smart casts do not work when the compiler cannot guarantee
that the variable cannot change between the check and the usage.
The above written is bit confusing, since the var variables can be changed after initialization and I could not find a example that can make it clear the actual insight of the statement.
Can anyone make it easier to understand this insight better, in anyway?
And does is operator provide some optimization benefit over the as operator if, any?
The idea of a smart cast is to help you avoid using as or as? to explicitly cast something that was already checked. As far as the bullet points above, here are some examples.
val local variables - since a val is final (cannot be changed), after you do the check, the variable can be smart cast as it cannot change again.
val a: Int? = 2
if (a is Int) {
// 'a' is smart cast to Int
val b = a * 2 // b is also Int
}
val properties - if accessed directly (via the default getter), smart cast is possible. If through a custom getter, it's not because we cannot know it was modified.
class Test {
val a: Int? = 2;
}
class TestGetter {
val a: Int? = 2
get() = field * 2
}
// Usage
val test = Test()
val testGetter = TestGetter()
if (test.a is Int) {
// 'test.a' is smart cast to Int
val b = test.a * 2
}
if (testGetter.a is Int) {
// smart cast is impossible, 'testGetter.a' is still Int?
val b = testGetter.a * 2 // can't happen because we don't know whether 'a' was changed by the custom getter or not (the getter could give unstable values)
}
var local variables - if the variable is not modified between the check and the usage, is not captured in a lambda that modifies it, and is not a local delegated property;
var a: Int? = 2
if (a is Int) {
// 'a' was not changed, so it can be smart cast to Int
val b = a * 2 // b is also Int
}
var c = 4
if (c is Int) {
c = null
// 'c' was changed between the check and the usage, we cannot smart cast it anymore
val b = c * 2 // won't work
}
var properties - the var can always be modified by something else in the code, so smart cast won't work.
class Example {
var a: Int? = 2
fun test1() {
if (a is Int) {
// smart cast is impossible because we don't know whether 'a' was changed by some other code/function
val b = a * 2 // won't work
}
}
}
As far as using as goes, if you look at the last example:
class Example {
var a: Int? = 2
fun test1() {
if (a is Int) {
// smart cast is impossible because we don't know whether 'a' was changed by some other code/function
val b = a as Int * 2 // this WILL work because we forcefully cast it to Int, but if a is null there will be an exception in runtime
}
}
}
You can also use as? when you're not sure if the var can be cast to something or not. If not, it will just give you a null. For example:
val a: Double = 2.0
val b = a as? String // 'b' will be 'String?', in this case initialized to 'null' since 'a' cannot be cast to it
val c: Int? = 2
val d = c as? Int // 'd' will be '2' but still 'Int?' since 'as?' always makes the variable nullable
Hope the examples helped, let me know if I need to clarify something further.
In Code A1 I have used the let statement, so I think it will not be null with filenameofVideo.path
But I get the following error, why?
Smart cast to 'File' is impossible, because 'filenameofVideo' is a mutable property that could have been changed by this time
At this moment, I have to use Code A2.
Code A1
private var filenameofVideo :File?=null
filenameofVideo?.let {
Navigation.findNavController(requireActivity(), R.id.fragment_container)
.navigate(UIFragmentCameraDirections.actionCameraToVideo(filenameofVideo.path))
}
Code A2
private var filenameofVideo :File?=null
filenameofVideo?.let {filenameofVideo ->
Navigation.findNavController(requireActivity(), R.id.fragment_container)
.navigate(UIFragmentCameraDirections.actionCameraToVideo(filenameofVideo.path))
}
And more, I find both Code B1 and Code B2 are correct. Why is Code B1 correct and Code A1 wrong?
Code B1
private val aa:String?=null
aa?.let {
print(aa)
}
Code B2
private val aa:String?=null
aa?.let{aa->
print(aa)
}
Added Content:
1: In Code C, the var aa might have been changed (perhaps by another thread) between the moment it is accessed in the ?.let call and the moment it is accessed within the let block.
Code C will be launched when aa is not null, and Code C will not launched when aa is null, right?
2: In Code D (I assume the compiler accept it ), the function always be launched no matter aa is null or not, it can not be accepted, so the system will interrupt, right?
Code C
private var aa: String? = null
aa?.let { kk ->
print(kk.length)
}
Code D
private var aa: String? = null
aa?.let {
print(aa.length)
}
Let's trim your code down a bit so that it's a little easier to discuss:
private var filename :File?=null
filename?.let {
action(filename.path)
}
The problem here is that, even though you've called ?.let, the compiler cannot guarantee that filename is not null inside the let block. This is because filename is a var, and the value of the var might have been changed (perhaps by another thread) between the moment it is accessed in the ?.let call and the moment it is accessed within the let block.
Therefore, you must handle the possibility of null yourself. The easiest way to do this will be to use the it value inside the let block, or to name that value yourself:
filename?.let { safe ->
action(safe.path)
}
The reason that this code does work:
private val aa:String?=null
aa?.let {
print(aa)
}
is that here the variable is a val. This means that it will either be null or non-null, but either way it can't change. So you and I can read the code and see that the .let block will never be executed... but if somehow it were, that would mean that aa is guaranteed to not be null, and so the compiler can smart-cast it to a non-nullable type.
The code like
private var aa: String? = null
aa?.let {
print(aa.length)
}
does not compile because, while the safe call ?.let { } block is executed only if aa property is not null, by the time the execution reaches aa.length, the value of aa property might have been already changed to null in another thread.
On the other hand, in this example
private var aa: String? = null
aa?.let { aa ->
print(aa.length)
}
the non-null value of aa property is captured after the safe call in the read-only aa parameter of the lambda passed to let.
Note that making aa property val instead of var as in your example B1 also helps to eliminate the error, but that causes the let block be never executed since a private val initialized to null can never become non-null.
As for the additional question about what is a difference between the examples C and D:
there's no difference in how the let { } function is executed. In both cases the lambda function is launched only when the receiver expression aa is not null, because you use ?. safe call operator.
the example D isn't accepted by the compiler because it can't ensure the value of aa property referenced inside lambda hasn't been changed by the time execution reaches that line.
If a variable is nullable in Kotlin, we need to either do a safety call ?., or !!. for explicitly call.
When I was trying to use some extensions(such as run or let) from nullable variable , I noticed that .run is fine and IDE did not complain it, usually I will receive a warning to remind me it is not a safety call.
Does it make any difference for ?.run{} and .run{} in kotlin? Is it considered as null safety if I use .run{} ?
var a? = "..."
a?.run{}
a.run{}
You'll need to safely handle the null somewhere.
Either when accessing a:
a?.run { }
Or when accessing this inside run on a:
a.run { this?.toSomething() }
Using a String? as an example, these both print null and the compiler is ok with both:
val x: String? = null
println(x.run { this?.toUpperCase() }) // prints null, type is String?
println(x?.run { this.toUpperCase() }) // prints null, type is String?