Kotlin will be the official language for Android was announced at Goole I / O 2017. I'm sure many Java programmers will start Kotlin in the future, so this post will explain what Java programmers tend to trip over in Kotlin.
This post is written so that it can be understood independently, but it is the second in the series below. It is assumed that you understand the basic syntax of Kotlin, so please see "Almost the same as Java" for the basics of Kotlin. ..
** When learning a new concept, you can't use it well without knowing not only what you can do, but why it is. ** In this section, I will explain what I think is particularly important in the fact that Kotlin is different from Java and requires a new way of thinking. ** Based on "Why is that so?", "What can I do?" "explain**.
Smart Cast
I don't know if it's a pervasive idea in general, but I think there is a tendency in Java that ʻinstanceof is not good. The idea is that branching at ʻinstanceof
and then casting is a last resort, and it is desirable to do something with polymorphism. However, there is a leap forward that polymorphism is the solution to the bad ʻinstanceof`. There should be other solutions.
The problem with Java's ʻinstanceof` is that you can't check and cast types at the same time.
// Java
Animal animal = new Cat();
if (animal instanceof Cat) {
Cat cat = (Cat)animal; //Downcast
cat.catMethod(); // `Cat`Processing that uses the method of
}
I once confirmed in ʻinstanceof that ʻanimal
was Cat
, but then I needed to downcast to Cat
. Downcasting is an unsafe process and I don't want to use it if possible. If you make a mistake and use (Dog) animal
here, you will get a ClassCastException
at runtime.
The funny thing is that the previous ʻif statement confirms that ʻanimal
is Cat
, but you have to cast it explicitly. The scope of ʻif guarantees that ʻanimal
is Cat
, so it would be nice if the compiler could treat ʻanimal as
Cat`. Kotlin's ** _Smart Cast _ ** does just that.
//Kotlin
val animal = Cat()
if (animal is Cat) { // Smart Cast
animal.catMethod() // `Cat`Processing that uses the method of
}
Smart Cast itself looks like a bit of a feature, but with Smart Cast, Kotlin uses cast (by Smart Cast) normally. In particular, it is indispensable for Null Safety, which will be explained later, and the Sealed Class, which will be explained next time, is also compatible with Smart Cast. It is necessary to break away from the idea that it is not good to check the type and branch.
Null Safety
The language spec that Java programmers are most confused about and that I find most wonderful in Kotlin is ** Null Safety **.
In Java, code like ↓ can cause a NullPointerException
.
// Java
String s = foo(); //May be null
int l = s.length(); //NullPointerException at runtime if null
However, writing similar code in Kotlin will result in a compilation error.
// Kotlin
val s = foo() //May be null
val l = s.length //Compile error
The Kotlin compiler detects code that may cause an error at run time and tells you at compile time. In general, ** error detection is more difficult to deal with as it is delayed. If the run-time error cannot be detected by the test, it may be embedded in the product as a potential bug and released. If it is a compilation error, it will not be released without being resolved. ** **
This compilation error can be eliminated by checking null
.
// Kotlin
val s = foo() //May be null
if (s != null) {
val l = s.length // OK
}
This is where Smart Cast comes in handy. The null
check guarantees that s
is not null
, and within the scope of this ʻif,
scan be treated as a
String that is not
null`.
So what type is Smart Cast casting to what type in this case?
Kotlin treats the types of possible values as null
and the types of values that are guaranteed to be non-null`. The former is called ** _Nullable Type _ ** and the latter is called ** _ Non-null Type _ **.
As with Java, if you write String
etc., it will be Non-null Type. In this case, you cannot substitute null
.
// Kotlin
val s: Sting = null //Compile error
If you add ?
to the original type and write String?
Etc., it becomes Nullable Type. You can assign null
to Nullable Type.
// Kotlin
val s: String? = null // OK
It's important to note that String
andString?
are completely different types. ** You cannot call methods of String
on a value of typeString?
. ** In the first example, s.length
causes a compile error, not because s
may be null
, but **String?
Type has properties such as length
. Not because **. It is String
that has length
, not String?
.
And another important thing is that ** String
is a subtype ofString?
. ** Therefore, you can assign from a String
type to aString?
Type, but you cannot assign from aString?
Type to a String
type.
// Kotlin
val a: String = "abc"
val b: String? = a // OK
val c: String? = "xyz"
val d: String = c //Compile error
This is the same as assigning from Cat
to ʻAnimal` but not vice versa.
// Kotlin
val a: Cat = Cat()
val b: Animal = a // OK
val c: Animal = Animal()
val d: Cat = c //Compile error
So, back to the original story, what type did Smart Cast with the null
check cast to what type? The answer is, I cast String?
To String
.
// Kotlin
val s: String? = foo() //here`String?`
if (s != null) { //Within this scope`s`Is`String`
val l = s.length
}
In other words, s! = null
works like s is String
. In fact, you can get the same result by writing as ↓.
// Kotlin
val s: String? = "abc"
if (s is String) { //Smart Cast with is instead of null check
val l = s.length
}
This is how Kotlin prevents NullPointerException
. If you're writing Java, it's normal to encounter NullPointerException
. Wouldn't it be great to have Null Safety where it doesn't happen?
!!
Null Safety is great, but the type isn't all-purpose.
For example, Kotlin's List
(to be exact, ʻIterable) has a method called
max. This will return the largest element in
List`.
// Kotlin
listOf(2, 7, 5, 3).max() // 7
However, the return value of the max
method ofList <Int>
is not ʻInt. ʻInt?
. This is because the empty List
does not have the largest element.
// Kotlin
listOf<Int>().max() // null
Now suppose you always have a List <Int>
that contains one or more elements, and you want to get the largest element and square it. In Java, you can just square the return value of max
without thinking.
// Java
List<Integer> list = ...; //Must contain one or more elements
int maxValue = Collections.max(list);
int square = maxValue * maxValue; // OK
But Kotlin doesn't work.
// Kotlin
val list: List<Int> = ...; //Must contain one or more elements
val maxValue = list.max()
val square = maxValue * maxValue //Compile error
This is because the type of maxValue
in ↑ is ʻInt?, Not ʻInt
. There is no operator *
that computes between ʻInt? `.
Checking null
to solve this will result in terrible code.
// Kotlin
val list: List<Int> = ...; //Must contain one or more elements
val maxValue = list.max() //maxValue is`Int?`Mold
if (maxValue != null) { //Within this scope`maxValue`Is`Int`Mold
val square = maxValue * maxValue // OK
} else {
throw RuntimeException("Never reaches here.") //Never come here
}
You might wonder if there is a ʻelse clause, but if you don't write it, you're in danger of squeezing the error in the unlikely event that the
list` is empty due to some bug. ** Crushing errors is the worst thing you can do to delay finding a bug rather than a run-time error. ** **
However, it is tedious to write a useless null
check like ↑ every time. This is an unnecessary process in Java. If you have to write this code every time for Null Safety, then Null Safety isn't very good. Besides, the tedious process tends to be omitted. Some people will not write the ʻelse` clause. Then the error is crushed and it is the worst.
The trouble with this problem is that it's difficult to solve with a type. Kotlin doesn't have a type like "a list that always contains more than one element", and even if it does, there are cases where it can't be solved.
// Kotlin
val list: MutableList<Int> = mutableListOf() //Empty at this point
...
list.add(42)
val maxValue = list.max() //It ’s definitely not empty here`maxValue`Is`Int?`
In the last line of ↑, list
is never empty, but the entity of list
is just an instance of MutableList
. Even if there is a type like NonEmptyMutableList
(non-empty list), it does not become an instance of NonEmptyMutableList
when the instance of MutableList
is not empty.
The postfix operator !!
deals with such problems. !!
will convert T?
To T
. However, it throws an exception if the value is null
.
// Kotlin
val list: List<Int> = ...; //Must contain one or more elements
val maxValue = list.max()!! // `maxValue`Is`Int`Mold
val square = maxValue * maxValue // OK
!!
throws a NullPointerException
if it fails to non-null and punctures the safety of Null Safety. Never abuse it. Use !!
only if you know it's not ** null
, as in the example above, and it never fails ** [^ 1]. Although it is Nullable on the type, it should be used only when it can be said that it is absolutely not null
in the processing flow.
?.
You can use ?.
To call a method of T
for a value of typeT?
Only if it is not null
.
// Kotlin
val s: String? = ...
val l: Int? = s?.length
Note that the l
in ↑ is ʻInt? . If
s is
null, the result of
s? .Length is
null. Therefore, the return value of
length is ʻInt
, but in s? .Length
it is the Nullable Type ʻInt?`.
? .
is convenient because it can be written as foo? .Bar? .Baz
when writing multiple processes that may be null
. However, abuse of ?.
Does not give very good results. In particular, we do not recommend handling values as ** Nullable. ** If you try to use a value and it is null
, you don't know the source of null
. If the reason for becoming null
is due to a bug, it is difficult to fix the bug because you do not know where the null
occurred. To prevent such a situation, it is recommended to check ** Nullable values as early as null
and make them non-null. ** **
?:
It is often the case that you want to fill in the initial value if there is no value. The ?:
Operator is useful in such cases. ?:
Is written in the form foo?: Bar
, and if foo
is null
, it will be bar
instead.
For example, if you want to display No Name when the user's name is not set, you can write as ↓.
val name: String? = ...
val displayName: String = name ?: "No Name"
The important thing here is that the type of displayName
is String
instead of String?
. ?:
Uses the alternative string " No Name "
if name
is null
, so it is always not null
. Therefore, the resulting type will be String
instead ofString?
.
Even more convenient is the ability to write statements such as return
on the right-hand side of?:
. It is difficult to write with the null
check by ʻif`, and it is useful because you can realize early escape at the same time as substitution.
fun foo(list: List<String>) {
val length: Int = list.min()?.length ?: return // `list`Escape early if is empty
//Processing that uses length
}
?.let
Since I received the comment of ↓, I will briefly explain the method that can be used in such a case.
kitakokomo For Nullable, "list Of (1,2) list [0] +5 works fine, but mapOf ( 1 to 11, 2 to 22) map [1] +5 is Nullable and it's sad that it doesn't work "was mentioned on 2ch.
?.
Can be used to call properties and methods like foo? .Bar
, but you cannot pass a Nullable value as an argument or an operand of an operator. You can use the let
method in such a case (I will explain the let
method in detail next time, so I will only introduce this method here).
// Kotlin
map[1]?.let { it + 5 }
In this way, you can get the result of adding Int?
And 5
, which are the return values ofmap [1]
, as ʻInt?. However, it is not readable and abuse can lead to handling values as Nullable, so keep usage to a minimum and check the result of the calculation immediately
?:` We recommend that you make it non-null.
As an aside, Kotlin's operator is syntactic sugar for methods (which I'll explain next time), so if you just want to add up, use the plus
method, which is the body of+
, like ↓. You can also write in.
map[1]?.plus(5)
Alongside Null Safety, Java programmers may find the non-local return with inline expansion annoying.
In Kotlin, methods with the ʻinline` modifier are inlined. Inline expansion is a mechanism by which the compiler expands the method implementation to the caller to reduce the method call overhead.
↓ is an image of inline expansion.
// Kotlin
//Before inline expansion
inline fun square(value: Int): Int {
return value * value
}
for (i in 1..100) {
println(square(i))
}
//After inlining
for (i in 1..100) {
println(i * i)
}
But what is important in Kotlin is that the lambda expression passed as an argument to the ʻinline method is also expanded to ʻinline
.
// Kotlin
//Before inline expansion
inline fun forEach(list: List<Int>, operation: (Int) -> Unit) {
for (element in list) {
operation(element)
}
}
val list: List<Int> = ...
forEach(list) { element ->
println(element)
}
//After inlining
val list: List<Int> = ...
for (element in list) {
println(element)
}
This gives you the same performance as you would write in a loop, using forEach
, map
, filter
, fold
(fold
is equivalent to reduce
, which gives the initial Java value), etc. It can be realized. ↑ I made forEach
by myself, but even in the standard library, all the methods such as forEach
, map
, filter
, and fold
that List
etc. have are ʻinline`.
Kotlin's ʻinline is supposed to be used in conjunction with lambda expressions like that, and if you try to ʻinline
an ordinary function or method like ↑ square
, the compiler will look like ↓ I will issue a warning.
Expected performance impact of inlining 'public inline fun square(value: Int): Int defined in root package' can be insignificant. Inlining works best for functions with lambda parameters
So far, it's not a special story. The most confusing thing for Java programmers is a mechanism called ** _Non-local return _ **. Non-local return is a mechanism that allows you to return
a method outside a lambda expression from inside a lambda expression, or break
a loop outside the lambda expression.
This is also explained with an example. Consider the method ʻand which takes the logical product (
&&) of all the elements of
List . For example, if
[true, true, true], the result of ʻand
is true
, but if even one false
is mixed like [true, false, true]
, the result is false. It becomes
.
The ʻandmethod is easy to implement in the extended
for` statement.
// Java
static boolean and(List<Boolean> list) {
for (boolean x : list) {
if (!x) {
return false;
}
}
return true;
}
However, you can't do the same in Java with forEach
, which is almost equivalent to for
. You can't return
a method outside a lambda expression inside a lambda expression.
// Java
static boolean and(List<Boolean> list) {
list.forEach(x -> {
if (!x) {
return false; //Compile error
}
});
return true;
}
Kotlin allows this.
// Kotlin
fun and(list: List<Boolean>): Boolean {
list.forEach { x ->
if (!x) {
return false // OK
}
}
return true
}
This is a non-local return.
It's unpleasant to be able to return
the outside of a lambda expression from within the lambda expression. How can you do this? This is because forEach
is inlined. The image after inline expansion of the code in ↑ is ↓.
// Kotlin
fun and(list: List<Boolean>): Boolean {
for (x in list) {
if (!x) {
return false
}
}
return true
}
It's no wonder you can return
in this case. The return
inside the ʻif that was originally inside the lambda expression is now the
return of the ʻand
that was outside the lambda expression.
In Kotlin, a return
written inside a lambda expression always works outside the lambda expression. You can't use return
to return the return value of the lambda expression itself. The return value of the lambda expression is the value of the last evaluated expression inside the lambda expression.
So what if you write a return
inside a lambda expression passed to a method that isn't ʻinline`? It doesn't expand inline, so it can't act on the outside of a lambda expression. In that case, a compilation error will occur.
// Kotlin
// `inline`If not
fun foo(f: () -> Int): Int {
...
}
fun bar(): Int {
return foo {
if (flag) {
return 0 //Compile error
}
42
}
}
In the example of ↑, if foo
is ʻinline` like ↓, there is no problem.
// Kotlin
// `inline`in the case of
inline fun foo(f: () -> Int): Int {
...
}
fun bar(): Int {
return foo {
if (flag) {
return 0 // OK: `bar`of`return`
}
42
}
}
** return
in a lambda expression always acts outside of the lambda expression ** is a pretty drastic spec from other languages, including Java, but here's when it comes to dealing with Kotlin's lambda expressions. I'm addicted to it if I don't hold it down.
Non-local return is unpleasant at first, but useful depending on how you use it. For example, in Java you can easily create an anonymous scope with {}
, but in Kotlin {}
is assigned to a lambda expression and you cannot create an anonymous scope. If you don't have an anonymous scope, you'll be in trouble when you want to cut the scope into smaller pieces to prevent duplicate variable names. Kotlin uses run
in such cases.
// Java
for (int x : list) {
{ //Anonymous scope
int a = 2;
...
if (x < a) {
break;
}
}
{
int a = 3; //Because the scope is different`a`Can be made
...
}
}
// Kotlin
for (x in list) {
run { //Instead of anonymous scope
val a = 2
...
if (x < a) {
break //The loop inside the lambda expression but outside`break`it can
}
}
run {
val a = 3 //Because the scope is different`a`Can be made
...
}
}
In this way, you can also use Non-local return to create something like custom control syntax. Abuse may impair readability, but it is a good property that if you want a new control syntax, you can solve it with the language mechanism rather than bloating the language specification. Let's get along well with Non-local return.
If you can't return
in a lambda expression, what if you want to escape early?
// Kotlin
numbers.forEach { number ->
if (number % 2 == 1) {
//I want to escape early here, what should I do?
}
...
}
In this case, it's better to use ** _ Anonymous Function _ ** instead of a lambda expression.
// Kotlin
numbers.forEach(fun(number: Int) {
if (number % 2 == 1) {
return //Early escape
}
...
})
Unlike lambda expressions, return
in an anonymous function means return
in the anonymous function itself.
and ʻAny?
In Java the root class was ʻObject. In Kotlin, that's ʻAny
. When using Java methods from Kotlin, wherever ʻObject is used in arguments and return values in Java, Kotlin makes it look like ʻAny
. But this is not just a name change. It is possible to use Java's ʻObject in Kotlin [^ 2]. In Kotlin, ʻObject
is not the root class, and ʻAny is a superclass of ʻObject
.
What do you need ʻAny for? In Java, you had to box into a wrapper class such as ʻInteger
once to assign from a primitive type to ʻObject. There is no type-based derivation between ʻint
and ʻObject. However, Kotlin's ʻInt
and others behave as classes, so ʻInt is a subtype of ʻAny
. Therefore, you can just assign an instance of the ʻInt class to a variable of type ʻAny
.
// Kotlin
val a: Int = 42
val b: Any = a
if (b is Int) { //Directly`Int`Check if it is an instance of
//In this scope`b`To`Int`Can be used as
println(b + 1) // 43
}
However, this does not mean that boxing is not done internally at run time. I couldn't find anything directly mentioned in the documentation, but assignments to ʻAny, such as ʻInt
, are, in principle, [behavior for assignments to Nullable Type](https: //). By analogy with kotlinlang.org/docs/reference/basic-types.html#representation), it seems that boxing will be done internally. Unlike Java, it can be used syntactically without being aware of boxing [^ 3], but be aware that assignments such as ʻInt to ʻAny
have performance overhead.
In Java, if you didn't explicitly specify a superclass with ʻextends when declaring a class, that class would inherit from ʻObject
. Similarly, Kotlin inherits ʻAny` if you don't specify a superclass.
But the tricky part is that Kotlin has some values that can't be assigned to ** ʻAny**. It is
null. This means that ʻAny
is not a supertype of Nullable Type, such as ʻInt?. ** In Kotlin, the top type that exists at the root of the type hierarchy is ʻAny?
. ** There is nothing that cannot be assigned to ʻAny? . ʻAny
is a name that can be assigned to anything, but it is confusing, but the type hierarchy can be represented graphically as follows [^ 4].
[Any?]
|
+---------------+
| |
[Any] [All other Nullable]
|
[All other Non-null]
The hard part if you don't understand this is when using Generics. If you understand that ?
Is just a symbol that allows you to put a null
, you tend to implement something like ↓.
// Kotlin
//I want to be able to pass Nullable Type as well
fun <T> id(x: T?): T? {
return x
}
However, the ** ↑ ?
Is not necessary. ** Because T
can represent a Nullable Type. Type parameters act as placeholders for all types, including Nullable Type, if unconstrained (upper bound is ʻAny? `By default).
// Kotlin
//You can still pass the value of Nullable Type
fun <T> id(x: T): T {
return x
}
And if you want to be able to pass only the value of Non-null Type, it will be like ↓.
// Kotlin
//Nullable Type cannot be passed
fun <T: Any> id(x: T): T {
return x
}
This is a constraint that only the type at the end of the ʻAnybranch on the left of the previous type hierarchy diagram can be taken as a
T (upper bound is ʻAny
). It's no different from constraining something like <T: Animal>
. If you don't understand the subtyping of Nullable Type and Non-null Type and understand that ?
Is a symbol that allows you to put null
, whyT: Any>
You won't understand if `will now represent a Non-null Type.
Any
: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/Nothing
andNothing?
Nothing
is a companion to ʻAny and ʻAny?
. Nothing
is called the bottom type and is a subtype of all types. This means that all classes are inherited multiple times and any method can be called. Of course, such a type is not feasible, so there is no instance of Nothing
. What's the point of thinking about such a fantasy pattern?
Kotlin allows you to specify Nothing
as the return type of a method. However, as mentioned above, there is no instance of Nothing
. If you specify the Nothing
type as the return type, but you cannot return
the value of the Nothing
type, a compile error will occur. The only way to get it compiled is to throw
the exception in the method. Therefore, in Kotlin, a method that returns Nothing
always fails, indicating that it will throw
an exception.
This is useful when dealing with compiler completeness checks. For example, suppose you have a method called ʻalwaysFailthat always fails and
throws` an exception. Consider the following code.
// Java
static void alwaysFail() {
throw new RuntimeException();
}
static String foo(int bar) {
switch (bar) {
case 0:
return "A";
case 1:
return "B";
case 2:
return "C";
default:
alwaysFail();
}
} //Compile error
ʻAlwaysFail always makes an exception
throw, so it never reaches the end of this method. However, syntactically, the end of the method is reached in the case of
default, so
foowill cause a compile error because there is a flow that does not
return` the return value.
In Kotlin, by making the return value of ʻalwaysFail of type
Nothing`, the compiler will determine that no further processing will be executed, and compilation will pass even in cases like ↑.
// Kotlin
fun alwaysFail(): Nothing {
throw RuntimeException()
}
fun foo(bar: Int): String {
when (bar) {
0 -> return "A"
1 -> return "B"
2 -> return "C"
else -> alwaysFail()
}
}
It's convenient!
By the way, this is a digression, but if there is ʻAny? , There should be
Nothing? . What does
Nothing?` Mean?
Nothing?
Indicates that it is Nothing
or null
. The only instance of Nothing?
Is null
because there is no instance of Nothing
. That is, Nothing?
Is a type that can only contain null
. In a sense, null
is like an instance of theNothing?
Class (of course, there really isn't a class calledNothing?
).
Nothing?
Is also a subtype of all Nullable Types. It looks like ↓ in the figure.
[Any?]
|
+---------------+
| |
[Any] [All other Nullable]
| |
[All other Non-null] |
| |
| [Nothing?]
| |
+-------+-------+
|
[Nothing]
I don't think you have a lot of chances to use Nothing?
, But if you want to use a type that can only contain null
, it may be useful to remember to useNothing?
.
Nothing
: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-nothing.htmlIf you do not know Java wildcards and denaturation, you cannot understand the contents of ↓. In that case, we recommend that you first read here.
In Java, wildcards such as List <? Extends Animal>
are used to control denaturation on the side that uses the generic type. This is a method called Use-site variance. On the other hand, C # and Scala took advantage of Java's reflection and adopted a method called Declaration-site variance. Declaration-site variance, as the name implies, controls denaturation on the declaring side, not on the side that uses the type. Kotlin can do both, but basically I think it's better to design it so that it can be managed only with Declaration-site variance. The Kotlin standard library also uses Declaration-site variance everywhere.
Let's look at a concrete example of Declaration-site variance.
For example, ʻIterable uses the type parameter
Tonly in the return value. In that case, it would be convenient if the user did not have to do something like ʻIterable <? Extends Animal>
(Java) / ʻIterable (Kotlin), but the
T` was covariant from the beginning.
On the other hand, Comparable <T>
uses the type parameter T
only as an argument. In that case, it would be convenient if the user does not have to do Comparable <? Super Cat>
(Java) / Comparable <in Cat>
(Kotlin), but the T
is rebellious from the beginning.
To achieve this, Kotlin's ʻIterableand
Comparable specify covariance and contravariance by adding ʻout
and ʻin` when declaring type parameters. This is Declaration-site variance.
// Kotlin
interface Iterable<out T> {
...
}
interface Comparable<in T> {
...
}
By adding ʻout or ʻin
when declaring a type parameter in this way, ? Extends
/ ʻoutor
? Super / ʻin
is always attached when using it, even if nothing is added. It behaves like this. Also, for example, if you try to use T
as a method argument with ʻIterable
The designer of the class or interface thinks more about the type than the user and should be able to set the denaturation appropriately. Conversely, thinking about denaturation at the time of declaration also prevents you from designing odd types that neglect denaturation. It is also troublesome to add ? Extends
/ ʻout` etc. every time you use it. Declaration-site variance is an approach to the problems that such Use-site variances have.
In Java, ↓ is fine code.
// Java
int x = 42;
double y = x; // OK
But in Kotlin I get a compile error.
// Kotlin
val x: Int = 42
val y: Double = x //Compile error
Normally, ʻA must be a subtype of
B in order to be able to assign a value of type ʻA
to a variable of type B
. However, Kotlin's ʻIntis not a subclass of
Double. If there is no type derivation relationship and you can assign it, it means that an implicit type conversion has been performed. Kotlin does not do implicit type conversion, even if it is from ʻInt
to Double
. This can seem tedious at first if you're used to Java, but I think it's a good spec as it doesn't make the type handling appropriate.
For example, running ↓ in Java causes short
to overflow and display -32768
.
// Java
short a = Short.MAX_VALUE;
a++;
System.out.println(a); // -32768
Then, what is displayed in ↓?
// Java
short a = Short.MAX_VALUE;
short b = 1;
System.out.println(a + b);
In this case, it is 32768
. This is because ʻaand
b are implicitly converted to ʻint
before the addition of ʻa + b` is performed. Implicit type conversion tends to produce such unexpected behavior if you do not understand the specifications well. I think it's a better approach to provide a concise method of explicit type conversion than implicit type conversion.
Kotlin uses the toDouble
method to explicitly convert from ʻInt to
Double`.
// Kotlin
val x: Int = 42
val y: Double = x.toDouble() // OK
Also note that Kotlin does type conversion with methods instead of casts, even in cases where explicit casting is required in Java, such as Double
to ʻInt. This is because ʻInt
and Double
have no type derivation relationship.
// Java
double x = 42.0;
int y = (int)x; // OK
// Kotlin
val x: Double = 42.0
val y: Int = x as Int // ClassCastException
// Kotlin
val x: Double = 42.0
val y: Int = x.toInt() // OK
In this post, I've covered some of the things that Java programmers starting with Kotlin may find stumbling blocks because they need new ideas.
and Non-local return --ʻAny
and ʻAny? --
Nothing and
Nothing? `** Next post **: What Java programmers find useful in Kotlin
[^ 1]: It is not always the code that guarantees that it is absolutely not null
. For example, if a user does not pass an argument to a script that must pass one or more arguments, it is the user's responsibility to fail. Code should guarantee that it is not null
on critical systems, but in many cases disposable scripts should be guaranteed by the user. In such a case, if you !!
null
, you are responsible for it.
[^ 2]: However, when I try to use ʻObject`, I get a compiler warning that it should not be used.
[^ 3]: Java also has autoboxing, but autoboxing only automatically converts types such as between ʻint and ʻInteger
. The Kotlin
ʻInt` is the same type whether internally boxed or not, and there is no difference in the code.
[^ 4]: However, since ʻInt is a subtype of ʻInt?
, To be exact, individual types in "all other Nullables" to individual types in "all other Non-nulls". There is a line that extends to.
[^ 5]: Strictly speaking, by taking T
for the contravariant type parameter of the argument type, this expression can be used upside down so that the multiplication of negative and negative becomes positive, so this expression is accurate. There is none. For the exact rules (although it's an article about C #) here Please see.
Recommended Posts