はじめに
久しぶりにネイティブなAndroidアプリを作ってみようと思い、2019年時点の開発環境や開発言語について調べてみました。
Webや書籍をみると、ほとんどが開発言語としてKotlinを利用しているため、まずはKotlinの開発環境の構築と学習を進めていきます。
Kotlin とは
kotlinは、Java言語をもっと簡潔かつ安全になるように改良した産業利用向け汎用言語として開発されたもの。
型推論のある静的型付けのオブジェクト指向プログラミング言語であり、Scalaの関数型プログラミング言語や、C#のジェネリクスの要素などを取り入れている。
Java言語と同様に、Javaバイトコードにコンパイルされて、Java仮想マシン上で動作する。
Java言語との連携が言語仕様として組み込まれているため、Java言語のプロジェクトにKotlinのソースを段階的に追加したり、KotlinからJava言語のクラスやメソッドを呼び出することもできる。
Jetbrains とは
Androidの開発言語としてKotilnが正式に採用されたため、Kotlinという開発言語とセットでGoogle社の名前が目立つが、Kotlinを開発しているのはJetbrains社である。
Jetbrains社は、IntelliJ IDEAというJavaの統合開発環境(IDE)の開発や販売を行っている企業である。
過去にAndroidアプリを開発していたころは、EclipseとADT Pluginの組み合わせが主流でしたが、現在はAndroid Studioが主流となってる。
このAndroid Studioも、Jetbrains社がIntelliJ IDEAをAndroid開発用にカスタマイズしたもの。
ちなみに、Jetbrains社はPythonの統合開発環境であるPyCharmを開発していることでも有名ですね。
開発環境の準備
Kotlinの統合開発環境として、Jetbrains社のIntelliJ IDEAを利用してみました。
IntelliJ IDEA Community Edition のインストール
-
JetBrains社のWebサイトから、IntelliJ IDEA Community Edition をダウンロードします。
-
ダウンロードしたセットアップファイルを実行します。
-
[Next]ボタンをクリックします。
-
インストール先のフォルダを指定して[Next]ボタンをクリックします。
-
必要に応じて設定を変更し、[Next]ボタンをクリックします。Kotlinのソースファイル(.kt)を関連付ける場合は、[.kt]をチェックします。
-
[Install]ボタンをクリックします。
-
インストール完了後、[Finish]ボタンをクリックしてセットアップを終了します。
IntelliJ IDEA Community Edition の起動
-
インストールしたIntelliJ IDEA Community Edition を起動します。
-
初回インストールで設定ファイルを指定する必要が無ければ、インポートしないを選択して[OK]ボタンをクリックします。
-
好きな配色を選んで[Next]ボタンをクリックします。
-
必要な物があれば有効化して[Next]ボタンをクリックします。
-
必要な物があればインストールして[Next]ボタンをクリックします。
-
IntelliJ IDEA Community Edition が起動しました。
IntelliJ IDEA Community Edition でプロジェクトの作成
-
Welcome画面の[Create New Project]をクリックします。
Welcome画面に戻る場合は、[File]→[Close Project]でプロジェクトを閉じます。
-
Kotlin の JVM | IDEA を選択して[Next]ボタンをクリックします。
-
プロジェクト名や場所およびSDKを指定して[Finish]ボタンをクリックします。
-
プロジェクトが作成されました。
-
左ペインのプロジェクトツールウィンドウのツリーを展開すると、このプロジェクトに関連するファイルなどを確認できます。
-
ソースファイルを作成する場合は、ツリーのsrcを右クリックして[New]→[Kotlin File/Class]を選択します。
-
作成するファイルの種類を選択し、ファイル名を入力します。
-
作成したファイルにコードを入力します。
fun main(args: Array<String>) { println("Hello, Kotlin!") }
-
▶(ランボタン)をクリックして[Run 'ファイル名']を選択することで実行できます。
-
実行結果は画面下部のコンソールに出力されます。
-
Kotlinをちょっと試してみたいときは、REPL(read, evaluate, print, loop)を起動します。
-
コードを入力して、[Ctrl+Enter]で実行できますので便利です。
Kotlinの学習とメモ
Kotlinを学習して覚えたことや気になったことなど書き留めます。
開発言語の経験値は C#(7.x) > Java(7あたり) > Python(3.x) > JavaScript(ES6) な感じを前提としたメモです。
後半は息切れ気味ですが、基本的な言語仕様はおおよそ網羅できたかなと思います。
理解が甘い所が多々ありますので順次更新していきます。
参考にさせて頂いた情報
- Kotlin - Android Developers
- 30分で覚えるKotlin文法
- 最初に押さえておきたいKotlin言語仕様
- Kotlinチートシート
- Android開発を受注したからKotlinをガッツリ使ってみたら最高だった
変数
- 変数の場合は var 変数名 : 型
- Kotlin には参照型しかない(プリミティブ型は用意されていない)
- 行末のセミコロンはいらない(あってもよい)
var a : Long = 1000L
var b : Int = 100
var c : Short = 10
var d : Byte = 1
var e : Float = 1.1F
var f : Double = 3.14
var g : String = "Hello!"
var h : Char = 'X'
var i : Boolean = true
- もちろん型推論できる(strはString型となる)
var str = "Good!"
- 読取専用変数の場合は val 変数名 : 型
val num : Int = 20
- 宣言と初期化をわけることもできる
val num : Int
num = 20
定数
- アンダースコア区切りの大文字が一般的
const val MAX_COUNT: Int = 1024
演算
- インクリメントとデクリメントが助かる
a + b // 加算
a - b // 減算
a * b // 乗算
a / b // 除算
a % b // 剰余
a++ // 前置インクリメント
++b // 後置インクリメント
a-- // 前置デクリメント
--b // 後置デクリメント
比較・論理演算
- ===と!==はJavaScriptの厳密等価演算子ではなく参照の比較となる
a < b // 小なり
a <= b // 以下
a > b // 大なり
a >= b // 以上
a == b // 構造等価
a != b // 非等価
a === b // 同じ参照
a !== b // 違う参照
!a // 論理否定
a && b // 論理積
a || c // 論理和
変換
- Intから文字列への変換
val str = num.toString()
- StringからIntへの変換
val num: Int = "3.14".toIntOrNull() ?: 0
- DoubleからIntへの変換
val ret1 = doubleValue.toInt()
var ret2 = doubleValue.roundToInt()
文字列
- "${}"で文字列テンプレート
val str = "Hoge"
val num = 100
val text = """
Moge
"""
println("文字列=$str、数値=${num * 2}") // 式も埋め込める
- このあたりはC#やPythonなどと同様(下記は一部のみ)
val text = "Hello,Goodby"
text.length
text.toUpperCase()
text.toLowerCase()
text.startsWith("He")
text.substring(0,3)
text.replace("H","h")
text.split(",").joinToString("-")
日付
- 文字列から日付への変換
// import java.time.LocalDate
// import java.time.format.DateTimeFormatter
val date = LocalDate.parse("2019-12-04")
val formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd")
val formattedDate = date.format(formatter)
plintln(formattedDate)
分岐(if式)
- if文ではなくif式だから判定結果を代入できる
val num = 100
val str = "Hello"
val ret = if( num in 50..100) { // Range指定
println("A")
"A"
} else if (str === "Hello" || num != 100) { // 論理演算子
println("B")
"B"
} else {
println("C")
"C"
}
println("Result $ret" )
分岐(when式)
- switch式みたいなもの
- inで範囲指定もできる
val num = 10
val ret = when (num){ // When式
10 -> "A"
in 11..20 -> "B" // Range指定
else -> "C"
}
println("Result $ret" )
関数
- デフォルト引数を設定できる
private fun createMessage(num: Int = 50, str: String): String {
val ret = when (num) {
in 0..49 -> "A $str"
in 50..99 -> "B $str"
else -> "C $str"
}
return ret
}
単一式関数
- 単一式の場合はこのように簡略できる
private fun createMessage(num: Int = 50, str: String) =
when (num) {
in 0..49 -> "A $str"
in 50..99 -> "B $str"
else -> "C $str"
}
Unit関数
- いわゆるvoid関数だけどジェネリクスに対応するためにUnit型となる
private fun showMessage(msg: String) = plintln("$msg")
Nothing型の関数
- 例外をスローするため制御が戻らない
inline fun TODO(): Nothing = throw NotImplementedError()
無名関数
- 関数型を利用するほうが多いかもしれない
{
val num = 10
plintln("$num")
}()
関数型
- C#のラムダ式に近いからわかりやすい
- 引数が1つの場合は暗黙の引数itを利用できることが特徴
val piyo: (String) -> String = {
"$it" + "001"
}
val payo: (Int, String) -> String = { num, str ->
"$num" + str
}
- 関数型を返す関数もできる
private fun createFunc(): (String) -> String {
return { str: String ->
str + "001"
}
}
null 許容型
- NULLを許容する場合はnull許容型を明示的に指定する
var c: String = "Hello!"
c = null // NG!
var c: String? = "Nomad"
c = null // OK!
セーフコール演算子
- NULLの場合は評価しない
var str: String?
str!.length
val ret = str?.let { // ? セーフコール演算子
if (it.isNotBlank()) {
it
} else {
"OK"
}
}
null合体演算子
- NULLの場合は右辺を返す(C#では"??")
var str: String? = null
val ret: String = str ?: "OK" // ?: null合体演算子
apply
- 一連の関数を呼び出す。Visual BasicのWithみたいなものかな
val file = File("hoge.txt").apply {
setReadable(true)
setWritable(true)
setExecutable(false)
}
let
- 渡された変数をラムダのスコープとしてitで参照できるようにする
val num: Int = 10
val squared = num.let {
it * it // num → it
}
- null合体演算子と組み合わせると便利
val num: Int? = 10
var str: String = num?.let { "$it is not null" } ?: "null"
run
- 一連の関数を呼び出す。レシーバーではなく結果を返す
fun createMessage(str: String) = str + "message"
var str = "Hello"
str.run(::createMessage).run(::println) // 関数リファレンスを実行
with
- 指定したオブジェクトに対して複数の操作を行う
val ret = with(StringBuilder()) {
append("hoge")
toString()
}
also
- 渡された変数をitで参照できるようにして連鎖できる
var ret: List<String>
File("hoge.txt")
.also {
print(it.name)
}.also {
ret = it.readLines()
}
}
takeIf
- 断言(条件を定義するラムダ式)をもとに評価を行った結果が偽であればnullを返す
val ret = File("hoge.txt")
.takeIf { it.canRead() && it.canWrite() }
?.readText()
例外
- よくある例外と同じ
try {
var str: String? = null
str ?: throw IllegalStateException() // 例外を送出
} catch (e: Exception) { // 例外を捕捉
println(e)
}
事前条件
- 引数がnullの場合は、IllegalStateExceptionを送出する。
var str: String? = null
checkNotNull(str, { "str is null" }) // 事前条件
他にも、require、requireNotNull、error、assertがある
配列
- 配列の作成と要素の取得
val list = arrayOf(1, 2, 3, 4, 5)
for (item in list) {
println(item)
}
リスト
- リストの初期化と要素の取得
val list: List<String> = listOf("Orange", "Grape", "Apple")
list[0]
list.first() // 最初の要素の取得
list.last() // 最後の要素の取得
list.getOrElse(4) { "Unknown Fruit" } // 存在すれば要素の値、存在しなければ指定した値
list.getOrNull(4) ?: "Unknown Fruit" // 存在すれば要素の値、存在しなければnull
if (list.contains("Orange")) { // 存在確認
plintln("OK")
}
if (list.containsAll(listOf("Grape", "Apple"))) { // 複数の存在確認
plintln("OK")
}
- 要素の追加と削除(Mutable)
val list = mutableListOf("Orange", "Grape", "Apple")
list.remove("Orange") // 要素の削除
list.add("Banana") // 要素の追加
list.add(0, "Pear") // 挿入場所を指定した要素の追加
list.clear() // 要素をクリア
- コレクションの相互変換
val readonlyList = list.toList() // Listに変換
val distinctList = list.distinct() // 重複を排除(内部的にはtoSet()+toList())
- リストの分解(5つまで)
val list = mutableListOf("Orange", "Grape", "Apple")
val (orange, _, apple) = list // 1つ目と3つ目の要素を取得
セット
- セットは要素の重複を許可しない
val set = mutableSetOf("Orange", "Grape", "Apple")
set.elementAt(2) // 要素の取得
set.remove("Orange") // 要素の削除
set.add("Banana") // 要素の追加
マップ
- キーと値をペアで登録
val map = mapOf("Orange" to 100, "Grape" to 200, "Apple" to 150)
var orange = map["Orange"]
- 要素の取得
val map = mapOf(Pair("Orange", 100),
Pair("Grape", 200),
Pair("Apple", 150))
map["Orange"]
map.getValue("Grape") // キーを指定して要素の取得
map.getOrElse("Banana") {"NG"} // 指定したキーの要素が存在しなければ指定した値
map.getOrDefault("Banana", 180) // 指定したキーの要素が存在しなければ既定の値
- 要素の追加と削除(Mutable)
val map = mutableMapOf("Orange" to 100)
map.put("Grape", 200) // 要素の追加
map.getOrPut("Apple"){150} // 要素がなければ追加して取得
map.remove("Orange") // 要素の削除
繰り返し(for)
- 繰り返す回数を指定するのではない
for (num in 1..10) {
println(num)
}
繰り返し(forEach)
- C#のForEachメソッドと同様
val list = listOf("Orange", "Grape", "Apple")
list.forEach {
fruit -> println("$fruit")
}
- 要素のインデックスも取得できる
val list = listOf("Orange", "Grape", "Apple")
list.forEachIndexed {
index, fruit -> println("${index+1}:$fruit")
}
繰り返し(while)
- do~whileもあり
var isLoop = true
var isBrake = true
while (isLoop) {
if (isBrake) {
break
}
}
クラス
- プライマリコンストラクタの引数にvar/valを指定すると自動でプロパティ化される
open class Animal(_name: String,
var isAlive: Boolean) {
private var name = _name
get() = field // ゲッター
set(value) {
field = value.trim() // セッター
}
val capitalizedName // 算出プロパティ
get() = name.capitalize() // プライマリコンストラクタの引数を利用できる
open fun cry() = println("hoge") // オーバーライド可能
public fun move(distance: Int = 1) =
if(isAlive) {
println("The $name has moved $distance kilometers.")
} else {
println("The $name is dead.")
}
constructor(_name: String) : this(_name, true) {
// セカンダリコンストラクタ
}
init {
// 初期化ブロック(イニシャライザ)
// プライマリコンストラクタの引数を利用できる
}
}
継承
class Cat : Animal("cat", true) {
override fun cry() = println("mew")
}
インターフェース
- インターフェースの定義
interface Walkable {
var strideLength: Int
fun walk()
}
- インターフェースの実装
class Animal() : Walkable {
override var strideLength: Int
get() = field
set(value) {
field = value
}
override fun walk() = plintln("$strideLength walk")
}
データクラス
- equals、hashCode、toString、copyが自動生成される
data class Point(val xpos: Int, val ypos: Int) {
val isInBounds = xpos >= 0 && ypos >= 0
val isOutBounds = xpos < 0 || ypos < 0
}
コンパニオンオブジェクト
- スタティックメソッドのようなもの
class Hoge {
companion object { }
}
fun Hoge.Companion.fuga() {
println("fuga")
}
Hoge.fuga()
- companion キーワードを付与すれば、オブジェクトの名前を明示せずにメソッド名やプロパティに直接アクセスできる
class Hoge {
companion object {
fun fuga(str : String) {
println("$str")
}
}
}
Hoge.fuga("Kotlin")
- ファクトリメソッド(staticファクトリ)を実現できる
class User {
val name : String
// セカンダリコンストラクタ1
private constructor(name: String) {
this.name = name
}
// セカンダリコンストラクタ2
private constructor(firstName: String, lastName: String) {
this.name = "$firstName $lastName"
}
companion object {
// 名前を指定してUserインスタンスを生成を生成するファクトリメソッド
fun createUser(name: String) = User(name)
// 姓と名を指定してUserインスタンスを生成するファクトリメソッド
fun createFullNameUser(firstName: String, lastName: String) = User(firstName, lastName)
}
}
val user1 = User.createUser("Tokyo Taro")
val user2 = User.createFullNameUser("Hanako", "Tokyo")
列挙クラス
- 列挙もクラス
enum class OS {
WINDOWS,
MAC,
LINUX
}
オブジェクト宣言
- スタティッククラスのようなもの
- シングルトンの実装に利用できる
object Log {
fun info(text: String) = println(text)
fun error(text: String) = System.err.println(text)
}
Log.info("Hello")
オブジェクト式
- 無名クラスのようなもの
val monster = object : Animal() {
override fun cry() = "Graaaaaaaa"
}
monster.cry()
型チェック
- オブジェクトがある型であるかを評価する
var cat = Cat()
if(cat is Animal) {
plintln("Cat is animal.")
}
var cat = Cat()
var className = when(cat) {
is Cat -> "Cat"
is Animal -> "Animal"
else -> throw IllegalArgumentException()
}
型キャスト
- オブジェクトの型を強制的にキャストする
- Anyはすべてのクラスのスーパクラス
(any as Animal).name = "animal"
スマートキャスト
- 型チェックが成功すれば明示的な型キャストは不要
if(any is Animal) {
any.cry()
}
ジェネリック
- C#のジェネリックと同じ
class Box<T>(item: T) {
private var value: T = item
}
val box: Box<Int> = Box<Int>(100)
拡張関数
- C#の拡張メソッドと同じ
fun Any.upperPrintln() = println(this.toUpperCase)