前回では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の実行環境 (基礎編)
