Androidなんも分からん (Kotlin編2)
初めに
前回の続き
真面目にやる
続き
Functions and Lambdas
function分からんとなんも分からんよな。
Functions
あまり新しいことはなかった。ほぼScalaと同じ感じで定義できる。
1 2 3 |
fun double(x: Int): Int { return 2 * x } |
Swiftのような、というよりはScalaに近いnamed argumentsの仕組みがある。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
fun reformat(str: String, normalizeCase: Boolean = true, upperCaseFirstLetter: Boolean = true, divideByCamelHumps: Boolean = false, wordSeparator: Char = ' ') { ... } reformat(str, normalizeCase = true, upperCaseFirstLetter = true, divideByCamelHumps = false, wordSeparator = '_' ) |
vararg
を引数につけると、いわゆる可変長引数にすることができる。arrayの前にspread operatorと呼ばれる*
を付与することで、arrayの各要素を展開することができる。
1 2 3 |
fun foo(vararg strings: String) { ... } foo(strings = *arrayOf("a", "b", "c")) |
infix
をつけると中置記法を使える。ただし、1引数のmember functionもしくはextension functionのみで、引数はデフォルト値ももたず、vararg
であってもダメ。
1 2 3 4 5 6 7 |
infix fun Int.shl(x: Int): Int { ... } // calling the function using the infix notation 1 shl 2 // is the same as 1.shl(2) |
また、再帰呼び出しを行う関数に対して、tailrec
をつけると、末尾再帰化を試みる。
1 2 3 4 |
val eps = 1E-10 // "good enough", could be 10^-15 tailrec fun findFixPoint(x: Double = 1.0): Double = if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x)) |
Lambdas
function typeをinterfaceとして実装したclassを定義できる(マジか)。
1 2 3 4 5 |
class IntTransformer: (Int) -> Int { override operator fun invoke(x: Int): Int = TODO() } val intFunction: (Int) -> Int = IntTransformer() |
また、function typeの(A, B) -> CとA.(B) -> Cは交換可能らしい(マジか)。
1 2 3 4 5 6 7 |
val repeatFun: String.(Int) -> String = { times -> this.repeat(times) } val twoParameters: (String, Int) -> String = repeatFun // OK fun runTransformation(f: (String, Int) -> String): String { return f("hello", 3) } val result = runTransformation(repeatFun) // OK |
lambda式の定義は次のようにしてできる。return typeがUnitでない場合、一番最後に評価された式がそのlambdaからのreturn valueになる。
1 |
val sum = { x: Int, y: Int -> x + y } |
高階関数において、関数を引数リストの最後にとる場合、Swiftなどでよくある形でlambdaを渡すことができる。
1 |
val product = items.fold(1) { acc, e -> acc * e } |
また、コンパイラがlambdaに渡される引数の型や返り値の型を推論できる場合、->
などは省略でき、代わりにit
という変数を引数値として使うことができる。Swiftみたく$0
とかではなくて名前がついちゃっててちょっとキモいかも。
1 |
ints.filter { it > 0 } |
anonymous functionというものもある。
1 |
ints.filter(fun(item) = item > 0) |
こいつの利点は、ラベル無しでただreturn
を書いたときに、そのfunからreturnする点。つまり、lambdaのときのように、内包しているfunctionそのものをreturnするという動作にならない。
Inline Functions
inline classと同様に、inline functionという機能もある。inlineが付与されたfunctionは、呼び出した箇所でコンパイラによってコード展開され、function objectを生成するといったoverheadを少なくできる。
1 |
inline fun <T> lock(lock: Lock, body: () -> T): T { ... } |
inline functionにおいて、型パラメータにreified
を付与すると、function内で、渡された型情報を知ることができる(type erasureされない)。
1 2 3 4 5 6 7 8 9 |
inline fun <reified T> TreeNode.findParentOfType(): T? { var p = parent while (p != null && p !is T) { p = p.parent } return p as T? } treeNode.findParentOfType<MyTreeNode>() |
Multiplatform Programming
ほぇ〜と思った点だけ抜粋。
expect
というkeywordを付与すると、各platform上で実装されることを想定して、宣言時点では実装を与えなくてもよいようにできる。
1 2 3 4 5 6 7 8 9 |
package org.jetbrains.foo expect class Foo(bar: String) { fun frob() } fun main() { Foo("Hello").frob() } |
これを実装するJVM上のコードは次のようになる。actual
がきも。
1 2 3 4 5 6 7 |
package org.jetbrains.foo actual class Foo actual constructor(val bar: String) { actual fun frob() { println("Frobbing the $bar") } } |
Others
この辺くらいまでやって終わりにしよ。
Destructuring Declarations
Destructuring Declarationってなんのこっちゃって思ったけど、コードみて完全に理解した。これそういう名前なんね。
1 |
val (name, age) = person |
実際には、コンパイラによって次のコードに変換されるらしい
1 2 |
val name = person.component1() val age = person.component2() |
なるほどね。map
のkey-value pairの宣言もできるし、for-loop内でも使える。
1 2 3 |
for ((key, value) in map) { // do something with the key and the value } |
Collections
Kotlinでは、明確にmutableとimmutableなcollectionを区別する。次のコードが好例。
1 2 3 4 5 6 7 8 9 |
val numbers: MutableList<Int> = mutableListOf(1, 2, 3) val readOnlyView: List<Int> = numbers println(numbers) // prints "[1, 2, 3]" numbers.add(4) println(readOnlyView) // prints "[1, 2, 3, 4]" readOnlyView.clear() // -> does not compile val strings = hashSetOf("a", "b", "c", "c") assert(strings.size == 3) |
Ranges
range expressionのfunction形式がrangeTo
とあるように、..
は右側の値も含む。
1 2 3 |
if (i in 1..10) { // equivalent of 1 <= i && i <= 10 println(i) } |
step
やdownTo
もあるし、なんならuntil
もある(あるんかい)。
1 2 3 4 5 6 7 8 |
for (i in 1..4 step 2) print(i) for (i in 4 downTo 1 step 2) print(i) for (i in 1 until 10) { // i in [1, 10), 10 is excluded println(i) } |
閉区間と半開区間周りは言語によってはややこしいから、Swiftみたく...
と..<
みたいな素直なのが良いよね。。
Type Checks and Casts
ランタイムにおける型チェックにis
および!is
が使える
1 2 3 4 5 6 7 8 9 10 |
if (obj is String) { print(obj.length) } if (obj !is String) { // same as !(obj is String) print("Not a String") } else { print(obj.length) } |
そして噂のSmart Cast。これほんと賢いでしょ。。。
1 2 3 4 5 6 7 |
// x is automatically cast to string on the right-hand side of `||` if (x !is String || x.length == 0) return // x is automatically cast to string on the right-hand side of `&&` if (x is String && x.length > 0) { print(x.length) // x is automatically cast to String } |
as
を用いたcastはexceptionを投げる可能性があるため、Unsafe castと呼ばれている。
1 |
val x: String = y as String |
また、as?
を用いたcastは、exceptionを投げない代わりに結果がnullableなので、Safe (nullable) castと呼ばれている。
1 |
val x: String? = y as? String |
Genericなclassの型情報はコンパイル時に消去されるため、型パラメータを含めたis
のチェックは行えない。代わりに*
を型パラメータの位置に書くことで、Genericsを無視した単なるclassとしてのis
チェックは行える。
1 2 3 |
if (something is List<*>) { something.forEach { println(it) } // The items are typed as `Any?` } |
なお、functionの引数などで型情報を明記している場合は、is
チェック時に<*>
を省略できる。
1 2 3 4 5 |
fun handleStrings(list: List<String>) { if (list is ArrayList) { // `list` is smart-cast to `ArrayList<String>` } } |
This expressions
receiverとしてthis
keywordを使用できる。inner class
内やreceiverが指定されたfunction内など、どのreceiverを指すのか不明になる場合は、this@A
のようにラベルで明記してあげる。Kotlinめっちゃラベル使うやん。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class A { // implicit label @A inner class B { // implicit label @B fun Int.foo() { // implicit label @foo val a = this@A // A's this val b = this@B // B's this val c = this // foo()'s receiver, an Int val c1 = this@foo // foo()'s receiver, an Int val funLit = lambda@ fun String.() { val d = this // funLit's receiver } val funLit2 = { s: String -> // foo()'s receiver, since enclosing lambda expression // doesn't have any receiver val d1 = this } } } } |
Equality
Structural equalityのチェックには==
を使用し、Referential equalityのチェックには===
を使用する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
data class Eq(val name: String) val eq1 = Eq("kagemiku") val eq2 = Eq("kagemiku") eq1 == eq2 // true eq1 === eq2 // false val eq3 = eq1 eq1 == eq3 // true eq1 === eq3 // true val eq4 = eq1.copy() eq1 == eq4 // true eq1 === eq4 // false |
Operator overloading
operatorのoverloadもできる。その際、どのoperatorがどのmethod名に変換されるのかは予め決められている。次のコードはunaryMinus
の例。すなわち単項演算子である–をoverloadしている。
1 2 3 4 5 6 7 8 9 |
data class Point(val x: Int, val y: Int) operator fun Point.unaryMinus() = Point(-x, -y) val point = Point(10, 20) fun main() { println(-point) // prints "Point(x=-10, y=-20)" } |
Null Safety
多くのモダンな言語と同様に、Kotlinにおいてもnon-nullとnullableの区別がある。
1 2 3 |
var b: String? = "abc" b = null // ok print(b) |
もちろん、bob?.department?.head?.name
のようなsafe callのchainもできる。
Elvis operatorと呼ばれる演算子がある。Swiftでいう??
(nil合体演算子)。
1 |
val l = b?.length ?: -1 |
not-null assertion operatorもある。!!と書く(Swiftよりヤバイ度合いが伝わる)。nullの場合はexceptionが投げられる。
1 |
val l = b!!.length |
Exceptions
exceptionを投げる可能性のあるコードはtry expressionで包む。expressionであるため、2つめのような記述もできる(エモい)。
1 2 3 4 5 6 7 8 9 10 11 |
try { // some code } catch (e: SomeException) { // handler } finally { // optional finally block } val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null } |
throw
もexpressionであるため、Elvis expressionとともに使うことができる。
1 |
val s = person.name ?: throw IllegalArgumentException("Name required") |
また、throw
expressionはNothingという特別な型となっている。
1 2 3 |
fun fail(message: String): Nothing { throw IllegalArgumentException(message) } |
このNothingという型は、throwを伴わないexpressionにおいても、ときおり目にすることがある。それはnullだけを取りうる変数の型として、である。(なるほど)
1 2 |
val x = null // 'x' has type `Nothing?` val l = listOf(null) // 'l' has type `List<Nothing?> |
Annotations
annotationとは、コードに対してmetadataを付与することのできる機能である。annotationはannotation
keywordと共にclassを宣言することで作成できる。
1 2 3 4 5 6 7 8 9 10 11 |
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION) @Retention(AnnotationRetention.SOURCE) @MustBeDocumented annotation class Fancy @Fancy class Foo { @Fancy fun baz(@Fancy foo: Int): Int { return (@Fancy 1) } } |
Reflection
最も基礎的なreflectionは、ランタイムにclassの型情報を得る機能である。
1 |
val c = MyClass::class |
また、::
operatorを用いることで、高階関数に対して関数の型を渡すことができる。
1 2 3 4 |
fun isOdd(x: Int) = x % 2 != 0 val numbers = listOf(1, 2, 3) println(numbers.filter(::isOdd)) |
Type-Safe Builders
長いので詳細は省く。
Kotlinでは、HTML.() -> Unit
のように、function typeにrecieverのtypeも指定できるため、エモい感じでDSLを作ることができる。これ面白そう。遊んでみよ。
Type Aliases
typealiasは次のようにして宣言できる。
1 2 3 |
typealias NodeSet = Set<Network.Node> typealias FileTable<K> = MutableMap<K, MutableList<File>> |
終わりに
Kotlin完全に理解しました。嘘です。
とりあえず、概要は把握できたと思う。ざっと見た上でKotlinの際立ってる点をあげると、
- Smart Castが激エモい
- Enum Class/Data Class/Sealed Classというように、基本的にclassがベースとなっている
- 構文レベルでDelegation Pattern推している
- function typeでreceiverの型も指定できる(表現の幅が広がり、DSLのようなものが容易に作れる)
といった感じ。だと思う。
なお、この記事におけるコード例は、そのほとんどがKotlinの公式からコピペしたものです。
次からはぼちぼちAndroid SDKを触り始めよう。
最近のコメント