LoginSignup
6

More than 3 years have passed since last update.

Kotlin スタートアップ!

Last updated at Posted at 2018-10-31

第一弾、です。

社内勉強会で発表する時にまとめた資料です。
1時間以内に終わるくらいの気持ちでまとめてたら実際1時間半ちょっとかかって当日の内容がこれだけで終わってしまいました^^;

新人プログラマが Java と比べてって言う目線で書いています。
Kotliner の第一歩になれば。。。
よろしくお願い致します。

基本編

変数

まずは変数の使い方。型の宣言は後ろになります。

定数 -> val (value)
変数 -> var (variable)

…ややこしい。

// Java
int hoge = 0;
final String lang = "java";

// Kotlin
var hoge: Int = 0
val lang: String = "kotlin"

// $で変数を埋め込める
val fuga = "$lang"

// ちなみに String はトリプルクォートで生文字列になる
val str = """
    Kotlin
    はじめよう!
"""
  • 型推論

Kotlin は型推論がきくので初期値与えるときは型を省略できる!かしこい!

Collection

配列

Kotlin では Array になります。
[] で参照できます。

// 配列初期化
val array: Array<Int> = arrayOf(1, 2, 3)
print(array[0].toString())

// プリミティブ型にはそれ用の Array もあります
val intArray: IntArray = intArrayOf(1, 2, 3)

List

Kotlin の List は List MutableList に分かれています。
初期化の仕方はだいたい Array と一緒。
Map とか Set とかもあります。

// List 初期化
val list: List<Int> = listOf(1, 2, 3)

// MutableList は値の変更が可能
val mutableList: MutableList<Int> = mutableListOf(1, 2)
mutableList.add(3)

ただし、例えばこんなこと

val mutable: MutableList<Int> = mutableListOf(1, 2, 3)
val list: List<Int> = mutable

mutable.add(4)
print(list) // 1, 2, 3, 4

MutableList は List なので代入できてしまうんですよね。
要するに List はイミュータブルなものよりは、ただリードオンリーなものとして扱っていく事になるんですかね。

関数

funです。ちょっと変な感じがしますね。。。

Java の void は Kotlin では Unit になります。
Unit に関しては省略しても問題ないのでかなり簡潔に書けますね。

// Java
public void hoge() {
    ...
}
public String fuga() {
    return "java";
}

// Kotlin
fun hoge() {
    ...
}
fun fuga(): String {
    return "kotlin"
}

また、Kotlin では引数にデフォルト値を持たせることができる。
かなり便利!
無駄にオーバーロードして数増やすならデフォルト値を持たせましょう。

// lang の指定がなければ "kotlin" が代入される
fun fuga(lang: String = "Kotlin"): String {
    return lang
}

関数の中身が式1つだけなら、こんな書き方もできます。

// Fragment Instance の初期化
// 戻り値の型も推論がきくので省略可
fun newInstance(value: String) = Fragment().apply {
            arguments = Bundle().apply {
                putSerializable(KEY, value)
            }
        }

クラス

かなりスッキリします!!

  • Java
public class Hoge {

    //------------- field ---------------
    private String lang;
    private int version;

    //---------- constructor ------------
    public Hoge(String lang, int version) {
        this.lang = lang;
        this.version = version;
    }

    // 上記 field と getter/setter を合わせて
    // property として扱うのが Java
    public String getLang() {
        return this.lang;
    }
    public void setLang(String lang) {
        this.lang = lang;
    }

    // Version getter/setter 割愛

    public String getLangVersion() {
        return this.lang + String.valueOf(this.version);
    }
}
  • Kotlin

Kotlin では field が Property になります。
なので、わざわざsetter/getter関数を作る必要はありません。
裏を返すと Kotlin は field を持てません。

class Hoge {
    var lang: String

    // 一応こんな感じでカスタム getter/setter を設定できます
    var version: Int
        // field を持てないためカスタム getter/setterで
        // 実体にアクセスするためにバッキングフィールドが提供される
        set(value) = { field = value }
        get() = field

    //---------- constructor -----------
    constructor(version: Int) {
        this.lang = "kotlin"
        this.version = version
    }
    constructor(lang: String, version: Int) {
        this.lang = lang
        this.version = version
    }

    //--------- initializer ----------
    init {
        // 何かしらの初期化処理ができるところ
        ...
    }

    fun getLangVersion(): String {
        // 各プリミティブ class には toString メソッドがある
        return lang + version.toString
    }
}

…ちなみに
こんな感じで Property と Constructor の宣言が一緒にできちゃいます

// プライマリコンストラクタ
class Hoge(var lang: String, var version: Int) {
    // セカンダリコンストラクタ
    constructor(version: Int): this("Kotlin", version)
}

…さらにさらに
Kotlin には引数にデフォルト値を指定できます!!

class Hoge(var lang: String = "Kotlin", var version: Int) {
    ...
}

Java に比べてオーバーヘッドと言うか無駄な部分が減ってスッキリ書けますねー。

継承

基本的に Kotlin の class や function は final です。
オーバーライドさせたいときは open 修飾子を使います。

open class BaseFragment: Fragment() {
    open fun initialize() {
    }
}

class HogeFragment: BaseFragment() {
  override fun initialize() {
  }
}

ちなみにあんまり継承はされるべきではないよねって言うのがベストプラクティスらしいですね。。。

データクラス

Java でもパラメータに指定するデータしか持たない様なクラスを作ることってありますよね。。。
Kotlin ではこんな感じで書けます。

data class RequestParam(
    val token: String,
    val key: String
    val command: Int
)

fun exec() {
    val param = RequestParam("token", "key", 001)

    // 中身を分解して取り出すこともできるよ
    val (token, _, command) = param
    print(token)
}

データクラスの宣言をしておくと equals toString などのメソッドが自動的にコピーされます。

Enum Class

型安全な列挙ができます。
初期値も持てちゃいます。

enum class Error(val code: Int) {
    RequestError(0),
  UnknownError(1),
  ServerError(2)
}

Seald Class

結構強力な機能だと思います。内部クラスにしか継承をさせない。
swift の enum みたいなことができます。

seald class Error() {
    class RequestError(val errorCode: Int): Error()
    class UnknowError(val description: String): Error()
    object ServerError: Error()
}

後述の when と一緒に使うのが強い(KONAMI感)

static

Kotlin には static が存在しません。少し悲しいです。
じゃあどうするの。。。

// companion object を使用する場合
class Hoge() {
    companion object {
        const val KEY = "key"
        fun do() { }
    }
}

// Package Lebel 宣言
// 直書き
package hoge.Util

val KEY = "key"
fun do() {
}

公式的には Package Lebel Function を推奨していますね。

今まで Java で書いていた Util クラスみたいなやつなら

// シングルトン宣言
object DateUtil {
    const val PERIOD = 7
    fun getWeekly(start: Calendar): List<Calendar> {
    }
}

みたいな感じにも書けますよ。

インタフェース

Kotlin のインタフェースはデフォルト実装や Property を持てます。
しかし、バッキングフィールドを持てないので Property の初期化はできません。

interface Listener {
    // バッキングフィールドが提供されないだけで getter の実装は可能
    val start: String
        get() = "start"

    val finish: String

    fun onStart()

    // オーバーライドされる前提でアクセスは可能
    fun onFinish() {
        print(finish)
    }
}

class Fragment: Listener {
    override val finish = "finish"
}

制御構文

インスタンスの初期化

Kotlin では Java と違って new がいらんです。

// Java
Hoge hoge = new Hoge();

// Kotlin
var hoge = Hoge()

for

まず、Kotlin には Java で言う拡張for分しか存在しません。

val hoge: List<Int> = listOf(0, 1, 2, 3)
for (h in hoge)
    print(h.toString())

じゃあ今までの for (int i = 0; i < 10; i++) みたいなのどうやるの?

  • こうやります
for (i in 0 until 10) {
}

for (i in 10 downTo 0 step 2) {
}

until は言語機能ではなく 0.until(10) な感じのメソッドです。
この戻り値は IntRange って言う Iterable な型なので for で使えます。

if

Kotlin の if は式として扱えます。
どういう事かというと・・・

// こんな感じで式の最後に評価される値を直接代入ができちゃいます
val fuga = if (hoge == 0) {
    ...
    "kotlin"
} else {
    ...
    "java"
}

// Javaで言う三項演算子的な
val fuga = if (hoge == 0) "kotlin" else "fuga"

これは慣れるまで少し大変かも…?

when

要するに他言語で言うswitchです。
数ある Kotlin の機能の中で一番お気に入りです!!

ちなみにwhenも if と同じで式として扱えるためこんな感じで変数の初期化に使えます。

val str = when (version) {
    1 -> "1の時"
    2 -> "2の時"
    in 10 until 20 -> "10 - 20 の範囲内の時"
    in list -> "Collection 内に含まれる時"
    else -> "それ以外"
}

when (lang) {
    // 型チェックもできる
    // is は Java で言う instanceof みたいなもの
    is String -> print(lang)
    is Int -> print(lang.toString())
    else -> return
}

// 引数なし
when {
    date.isSaturday -> print("土曜日")
    date.isSunday -> print("日曜日")
    else -> print("平日")
}

// Seald Class と合わせて使うとこんな感じ
when (error) {
    is RequestError -> dispatch(error.errorCode)
    is UnkownError -> print(error.description)
    is ServerError -> pring("Server Error")

    // コンパイラは Error に定義されている class を把握しているので
    // 全て書いてある場合はわざわざ else を書く必要はない
}
  • case がアロー式で書ける!かっこいい!!
  • いちいち break を書かなくていい!スマートかっこいい!!
  • わざわざ事前に格納用の変数宣言しなくていい!かしこいかっこいい!!

要するにかっこいい!!

言葉の意味的にも「これの時は…、これの時は…、それ以外は…」
って感じで switch よりしっくりくる。

else if を使うなら when 使いましょうねって公式にも言われてるのでガンガン使ってってください!!

Null Safety

なんといっても Null Safety。NPE大好きっ子はスルーしてください。
Kotlinの中で一番強力な気が。。。
これだけでも Kotlin を採用する理由になる気が。。。
上で書いてたのは全て Non-null 。

var hoge: String = "hoge"  // Non-Null
var fuga: String? = null   // Nullable

hoge.length   // OK
fuga.length   // Error

Safe Call

Nullable 型の変数が null じゃない場合だけメソッドを呼び出すやり方。
? を付けた後ろは全て Nullable 型になるため、注意が必要です。

// fuga が null の場合、その先は判定されずに null が返る
fuga?.length  // OK

// チェインさせることもできる
// どこかで null が来た場合はそれ以降は処理されずに null が返る
hey?.isThis?.a?.pen

あんまりチェインさせまくるとどこが null かとかバグフィックスが大変だったりとか見た目がアレだったりとか。。。
なので、できるだけ最初の方で Non-Null 化してしまうのがいいですね。

エルビス演算子

値が null の場合に初期値を決めたいときなんかはよくあると思います。
そんな時に使うのが ?:

var hoge: String? = null
var fuga = hoge ?: "NPE!!!!!!!"

ここで便利なのが、?: の後ろに return が書けます。
値が存在しない場合は早々に脱出してしまいましょう。

fun hoge(fuga: String?) {
    val length = fuga?.length ?: return
    print(length)
}

null かもしれない爆弾を引きずり回すのは危ないので早々にnullチェックなりして Non-Null 化してしまいましょう。

…ちなみに
swift の if let みたいなのもかけます。let に関しては後ほど。

var hoge: String? = "kotlin"
hoge?.let{
    print(it)
}
  • お待たせしましたNPE大好きっ子のみなさま!!!!
var hoge: String? = null
var fuga = hoge!!
// _人人人人人人人_
// > NPE大発生!<
// ^Y^Y^Y^Y^Y^Y^

爆発四散。👆👆👆😎
try - catch で運用することになるんでしょうか?🤔
使ったことないです...

Smart Cast

型チェックなんかをするとコンパイラが自動的にキャストしてくれます。
Java ではチェックしたのにキャストして使わなければいけなかったのが煩雑になって見にくいし嫌だったのでこれは嬉しいですねー。

明示的にキャストする場合は as を使うです。

val error = RequestError()
if (error is RequestError) {
    // キャストされて RequestError 内の Property を扱える様になりました
    error.errorCode
}

val e = error as Error()   // Error
// キャストに失敗した場合は null を返す
val e = error as? Error()  // Error?

遅延初期化

基本的に Property は constructor 等で初期化されているのが望ましい。Kotlin では明示されていない場合初期化されている必要があります。

が、セットアップ処理なんかで後から初期化したい時がたまにありますね。
(例 : Activity や Fragment なんかでの Binding)

そんな時に使えるのが lateinit 修飾子です。

class Fragment() {
    // インスタンス初期化時には取れないけど
    // Viewの生成時に取れるからそれまで待たせたい
    private lateinit var binding: FragmentBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
          // ここで初期化
        binding = DataBindingUtil.inflate(
                inflater,
                R.layout.fragment,
                container,
                false
        )
        return binding.root
    }
}

lateinit 修飾子をつけた変数は、書き込まれていない時に参照されると Exception が発生するので注意です
プリミティブ型にも使えないのでお気をつけて。。。

一応初期化されてるかどうかは isInitialized で確認できる。
(けど、それなら Nullable にしておいた方がいいんじゃ。。。)

onCreate や、 onCreateView で初期化できそうな Property は lateinit を使って、それ以外はNullableにするといいかもしれませんね。

  • もう一つ

lateinit は変数にしか使えません。
じゃあ定数は…?

val langVersion by lazy {
    lang + version.toString()
}

みたいな感じで書けます。
最初にアクセスされた時に値を決めて、以降はその値を返します。

by lazy でセットではなく、by は処理を移譲できる言語機能です。(今回は割愛)
lazy は一回 get された時の結果を返し、記憶する標準ライブラリ関数です。
以降は記憶された値を返します。定数っぽいですね。

  • アクセスされる度に値を更新したい

カスタム getter 使いましょう。

高階関数

…と、呼ばれるものがあります。
何かって言うと、パラメータとして関数を受け取ったり返したり、関数をリテラルとしてやりとりをする関数のことです。

… What's?🤔

fun onRequestFinish (callback: () -> Unit) {
    ...
    callback.invoke()
}

onRequestFinish {
    ...
    terminate()
}

onRequestFinish は引数として関数リテラルを取ります。要するにラムダです。
こんな感じの関数のやり取りができます。

Kotlin は各レイヤー間のやりとりが高階関数で補えるため Data Binding (View Model) と相性がいいです。

ちなみに、Kotlin のラムダはクロージャの外側を参照できます。
これはちょっと Java の考え方だと慣れるのが難しいかも。。。?

val total = 0

// 引数の最後が関数リテラルの場合は()の外側に書ける
view.setOnClickListener {
    // クロージャの外側を参照して値を変更
    total += 1
}

Local Return

ラムダから抜ける場合は return にラベルが必要になります。
普通に return を書くとエラーになるので注意が必要です。
※ なんでかは後で出てきます。

onRequestFinish { data ->
    // data が null だった場合は処理を抜ける
    data ?: return@onRequestFinish
}

inline 関数

inline 修飾子がつけられた関数は関数の中身が呼び出された箇所にベタ書きされます。

public inline fun <T> Iterable<T>.forEach(operation: (T) -> Unit): Unit {
    for (element in this) operation(element)
}

上記は、list の forEach の実装です。
コンパイラがどんな風に解釈するかというと。。。

fun hoge(itemList: List<Int>): Boolean {
  itemList.forEach {
    if (it == 0) { return true }
  }
  return false
}

↓

fun hoge(itemList: List<Int>): Boolean {
    for (item in itemList) {
        if (item == 0) { return true }
    }
    return false
}

こんな感じです。

…気づきました?
こっちのクロージャ内では return 使えてますね。。。
inline 展開されたラムダの中では外側に作用できるので return で一番近い関数(ここでは hoge)を抜けます。

  • Kotlin ではクロージャ内の return は必ず外側に作用します。

しかし、inline 展開されていないラムダ内では forEach 内で処理するため外側 (hoge) に作用できません。
ここはちょっと躓くこともあると思います。(実際僕もこけました。。。)

ちなみに、forEach を抜ける場合は return@forEach ってやってあげる必要があるので気をつけてください。

スコープ関数

Kotlin にはスコープ関数って言う標準ライブラリ関数があり、let with run apply also の5つ存在します。
ローカルなスコープを作る関数です。

ここでは let, apply について説明を。。。

let

public inline fun <T, R> T.let(f: (T) -> R): R = f(this)

任意のレシーバを受け取り、任意の型を返す関数リテラルを引数にとります。
もちろん評価されるのは一番最後の式になります。

  • Nullable と組み合わせて使用

swift の if let みたいな感じですねー。再喝

var hoge: String? = "kotlin"
val a = hoge?.let{
    print(it)
}

apply

public inline fun <T> T.apply(f: T.() -> Unit): T { f(); return this }

let と比べてこっちは受け取ったレシーバ自身を返します。
返す前に受け取った関数リテラルを実行します。

  • 初期化処理

インスタンスの初期化時にわざわざ instance.setter みたいな感じで繰り返し呼び出すのはどうかなーって。。。

val anim = ObjectAnimator.ofFloat(showTarget, View.ALPHA, 0f, 1.0f)
    .apply {
        duration = STATE_CHANGE_DURATION
      startDelay = 2
      interpolator = LinearInterpolator()
  }

などなど、うまく使えばスッキリ書けるのでオススメです。
null の時にエルビス演算子と run で早期リターンなんかはよくやります。

こちらがまとまってていい感じなので気になる人は。。。
Kotlin スコープ関数 用途まとめ


拡張

Function

Kotlin では拡張関数が書けます。
Nullableに対しても追加できます。

fun ByteArray.toHexString(): String {
    if (this.isEmpty()) return ""

  return StringBuilder()
        .apply {
            this.forEach { append(String.format("%02x", it)) }
        }.toString()
}

だいたいは Package のトップレベルに書いて Util クラスみたいな感じにできます。
ただし、自由度が高すぎるため運用上は注意が必要ですね。

Property

Property も拡張できてしまいます。
ただし実際にメンバに入れるわけではないのでバッキングフィールドは提供されません。

// 年
val Calendar.year: Int
    get() = this.get(Calendar.YEAR)

// 月
val Calendar.month: Int
    get() = this.get(Calendar.MONTH) + 1

// 日
val Calendar.date: Int
    get() = this.get(Calendar.DATE)

参考

今回参考させていただいた記事です。ありがとうございました。

Kotlin文法 - 基本 - Qiita
かなり参考にさせてもらいました。ほぼこちらのまとめみたいな感じです😂

JavaプログラマがKotlinでつまづきがちなところ - Qiita

Kotlinのsealed classを使いこなす - Qiita

感想

  • 全体的にスマートに書ける
  • モダンな書き方ができる(かっこいい)
  • Java の考え方が抜けないと逆に生産性が落ちるかも。。。(自分はへっぽこアンドロイダーJava歴半年なのでスルッと入ってきました。。。)
  • swift とは書き方が似てるので iOSer が Android アプリ書くなら Kotlin かな
  • 結局チームで開発ってなったら Kotlin は自由度が高過ぎて Java の方がいいのか?🤔

個人的には(色々不満はあるけども) Java よりかなり好きですね。
Java で書いてるサーバサイドのプログラムも書ける (と言う話を聞いた) ので Kotlin の可能性に期待大ですね。
Kotliner が増えて躊躇されずに開発に使える様になるといいなぁ。。。

拙いコードや文章のところもあると思いますが、ここまで読んでいただきありがとうございました。
ご指摘や間違い等ありましたらアドバイスいただけると嬉しいです。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6