当記事では、自作したじゃんけんゲームを題材にしてKotlinの文法や使用するときの注意点などを確認していきます。Windows 10 Home 64bit版環境で検証しています。
ソースコードは以下よりご確認ください。
rps-like/Kotlin at master · tomomoss/rps-like
じゃんけんゲームとは
じゃんけんゲームはコンソール上で動作するCUIゲームです。
基本的なルールは既存のじゃんけんそのものです。じゃんけんで勝つごとに1点取得し、対戦相手よりも先に5点先取することで勝者となります。
対戦内容はログファイルに書き出して、後から読み返せるようにします。
Kotlinとは
Kotlinはチェコに本社を置くJetBrainsによって開発されたプログラミング言語です。2011年7月20日に発表されてからというもの、ジワジワと人気を伸ばしている印象です。
JVM(Java Virtual Machine)上で動作する、いわゆる「JVM言語」という括りに分類されるプログラミング言語の1つでありJavaと強い互換性を有します。
文法もJavaと似ていますが、関数型言語の要素を取り入れたりJavaで指摘された欠点を改良するなどして、より洗練されています。その性質から「Better Java」と評されることがありますが、実際今回触ってみた感触としては、まさに「洗練されたJava」あるいは「近代化されたJava」といった感じでした。Javaを触ったことがある人ならば、すんなりと入っていけるでしょう。
人気がある言語とは言いがたいですが、どちらかというと知名度がないというほうが実態に沿っているでしょう。2017年にはGoogleのAndroidアプリ開発用言語として正式採用されるなど、その実力は本物であると考えます。現場の開発でKotlinを使用された方々の感想などを見ても概ね好評であり、知名度さえ上がっていけばシェアも拡大していくことが予想されます。
ただ、1つ気になるのは、今後Javaとの距離感をどうとっていくのかということです。Javaに寄せればJavaユーザーの取り込みはしやすくなりますが差別化にはなりません。わざわざJavaから乗り換える必要性と価値が薄れるでしょう。逆に、差別化を強めればKotlinらしさを強めることができますが、乗り換えてもらうには大きな強みが求められるでしょう。いずれにせよ今後の動向に注目です。
開発環境構築手順
Kotlinで開発するには、Javaの開発環境であるJDK(Java Development Kit)とKotlinの開発環境用プログラム群が必要です。一般的には開発環境を簡単に整えることができるIDE(Exlipse・IntelliJ IDEA・Android Studio・Gradle Kotlin DSLなど)を使って開発するのが人気らしいですが、今回はコンソールで直接コンパイラを叩くような開発環境を構築してみようと思います。
JDKを導入
まずはJDKを導入します。JDKの導入方法は以下記事を参考にしてください。
Kotlinコンパイラをダウンロード
Kotlinコンパイラをダウンロードします。KotlinコンパイラはGitHubに上がっていますので、そこから落とします。
以下ページは、当記事執筆時点での最新版であるv1.5.10のコンパイラのダウンロードページです。ページ末尾から.zipファイルをダウンロードしてください。
Release Kotlin 1.5.10 · JetBrains/kotlin
Kotlinコンパイラを配置
.zipファイルを解凍すると「kotlinc」というディレクトリが入っているはずですので適当な場所に置いてください。私はドライブ直下に置くようにしています。
PATHを通す
kotlincディレクトリ直下の「bin」ディレクトリにPATHを通しておきましょう。
動作確認
最後に、コンパイラが正常に動くか、PATHが通っているかを確認しておきましょう。
PS C:\Users\tomomoss> kotlinc -version
info: kotlinc-jvm 1.5.10 (JRE 16.0.1+9-24)
PS C:\Users\tomomoss> kotlin -version
Kotlin version 1.5.10-release-890 (JRE 16.0.1+9-24)
ここまで問題なく進めたら開発環境の構築はOKです。
注意したい仕様
基本的な文法などはGitHubに上げているソースコードを読んでいただくか、あるいは自前で調べていただくとして――ここからは、GitHubに上げているソースコードからは読み取れない仕様や初めてKotlinを触る人に向けての注意事項を列挙します。
Kotlinの動かし方
ソースファイルの拡張子は「.kt」です。
ソースファイルが用意できましたらKotlinコンパイラを使い中間ファイル(.classファイル)を生成します。
PS C:\Users\tomomoss> kotlinc .\Main.kt
中間ファイル生成の仕組みはなかなか面白いものになっています。まず、基本的にはソースファイル内で定義されたクラスごとに中間ファイルが生成されます。「Main.kt」というソースファイル内で「Sub」「Foo」というクラスが定義されていた場合、コンパイル後に「Sub.class」と「Foo.class」という中間ファイルが生成されます。
ただし、エントリポイントとなるmain関数を格納したファイルだけは別で、 ソースファイル名とその末尾に「Kt」を付けた独自の中間ファイル が生成されます。つまり、上記「Main.kt」内にmain関数を置いていた場合に生成される中間ファイルは「Sub.class」「Foo.class」に加えて「MainKt.class」も作られるということです。
エントリポイント
Kotlinのエントリポイントは「main」という名前の 関数 です。「ん? 『関数』? 『メソッド』じゃなくて?」と思われるかもしれませんが、あえて関数と表現しています。
Kotlinに影響を与えたJavaというプログラミング言語では、全ての関数――もとい、メソッドは何らかのクラスに属している必要がありました。しかし、Kotlinではクラスに属さないメソッドを定義することができるのです。このメソッドを指して関数と私は呼んでいます。
そして、繰り返すように、エントリポイントとなるのはmain関数です。mainメソッドではありません。
// これはメソッドです。
class Example() {
public fun exampleMethod() {
// ※色々な処理
}
}
// これは関数です。
fun exampleFunction() {
// ※色々な処理
}
また、Javaではコマンドライン引数を格納するmainメソッドの引数を省略できませんでしたが、Kotlinではむしろコマンドライン引数を使わない場合――main関数の引数を使わない場合は 引数の記述を省かないと以下のようなエラーになります 。
PS C:\Users\tomomoss> kotlinc .\Main.kt
Main.kt:1:10: warning: parameter 'args' is never used
fun main(args: Array<String>) {}
変数宣言文の違い
変数を宣言するときはvar文かval文を使うことになります。前者は再代入可能な変数であることを意味しており、後者は再代入不可な変数であることを意味します。なお、これは余談ですがvarはvariableの略で、valはvalueの略であるそうです。
ただ、この説明から「なるほど、val文で宣言した変数には再代入できない――つまり、定数になるということだな!」と早合点してはいけません。val文は、あくまで再代入を許可しないだけで 既に代入されている値を操作することは可能 なのです。
たとえば、val文で宣言した変数に配列を代入した場合、その配列を上書きするように別の値を代入することはできません。ただし、配列に格納された要素を弄ることはできます。
// var文は再代入を許可します。
var variable = arrayOf(1, 2, 3)
variable = arrayOf(4, 5, 6) // 問題ありません。
// val文は再代入を許可しません。
val value = arrayOf(1, 2, 3)
value = arrayOf(4, 5, 6) // ここでエラーになります。
// 再代入は許可しませんが値を操作することは問題ありません。
value[0] = 7
数値レンジ
連続した数値の表現は簡単に行えます。
fun main() {
for (i in 1..3) {
println(i)
}
}
PS C:\Users\tomomoss> kotlin ExampleKt
1
2
3
文字列テンプレート
文字列テンプレートを使うと、文字列中に変数や式を組み込みやすくなります。
変数を組み込む場合はドル記号に続けて変数名を記述します。ただし、 文字列中変数に文字列を接続することはできません 。文字列中変数の前後は半角空白か、あるいは始端・終端である必要があります。
fun main() {
val foo = "Hello"
// 「Hello world.」と出力されます。
println("$foo world.");
}
式を組み込む場合はドル記号に続けて波括弧を置き、そのなかに計算式を書きます。
fun main() {
// 「2」と出力されます。
println("${1 + 1}");
}
if式とwhen式
条件分岐用命令文としてif式とwhen式が実装されています。文ではなく式ですので、変数の代入部分に記述することもできます。
if式はif文の式バージョンです。
fun main() {
val foo = true
val result = if (foo) {
"真"
} else {
"偽"
}
println(result)
}
when式はJavaでいうところのswitch文です。
fun main() {
val foo = true
val result = when (foo) {
true -> "真"
false -> "偽"
else -> "真でも偽でもありません"
}
println(result)
}
どちらも、ちょっとクセがありますが慣れると非常に便利だと思います。
fun文の糖衣構文
fun文は次のように省略することができます。関数型言語っぽいですね。
// 普通の書き方です。
fun example(number: Int): Int {
return number * 2
}
// 糖衣構文版です。
fun example(number: Int): Int = number * 2
クラスの定義
Kotlinでクラスを定義する方法は かなり斬新 なものになっているように思われます。とにかく色々な機能があり、良くも悪くも複雑なのです。ですので、当記事では全ての機能は解説せず簡単に説明します。
基本となるのは以下の形です。
アクセス修飾子 class クラス名(コンストラクタに渡す引数) {
init {
// 初期化ブロックと言い、ここがコンストラクタの代わりになります。
}
}
目を引くのはクラス名の後ろに小括弧があり、しかも、そこにコンストラクタに渡す引数を定義するということでしょう。まるで関数やメソッドの定義文のようです。
また、initブロックなる奇妙なものがあることにも驚かされます。なお、コンストラクタに渡す引数が無い場合は小括弧を省略してもかまいませんし、初期化が不要であればinitブロックも省略できます。
アクセス修飾子 class {
// ※色々な処理
}
インスタンス化
他のプログラミング言語とは異なり、Kotlinに newキーワードはありません 。インスタンス化するときはnewキーワードを省くように記述します。
fun main() {
val instance = ExampleClass()
}
シングルトンパターン
シングルトンパターンに則ったクラスを定義するときはobject文を使うと簡単に実装できます。
object クラス名 {
// ※色々な処理
}
クラスの継承と抽象とオーバーライド
Kotlinでは クラスの継承は原則禁止 です。継承する場合は継承元のクラス(スーパークラス・親クラス)にopenキーワードを付ける必要があります。
// 親クラスです。
open class SuperClass() {
// ※色々な処理
}
継承する場合は以下のように記述します。
// 親クラスです。
open class SuperClass() {
// ※色々な処理
}
// 子クラスです。
open SubClass(): SuperClass() {
// ※色々な処理
}
ただし、抽象クラスの場合はopenキーワードは不要です。継承前提だからですね。
// 抽象クラスです。
abstract class AbstractClass() {
// ※色々な処理
}
また、抽象メソッドはオーバーライド時に、オーバーライドしていることを表すoverrideキーワードを付けなければなりません。
// 抽象クラスです。
abstract class AbstractClass() {
// 抽象メソッドです。
abstract fun abstractFun()
}
// 実装クラスです。
class ExampleClass(): AbstractClass() {
// 抽象メソッドを実装しています。
override fun abstractFun() {
// ※色々な処理
}
}
モジュール
Kotlinにはモジュールという機能――いや、概念があります。
モジュールとは1度のコンパイルごとのまとまりを指します。たとえば、「A.kt」と「B.kt」をまとめてコンパイルした場合は両ファイルは同じモジュールに属していると見なされます。
アクセス修飾子の1つである「internal」は、モジュールをスコープとするための修飾子です。
null安全
Kotlinはnull安全の言語です。
null安全であることは多くの効用をもたらしますが、時にはnullを許容しなければならないときがあるでしょう。そのようなときはnullを受け取る対象のデータ型末尾に疑問符記号を付けることで解決します。
fun main() {
// Int型かnullを格納できる変数です。
val foo: Int? = null
}
私が意識していること
Kotlinを使うときに私が意識していることを列挙します。1つ前の「注意したい仕様」を読んでいることを前提とした内容になっています。
配列のインデックス値と要素を同時に取得したいとき
配列のインデックス値と格納された要素を同時に取得したいときは次のような処理が便利です。
fun main() {
val array = arrayOf("第1要素", "第2要素", "第3要素")
for ((index, value) in array.withIndex()) println("$index : $value")
}
PS C:\Users\tomomoss> kotlin ExampleKt
0 : 第1要素
1 : 第2要素
2 : 第3要素