Where Java programmers tend to trip over Kotlin

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. ..

  1. Almost the same as Java
  2. ** Places that require a new way of thinking and tend to trip ← Contents covered in this post **
  3. Convenient things unique to Kotlin

Where you need a new way of thinking and tend to stumble

** 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 aString 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)

ʻInline` and Non-local return

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 extendedfor` 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.

Anonymous function

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.

ʻAny 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 isnull. 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 aT (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.

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 andthrows` 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 notreturn` 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?.

Generics and denaturation

If 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 parameterTonly 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 theT` 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 ʻIterableandComparable 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 ` [^ 5], the compiler will tell you as a compilation error.

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.

Explicit conversion of primitive types

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 ofDouble. 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 ʻaandb 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

Summary

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.

** 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

Where Java programmers tend to trip over Kotlin
Kotlin Class to send to Java developers
[Android] Convert Android Java code to Kotlin
Kotlin Class part.2 to send to Java developers
Convert all Android apps (Java) to Kotlin
Kotlin scope functions to send to Java developers
What Java programmers find useful in Kotlin
Easy to trip with Java regular expressions
Memo for migration from java to kotlin
Kotlin functions and lambdas to send to Java developers
Introduce Kotlin to your existing Java Maven Project
Migrate from Java to Server Side Kotlin + Spring-boot
Getting started with Kotlin to send to Java developers
How to write Java String # getBytes in Kotlin?
What Java inexperienced people did to study Kotlin
Where about java
[Java] Introduction to Java
Introduction to java
Mastering Kotlin ~ Convert Java File to Kotlin File Road to Graduation ~ Part 3
Mastering Kotlin ~ Convert Java File to Kotlin File Road to Graduation ~ Part 2
Mastering Kotlin ~ Convert Java File to Kotlin File Road to Graduation ~ Part 1
I want to transition screens with kotlin and java!