13
Help us understand the problem. What are the problem?

posted at

updated at

Java民から見た時のKotlinのギャップまとめ

経緯

社内PJでKotlinを使ってコードを書くことになりました。
メンバーには自分含めJava民が多いです。Kotlinへの移行がちょっとでもスムーズになるように、Java民から見た「Kotlin特有の書き方だなぁ」ってところをまとめました。
å

Kotlin慣れに良いサイト(たぶん)

(公式)イディオム
(公式)コレクションタイプ
Javaでこう書いてたものがKotlinだとこう、みたいな感じ

リファレンス
コレも公式。
公式を個人が日本語訳したもの。
右上の「TRY ONLINE」からオンライン環境でコーディングできる

(公式)SpringBootとH2DB使ってAPIつくる
英語が面倒だけど実処理書こうとするとこんな感じ

Kotlinの基本的な言語仕様を学習したまとめ
ぶっちゃけ本記事の上位互換。

変数定義周り

Null Safety

変数がデフォルトで非NULL型。コンパイルの時点でエラー。
これによりNullPointerExceptionが起きづらい(後述の演算子もあるので、絶対起きないとは言っていない)

Some.kt
var a: String = "hogehoge"
a = null //コンパイルエラー Null can not be a value of a non-null type String

var b: Int = "hogehoge"
b = null //コンパイルエラー Null can not be a value of a non-null type Int

NULL許容型も作れる

Some.kt
var a: String = "hogehoge"
a = null //コンパイルOK

var b: Int = "hogehoge"
b = null //コンパイルOK

定義した変数を呼ぶとき

NULL許容で変数定義した場合は、下記の2つのパターンでメソッドを呼ぶ。
後者は非推奨なので実質セーフコールがベター。

セーフコール演算子
値がNULLでない場合のみメソッドが呼ばれる。

Some.kt
var a: String = "hogehoge"
println(a?.length) // 8

var b: String = null
println(b?.length) // null lengthメソッドは呼ばれない

非NULL表明演算子(きっと非推奨)

指摘頂いて追記。
NullableなものをnonNullに変更できる。

Some.kt
val nullable: String? = getNullable()
val nonnull: String = nullable!!

またメソッド呼び出しと一緒に使うと、NULLだろうが何だろうがメソッドを呼ぶ。
当然nullとなっている変数に対してメソッドが呼ばれるとNullPointerExceptionが出る。

Some.kt
var a: String = "hogehoge"
println(a!!.length) // 8

var b: String = null
println(b!!.length) // ぬるぽ

変数定義時にvar(再代入可能) OR val(再代入不可能)を必ず指定する

var...再代入可。
val...再代入不可。Javaでいうfinal定義みたいなもん
可能な限りval使えると変数のスコープが小さくなって保守しやすくなりそう

Some.kt
var a: String = "hogehoge"
a = piyo //OK

val b: String= "hogehoge"
b = piyo //コンパイルエラー。 "Val cannot be reassined"(再代入すんな)

型推論可能

Some.kt
val a = "hogehoge"
a.startsWith("h") // startWithはStringクラスが持つメソッド。 コンパイルエラーにならない

あとlateinitとかあった気がするけどなんだっけ。。。
lateinitはインスタンス生成時にまだ初期化したくないフィールドに対して使うもので、型推論とは別物。

クラス定義周り

クラスを作る側の話。

CompanionObject

インスタンス生成せずにメソッド/変数を呼びたい時、static定義ではなく、
代わりにCompanionObjectなるものを用いる

Some2.kt
class Hoge {
//クラス内で定義
    companion object {
        fun fuga(str : String) {
            println("$str")
        }
    }
}

Hoge.fuga("Kotlin")

変数定義についても同様
クラス内での変数定義の話

データクラス

クラスにlombok(@Data)が外部ライブラリなしで実現できるようになったイメージ
エンティティクラスとかによく使われそう

equals
hashCode
copy
toString()Object(key1=value1, key2=value2)型のもの
componentN

Book.kt
import java.time.LocalDate
data class Book {
    var id: Long? = null,
    var title: String? = null,
    var author: String? = null,
    var releaseDate: LocalDate? = null
)

デフォルトで継承不可(Javaでいうfinalクラスみたいなもん)

User.kt
class User(val name: String, val age: Int) //継承不可

明治的に継承させたくない場合はsealdをつける
指摘頂いて修正。sealdの挙動は下記でした。

  • 同じファイルに定義されているクラスからのみ継承を許す (=別ファイルに定義されたクラスから継承不可)
User.kt
seald class User(val name: String, val age: Int) //「別ファイルに定義されたクラスから」継承不可

明示的に継承させたくない場合はfinalをつける(Javaと一緒だが、デフォルトで継承不可なのであんまり使うことなさそう)

User.kt
final class User(val name: String, val age: Int) //継承不可

他ファイルでもそのクラスを継承したい場合はopenをつける

User.kt
open class User(val name: String, val age: Int) //継承可

getter/setter定義不要

User1.kt
  class User1 {
    var name: String = ""
  }
Some.kt
  val user = User1()
  user.name = "Tanaka" // user.setName()とかしなくて良い
  println(user.name) //Tanaka

・プロパティをvalで定義するとgetterのみ生成される(再代入不可なので当然っちゃ当然)

getter/setterを拡張することもできる

getter拡張

User2.kt
  class User2 {
    lateinit var name: String
    // isValidNameプロパティに対するget()関数の処理を書き換える
    val isValidName: Boolean
      get() = name != ""
  }
Some.kt
  val user = User2()
  user.name = "Tanaka" 
  println(user.isValidName) //True

setter拡張

User3.kt
  class User3 {
    var name: String = ""
      //空文字の場合はデフォルト値として"Kotlin"を返す拡張
      set(value) {
        if (value == "") {
          field = "Kotlin"
        } else {
          field = value
        }
      }
  }
Some.kt
  val user = User3()
  user.name = "" //空文字を登録しているようで、拡張setterで実質"Kotlin"を代入している
  println(user.name) //"Kotlin"

プライマリコンストラクタ、セカンダリコンストラクタ

プライマリコンストラクタ

Person.kt
class Person constructor(
  val firstName: String, 
  val lastName: String, 
  var isEmployed: Boolean = true
)

コンストラクタに注釈・可視性修飾子がない場合は、constroctorを省略できる

Person.kt
class Person(
  val firstName: String, 
  val lastName: String, 
  var isEmployed: Boolean = true
)

コンストラクタに注釈・可視性修飾子がある場合は、constructor必須

Person.kt
class Person public consutructor (
  val firstName: String, 
  val lastName: String, 
  var isEmployed: Boolean = true
)

セカンダリコンストラクタ

Person.kt
class Person (private val name: String, private val age: Int) {
    // thisはプライマリコンストラクタを指す
    constructor(name: String) : this(name, 20)
}

これ調べてる時、kotlinでSingletonパターン書こうとしたらどうなるんだろうな...と思った。
結果、objectで生成する方法が有力らしい

インスタンス生成周り

クラスを使う側の話。

List,Mapのインスタンス生成(ミュータブル or 読み取り専用OK)

Some.kt
  val mutableList = mutableListOf(1, 2, 3) //可変 add可能
  val List = listOf(1, 2, 3)  //読み取り専用 getのみ可能...★
  val mutableSet = mutableSetOf(1, 2, 3) //可変 add可能
  val set = setOf(1, 2, 3)  //読み取り専用 getのみ可能...★

★なお、List,Setは"読み取り専用"なだけで、イミュータブルではない。
一度定義した後でも、下記のように参照先を書き換えると変更可能。

Some.kt
    val mutableList = mutableListOf(1, 2, 3)
    val list: List<Int> = mutableList //Listインターフェース。 読み取り専用。この時点では(1,2,3)
    println(list) //1,2,3

    mutableList.add(9)
    println(mutableList) //1,2,3,9
    println(list) //1,2,3,9 

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
13
Help us understand the problem. What are the problem?