前回ではKotlinの開発環境構築について解説しました。今回はKotlinのプログラミング言語としての文法や機能をじっくり紹介していきます。
定番のHello World
まず紹介するのはHello Worldプログラムです。第1回でも紹介しましたが、
リスト1は実行されると標準出力にmain
関数はKotlinプログラムのエントリポイントです。main
関数はクラスに属さずパッケージ直下に置く必要があります。パッケージ名はJavaと同じようにドメイン名をひっくり返してピリオド.
)
package com.taroid.sample
fun main(args: Array<String>) {
println("Hello, world!")
}
実際に動かすにはKotlinコンパイラやIntelliJ IDEA
変数の使い方
今はただ世界に挨拶するだけのプログラムですが、
fun main(args: Array<String>) {
val name: String = "Taro"
println("Hello, ${name}!") // => Hello, Taro
}
name
という名前の変数に挨拶する相手の名前を代入していますval
キーワードまたはvar
キーワードが必要です。val
を使うとその変数は再代入、var
を使うと再代入可能な変数になります。基本的にはval
を使用し、var
を使うのは最小限にとどめておきましょう。
name
の宣言でString
という型がアノテートされています。name
はString
型であることを明示しているわけです。しかし右辺の"Taro"
という文字列リテラルの存在によって、name
がString
=
文字列)name
をString
型だと推論してくれます。たとえばval name = "Taro"
のように変数定義ができます。このようなしくみを型推論と呼びますが、
さて、name
という変数に代入していることがわかりました。次に挨拶文を出力するコードです。"Hello, ${name}!"
はStringテンプレートと呼ばれている機能です。式を埋め込むことが可能で、name
の中身が"Taro"
なので"Hello, Taro!"
という文字列が得られます
コマンドライン引数の使い方
挨拶する相手の名前を変数に定義しましたが、main
関数の引数args
にコマンドライン引数が設定されています
fun main(args: Array<String>) {
val name = args[0]
println("Hello, ${name}!")
}
args
はArray<String>
からもわかるとおり文字列の配列です。0が配列の最初の要素のインデックスです。args[0]
には複数渡され得るコマンドライン引数の最初の引数が代入されています。コマンドライン引数が指定されずに実行された場合、args[0]
によりクラッシュします。
if式の使い方
そこで、args
が空の場合は、
fun main(args: Array<String>) {
if (args.isNotEmpty()) {
println("Hello, ${args[0]}!")
} else {
println("Hello, 名無しさん!")
}
}
isNotEmpty
メソッドにより、isNotEmpty
がtrue
を返す場合はifの後に続くブロックを実行します。それ以外の場合はelseの後に続くブロックを実行します。Javaにおけるif-elseと同様に、{ }
)
Kotlinのif-elseは式です。つまりif-elseは値を返します。それはちょうどJavaの条件演算子
fun main(args: Array<String>) {
val name = if (args.isNotEmpty()) args[0] else "名無しさん"
println("Hello, ${name}!")
}
forループの使い方
複数指定されたコマンドライン引数に対し、for
ループを使います
fun main(args: Array<String>) {
for (name in args) {
println("Hello, ${name}!")
}
}
このように、for
は、for
はサポートしていません。
Kotlinの関数の定義の方法
まずは簡単な関数を定義する例を示します
fun hello(name: String) {
println("Hello, ${name}!")
}
リスト7にhello
という名前の関数を定義しました。関数を定義するにはfun
キーワード、
関数定義と関数の呼び出し例をリスト8に示します。"Kotlin"
を引数に、hello
を呼び出しています。
fun hello(name: String) {
println("Hello, ${name}!")
}
fun main(args: Array<String>) {
hello("Kotlin") // => Hello, Kotlin!
}
値を返す関数
引数を取って、plus
をリスト9に定義しました。
fun plus(a: Int, b: Int): Int {
return a + b
}
fun main(args: Array<String>) {
println("2 + 5 = ${plus(2, 5)}")
// => 2 + 5 = 7
}
先ほどのhello
関数と違うのは、return
キーワードにより値を返しているところです。戻り値の型は、:
)plus
関数の戻り値の型は2つのInt
型の足し算なのでInt
です。return
キーワードは関数の値を返すためのキーワードです。
plus
関数は、return
文のみにより構成されているので、
fun plus(a: Int, b: Int): Int = a + b
関数の基本的な使い方は以上です。リスト11のように面白い関数を作って遊んでみましょう!
// 掛け算
fun times(a: Int, b: Int) = a * b
// 平方
fun square(n: Int): Int = times(n, n)
// 大きい方を返す
fun max(a: Int, b: Int): Int = if (a < b) b else a
// 小さい方を返す
fun min(a: Int, b: Int): Int = if (a <= b) a else b
// 最大公約数を返す
fun gcd(a: Int, b: Int): Int {
var x = max(a, b)
var y = min(a, b)
while(y != 0) {
val w = y
y = x % y
x = w
}
return x
}
デフォルト引数と名前付き引数
関数の引数にはデフォルト値を設定しておくことができます
fun hello(name: String, exclamation: Boolean = false) {
val suffix = if (exclamation) "!" else ""
println("Hello, ${name}${suffix}")
}
リスト12のhello
関数は、Boolean
型のexclamation
という引数を持っていますが、
// 第2引数を省略
hello("Kotlin") // => Hello, Kotlin
// 第2引数を指定
hello("Kotlin", true) // => Hello, Kotlin!
また、
hello(name = "Foo")
// 引数リストの順番に従う必要はない
hello(exclamation = false, name = "Baz")
再帰呼び出し
関数が、
fun sum(ints: List<Int>): Int {
var sum = 0
for (e in ints) {
sum += e
}
return sum
}
for
によりループを回しています。変数sum
はvar
により宣言されており、
fun sum(ints: List<Int>): Int =
if da(ints.isEmpty()) 0
else ints.first() + sum(ints.drop(1))
for
も再代入もなくなりました。代わりにsum
関数の定義の中で自分自身を呼び出しています。
ちなみに、isEmpty
、first()
、drop(1)
は整数のList
であるints
のメソッドです。それぞれ、
リスト11で定義した最大公約数を求めるgcd
関数を再帰呼び出しを使って実装してみましょう
fun gcd(a: Int, b: Int): Int {
val x = max(a, b)
val y = min(a, b)
return if (y == 0) x
else gcd(y, x % y)
}
var
もwhile
ループも、
再帰呼び出しの欠点は、
末尾呼び出しとは、gcd
関数は末尾呼び出しを行っています。このような再帰関数にtailRecursive
tailRecursive fun gcd(a: Int, b: Int): Int {
val x = max(a, b)
val y = min(a, b)
return if (y == 0) x
else gcd(y, x % y)
}
最適化が有効になるのはあくまで末尾呼び出しのみなので、
関数オブジェクトと関数型
Kotlinでは関数をほかの値と同じように変数に代入したり、
fun succ(n: Int) = n + 1
val hoge = ::succ
定義されたsucc
関数を変数hoge
に代入しました。ポイントは、::
と記述することです。::
を置くことで関数オブジェクトを得ることができるのです。
関数オブジェクトの関数としての機能を呼び出すには、invoke
メソッドを使います
val r = hoge.invoke(5)
println(r) // => 6
この機能はよく使うので構文糖衣が提供されています。リスト21のように普通の関数呼び出しに似ています。
val r = hoge(5)
println(r) // => 6
ところで、hoge
の型を明示していませんでしたが、hoge
の宣言を型推論に頼らないで記述するとリスト22のようになります。
val hoge: (Int) -> Int = ::succ
(Int) -> Int
の部分が関数の型です。->
を挟んで左が引数の型リスト、(Char, Int) -> String
のようになります。
高階関数
関数オブジェクトと関数型についてわかったので、
fun apply(n: Int, f: (Int) -> Int): Int {
println("開始")
val r = f(n)
println("終了")
return r
}
apply
関数は引数を2つ取ります。Int
型のn
と、(Int) -> Int
型f
です。apply
関数の動きとしては、n
を引数にf
を適用した結果を返すだけです。では、apply
関数を使ってみましょう
val got = apply(5, ::succ)
println(got) // => 6
i
として5
を、f
としてリスト19で定義したsucc
関数の関数オブジェクトを渡しています。このコードを実行すると
もう少し複雑で役に立ちそうな例を見てみましょう。リスト25で定義したmap
関数は、
fun map(ints: List<Int>, f: (Int) -> Int): List<Int> {
val newList = java.util.ArrayList<Int>()
for (e in ints) {
newList.add(f(e))
}
return newList
}
関数シグネチャを注意深く見てみましょう。map
関数は2つの引数を取ります。Int
のList
であるints
が第1引数で、(Int) -> Int
型のf
は、List<Int>
で、
次に関数本体です。新しいリストが欲しいので、java.
)newList
という名前を付けておきます。ここでfor
ループが登場し、ints
の各要素に対してループします。各要素はf
の引数となり、newList
に追加されていきます。こうしてnewList
は元のリストの各要素が変換された値で構成されるリストとなり、map
の戻り値となります。
実際にmap
を使ってみましょう
// [2, 3, 4]のリストを作る
val src = listOf(2, 3, 4)
// 平方を得る関数を各要素に適用
fun square(n: Int): Int = n * n
println(map(src, ::square)) // => [4, 9, 16]
// 階乗を得る関数を各要素に適用
fun factorial(n: Int): Int =
if (n == 1) 1
else n * factorial(n - 1)
println(map(src, ::factorial)) // => [2, 6, 24]
このように、map
関数はリストの各要素を別の値に変換する処理ですが、
クロージャ
定義済みの関数を::
により、
// ①
fun succ(n: Int): Int = n + 1
map(list, ::succ)
// ②
map(list, fun(n: Int): Int { return n + 1 })
// ③
map(list, {n: Int -> n + 1})
②や③のように関数オブジェクトを直接生成するような記法を関数リテラルと呼びます。ほかの言語ではラムダ式や無名関数と呼ばれるものです。③のような関数リテラルの書式を一般化すると次のようになります。
{引数リスト -> 関数本体}
関数リテラルには波括弧が必須であることに注意してください。また、
// 関数リテラル内で型推論が働く
val foo: (Int) -> Int = {
n -> n + 1
}
// 引数が1つの場合は暗黙の変数itが使える
val bar: (Int) -> Int = {
it + 1
}
// 複数の文を持つ関数リテラル
val baz: (Int) -> Int = {
var sum = 0
for (e in 1..it) {
sum += e
}
sum
}
// 高階関数に渡す特殊な記法
map(listOf(1, 2, 3)) {
it + 1
}
ところで、
今、counter
があります。
fun counter(): ()->Int {
var count = 0
return {
count++
}
}
この関数はInt
を返す関数」Int
を返す関数」{ count++ }
のことです。変数count
は関数Aの外で宣言されていますが、count
の値を返して、count
をインクリメントします。
関数counterの使用例がリスト30です。
val counter1 = counter()
println(counter1()) // => 0
println(counter1()) // => 1
println(counter1()) // => 2
val counter2 = counter()
println(counter2()) // => 0
println(counter2()) // => 1
counter()
により関数Aを取得しています。関数Acounter1
などと名前を付けています)
まとめ
Hello Worldプログラムを通じて、val
とvar
による変数宣言とStringテンプレート、for
ループは、
後半はKotlinの関数について学びました。関数の引数にはデフォルト値を設定しておくことができます。また関数呼び出し時に、
次回はクラスについて解説します。
本誌最新号をチェック!
Software Design 2022年9月号
2022年8月18日発売
B5判/
定価1,342円
- 第1特集
MySQL アプリ開発者の必修5科目
不意なトラブルに困らないためのRDB基礎知識 - 第2特集
「知りたい」 「使いたい」 「発信したい」 をかなえる
OSSソースコードリーディングのススメ - 特別企画
企業のシステムを支えるOSとエコシステムの全貌
[特別企画] Red Hat Enterprise Linux 9最新ガイド - 短期連載
今さら聞けないSSH
[前編] リモートログインとコマンドの実行 - 短期連載
MySQLで学ぶ文字コード
[最終回] 文字コードのハマりどころTips集 - 短期連載
新生「Ansible」 徹底解説
[4] Playbookの実行環境 (基礎編)