Androidなんも分からん (Kotlin編)

初めに

Androidなんも分からんから今年こそは分かるようになりたい

この記事は分かるようになるまでの過程の自分用メモ。決して初学者用の便利まとめなんかではない。すまん。体裁を整えていないのでクソ長いです。ほんますまん。

とりあえずKotlinがなんも分からん。


環境準備

Kotlin公式のTutorialsに沿って、Kotlinの開発環境のセットアップをする。

方法はいろいろあるっぽいけど、一旦はVimでKotlin書きたいんで、brewでぶちこむ。

vimのkotlin用プラグインをぶちこむ。パッとググって出てきたのはこれ

初めなんかハイライトしてくれなかったけど、kotlinのfiletypeの設定とか書いてガチャガチャしてたらできた。

いつものやつやる。(使ってるHighlighterがKotlinに対応してないっぽいけど、大丈夫なのかな)

Kotlin完全に理解しました。

-include-runtimeってオプションは、Kotlinのランタイムを.jarに埋め込むことで、それ単体で実行可能にしてくれるオプションらしい。他のKotlinプログラムから実行されるライブラリなどを作る際は、このオプションは外してしまって良いらしい。

kotlincjavaの2種類叩くのややこしいな、と思ったらkotlinコマンドというのもあるらしい

-classpathでコンパイル対象にしたいjarファイル群を指定するっぽい。HelloKtは、もとのファイル名がhello.ktだった際に、Kotlinのコンパイラによって生成されるメインクラス名とのこと。なるほどね。分からん。

この辺のtermは、やっぱJavaに対する経験がほどよくあったほうが理解がはやそう。ちなみに僕にはない。


真面目にやる

とりあえず、公式のReferenceを読み進める


Getting Started

とりあえずこれからやろう

Basic Syntax

関数定義はScalaに近いっぽい。defではなくてfunだけど。

2つめの宣言方法とか、いわゆるVoid型を返値とすることを表すUnitとか超Scalaっぽい。ScalaもJVM言語だもんね。というか、KotlinはScalaの影響も多大に受けた言語っぽいのでそれはそう。${a + b}で文字列内での評価結果展開ができるのもいっしょ。

valがimmutable変数宣言で、varがmutable変数宣言なのも同じ。

Optional型というか、nullable変数はInt?のように表す。このSyntaxはモダンなものに近い。

これ驚いた。Swiftとかだとif-let内でas? StringとかしてString型として束縛するところを、Kotlinだとis使った分岐内では自動でその型にキャストしてくれるらしい。isの否定は!isなのはしょうがないけど微妙感ある。

同じことをするifブロックをSwiftで書くとこう。

Kotlinの方が若干コンパクト。

when式はScalaでいうmatch式。switch文ぽいものがになってるのもScalaに同じ。

Idioms

急にCreating DTOs (POJOs/POCOs)とか出てきてDTOってなんだ🤔🤔 と首をかしげていたけど、Data Transfer Objectのことらしい。なるほどね。

Scalaでいうcase classはKotlinではdata classと表記・宣言するっぽい。

lazyに初期化を行う変数は、型宣言のあとにby lazy…と続ける。

SwiftやC#でいうところのnil合体演算子(nil coalescing operator) ??はKotlinでは?:のよう。3項演算子を短縮したように?:が使われていて、こっちのほうが個人的に好み。

Coding Conventions

ふと思ったけど、KotlinのGetting StartedはBasic Syntax -> Idioms -> Coding Conventionsって流れになっててすごくスムーズ。Getting StartedにCoding Conventionsまで入れちゃうのとかかなり良さを感じる。新しいプログラミング言語始めるときって、文法とかはもちろんだけど、文法を超えた、慣例的なIdiomsやCoding Conventionsを本当は知りたい、ってときが多々あるしね。Goに入ってはGoに従え、じゃないけど(ほんますまん)。

クラスを定義する際のクラス内レイアウトについて言及があった。

  1. Property declarations and initializer blocks
  2. Secondary constructors
  3. Method declarations
  4. Companion object

こういうのを公式で書いてくれるのはありがたい。


Basics

続いてBasicsを読む。サクッとKotlin書き始める分にはこの章まででよさそう。

Basic Types

Kotlinの数値型では暗黙的なキャストはできない。明示的にメソッドを呼び出してキャストを行う。

初期値を伴ったArrayの宣言にはarrayOfを使うことができる。

arr2では、primitive型(ここではint)用の特殊なarrayを宣言している。こちらの場合、boxingによるオーバヘッドは発生しない。

文字列は特に変わりなし

immutableであり、s[i]によって各要素にアクセスできる。

Packages and Imports

Kotlinファイルにおいて、いくつかのpackageはデフォルトでimportされている。

  • kotlin.*
  • kotlin.annotation.*
  • kotlin.collections.*
  • kotlin.comparisons.* (since 1.1)
  • kotlin.io.*
  • kotlin.ranges.*
  • kotlin.sequences.*
  • kotlin.text.*

Scalaと同様に、asを用いて別名でimportできる。

Control Flow

Scalaと同様に、ifはexpressionである。各ブロック内の最後に記述された式がif式の値となる。

whenも同様にexpressionである。elseが、Cライクなswitch文でいうところのdefaultとなる。

各分岐条件ではinisを使用できる。

forループはよくある構文。whileループやdo-whileループもある。

Returns and Jumps

Kotlinではfor文などにlabelをつけることができる。breakおよびcontinueでそのラベルを指定したジャンプを行える。

Kotlinでかなり驚いた点。ラムダ式内でのreturnは、もっとも内側のfunctionからのreturnになる。つまりbreakのような振る舞いをする(ラムダ式後続の文が実行されないという点で厳密には異なる)。continueのような振る舞いをさせたい場合、ラムダ式にラベルを付け、returnではそのラベルを明示する必要がある。

2つめの例のように、関数名と同名のラベルをreturn時に指定することで、同様の振る舞いになる。ちなみにラムダ式ではなく無名関数の場合は、最も内側のfunctionがその無名関数であるため、continueと同様な振る舞いになる。


Classes and Objects

クラス分からんとなんも分からんよな。

Classes and Inheritance

クラス宣言時にはprimary constructorおよびsecondary constructorを宣言できる。クラス宣言時に、primary constructor内にvalないしはvar付きで引数を書くと、それがそのままpropertyとして生える。この辺のpropertyの話はScalaといっしょ。

なお、特に修飾子がない場合constructrは省略でき、class Hoge(val id: Int)のように書ける。クラスの初期化は書いた順に上から行われる。

secondary constructorはクラス定義内に書くことができる

このとき、primary constructorがある場合は、上記のthis(name)のように、propertyの初期化をdelegateしてあげる必要がある。

なお、クラスのインスタンスは次のようにして生成する。その際、new keywordはいらない。

クラスの継承、およびメソッドのオーバロードの際は、元となるクラスおよびメソッドにopen修飾子が必須となる。

なお、同名のメソッドをもつ複数の基底クラスを継承したクラスでは、該当のメソッドを必ずoverrideする必要がある。

Properties and Fields

mutableなクラスプロパティはカスタムされたgetter/setterを持てる。

Swiftみたいに中括弧{ }がないからちょっと不安になるな…

Kotlinではfieldの直接的な宣言はできない(つまり、propertyはsetter/getterメソッドを提供しているに過ぎない)。しかし、fieldというキーワードをアクセッサ内で用いることで、backing fieldを自動で生成する。

Interfaces

interfaceの宣言方法はごく普通。interfaceの継承も当然できる。interfaceを実装するclassでは、primary constructorの中でoverrideを付与することで、interfaceのpropertyを実装できる。

Visibility Modifiers

classおよびinterfaceのメンバには次の4つの可視性に関する修飾子をつけることができる。

  • private: 同じclassからのみ参照できる
  • protected: 同じclassおよびそのクラスを継承したクラスからのみ参照できる
  • internal: そのclassが宣言されたmoduleと同じmodule内からのみ参照できる
  • public: そのclassを参照することのできるすべての場所から参照できる

Extensions

KotlinにおけるExtension Functionは次のようにして生やす。Swiftみたいにextensionブロックで囲む感じではない。ドキュメント読んだ感じ、これはC#ライクな方法なのかな。

なお、KotlinにおけるExtension Functionは静的に解決される。ランタイムではなく、コンパイル時の型で呼び出されるfunctionが決まる。

この場合、出力されるのはcである。また、メンバであるmethodと同じsignatureのfunctionをextensionで定義した場合、呼び出されるのはメンバの方らしい。というか定義できちゃうんね。

もちろん、propertyもextensionで追加できる

Swiftの場合、computed property、乱暴に言い換えればgetterのみをもつpropertyだけをextensionで追加できたが、Kontlinの場合はsetterも持たせることができるらしい。その際、明示的な初期化はできないようなので、getter/setterの双方の明示的な実装が必須となる。

Data Classes

data classはScalaにおけるcase class

data classの場合、次のメンバがコンパイラによって自動で生える。初めの2つは明示的に宣言することもできる。

  • equals()/hashCode()
  • toString()
  • componentN()
  • copy()

基本的に、primary constructor内の引数それぞれを考慮したtoString()equals()hashCode()およびcopy()が生える。これらのメソッドに考慮に入れたくないが、propertyとして持ちたいものがある場合、primary constructorからははずしてクラス内に書いてしまえば良い。

copy()の際、特定のpropertyだけを変更してほかはそのままでcopyするといったことができる。

ぶっちゃけstructが欲しいけど、そんなこと言ったらきっとぶん殴られるんだと思う。

Sealed Classes

Scalaのsealed classと同じ。

sealed修飾子がついたclassは自動でabstractとして扱われる。すなわちそのsealed classそのもののインスタンス化はできない。sealed classの利点はScalaのmatch式使用時とほぼ同様である(多分)。Kotlinにおいては、when式使用時にelse節を省略できるようになる。

Generics

Genericなclassの宣言方法は普通。

covariantな(共変な)型パラメータはoutと共に宣言する。

他方、contravariantな(反変な)型パラメータはinと共に宣言する

これらのように、Javaのときとは対象的に、ジェネリクス宣言時に変性を指定するので、declaration-site varianceと呼ぶ。Javaの場合はuse-site varianceと呼ぶらしい。

Scalaの+Aとか-Aとかもややこしかったけど、KotlinはKotlinでややこしいな… 慣れだとは思うけど。

さらに、Javaのようなuse-site varianceもできるらしい。

1つめの場合、fromにはArray<String>も渡すことができる(covariant)。2つめの場合、destにはArray<Any>も渡すことができる(contravariant)。

もちろん、Genericなfunctionも宣言できる。その場合、型パラメータはfunction名よりも前につけ、呼び出す際はfunction名の後につける(ややこしい)。

Nested Classes

innerをネストされたクラスの定義につけると、外側のメンバにアクセスできる。

Enum Classes

enumはclassとして提供される(マジか)。

2つめの例は、enumのそれぞれのconstant(case的な)はenum classのinstanceであることを示している。マジか。

こういうこともできる(マジか)。

ProtocolState.signal()はabstractなメソッドである。それを、それぞれのcaseに付随したanonymous class内でoverrideして実装している(マジか)。

なお、それぞれのenum constantは次のmethodを持っている。

Objects

anonymous classのobjectは次のようにして宣言できる。これはobject expressionと呼ばれる。object expressionは、わざわざclassを宣言したくはないが、anonymous classっぽいものを宣言したいときに便利である。

また、Scalaと同様に、Kotlinもobjectという形でシングルトンなclassを宣言できる。これはobject declarationsと呼ばれる。

Scalaでいうcompanion objectを宣言するには、class内でcompanionを伴ったobjectを宣言する。

Inline Classes

primitive型とかをpropertyとして持っているけど、classのインスタンス化に伴うオーバヘッドがきついよ〜〜〜〜ってときのために、Kotlinではinline classというものがある。(Kotlin 1.3時点でexperimentalな機能らしい)

methodやaccessorも持てるが、それらはstatic methodとして呼び出される(なるほど)。

Delegation

Kotlinでは簡単に、構文レベルでDelegation patternを実現できる。

byによって、Baseの各memberへforwardするmemberがDerivedに生える。methodなどをoverrideした際は、そちらが優先されて呼び出されるっぽい。

Delegated Properties

Delegated Propertiesというものもあるらしい。 setter/getterの処理をwrapできる。

そんで、いくつかのDelegateが標準ライブラリには備わっている。

まずはLazy。多分一番使うのかな?

次にObservable。直前の値と新しい値を、値が更新されるときにhandler内で参照できる。

次にmap。primary constructorなどで渡したmapに実際の値を保存するっぽい。


終わりに

長くなってきたので、続き(functionとか)は別の記事で。

なお、この記事におけるコード例は、そのほとんどがKotlinの公式からコピペしたものです。

kage

A student at Tokyo Institute of Technology. Programmer.

あわせて読みたい

コメントを残す

%d人のブロガーが「いいね」をつけました。