Kotlin 入門
Kotlinとは
- Kotlinとは、Javaの統合開発環境であるIntelliJ IDEAで有名なJetBrainsが開発したオブジェクト指向プログラミング言語
- Groovy やScala から、機能や簡易記法(糖衣構文)を採用している
- ジェネリクスの構文などでC#の影響を受けている
- 2012年2月14日、Kotlin はApacheライセンス バージョン2.0に基づいてオープンソース化された
- 2019年、GoogleはKotlinをAndroidアプリケーション開発の標準にした
- 言語構文自体はJavaとは互換性がないが、コンパイルされたコードはJava VM上で動作するため、これまでのJava資産の多くを流用できる
- Java JVMだけがターゲットでなく、JavaScript / Native コードの生成も可能である
- Java VMで動作する他のプログラミング言語
- Groovy : https://groovy-lang.org/
- Scala : https://www.scala-lang.org/
- Clojure : https://clojure.org/
- JRuby : https://www.jruby.org/
- Jython : https://www.jython.org/
- Ceylon : https://ceylon-lang.org/
基本
エントリポイント
-
main
関数がエントリポイントとなるfun main() { println("Hello world!") } // プログラム引数を取る場合 fun main(args: Array<String>) { println(args.contentToString()) }
標準出力
-
print
/println
関数を利用するprint("Hello ") print("world!") println("Hello world!") println(42)
構文
関数の定義
-
2つの
Int
型の引数を持ち、Int
型を戻り値とする関数fun sum(a: Int, b: Int): Int { return a + b } // 式本体と推論された戻り値の型 fun sum(a: Int, b: Int) = a + b
-
値を返さない関数
fun printSum(a: Int, b: Int): Unit { print(a + b) } // Unitは省略可 fun printSum(a: Int, b: Int) { print(a + b) }
変数の定義
-
1度だけ代入できる(Immutable)ローカル変数
val a: Int = 1 val b = 1 // `Int`型が推論される val c: Int // 初期値が与えられない場合、型指定が必要 c = 1 // 明確な代入
-
変更可能 (Mutable) な変数
var x = 5 // `Int`型が推論される x += 1
null可能値を使用した、nullのチェック
-
null
値を取り得る場合、参照は明示的にnull
をチェックする必要あり// nullを返す可能性のある関数 fun parseInt(str: String): Int? { // ... } // null可能値を返す関数を使用 fun main(args: Array<String>) { if (args.size < 2) { print("Two integers expected") return } val x = parseInt(args[0]) val y = parseInt(args[1]) // `x`, `y`はnullが入っていることがあるので、`x * y`はエラーを引き起こす if (x != null && y != null) { // xとyは、nullチェックの後自動的に非null許容型へキャストされる print(x * y) } } // 別な例 // null可能値を返す関数を使用 fun main(args: Array<String>) { if (args.size < 2) { print("Two integers expected") return } val x = parseInt(args[0]) val y = parseInt(args[1]) if (x == null) { print("Wrong number format in '${args[0]}'") return } if (y == null) { print("Wrong number format in '${args[1]}'") return } // x and y are automatically cast to non-nullable after null check print(x * y) }
型チェックと自動キャストの使用(スマートキャスト)
-
is
演算子で型チェックを行う (Javaのinstanceof
に相当)-
is
で特定の型をチェックした後は、明示的なキャストが不要
// 例1 : if内はStringとして参照 fun getStringLength(obj: Any): Int? { if (obj is String) { // `obj` はこのブランチ内では自動的に`String`へキャストされる return obj.length } // `obj` は型チェックが行われたブランチ外では、まだ`Any`型である return null } // 例2 : ifの外側でStringとして参照 fun getStringLength(obj: Any): Int? { if (obj !is String) return null // `obj` はこのブランチ内では自動的に`String`へキャストされる return obj.length } // 例3 : 遅延評価演算子 && とif内ではStringとして参照 fun getStringLength(obj: Any): Int? { // `obj` は`&&`の右側では自動的に`String`へキャストされる if (obj is String && obj.length > 0) return obj.length return null }
-
範囲
-
in
演算子で数値が範囲内にあるか判定(!in
で範囲外を判定)// 範囲内の判定 if (x in 1..y-1) print("OK") // 範囲外の判定 if (x !in 0..array.lastIndex) print("Out") // forループでの使用 for (x in 1..5) print(x)
基本型
数値
型
整数型
Type | Size (bits) | Min value | Max value |
---|---|---|---|
Byte |
8 | -128 | 127 |
Short |
16 | -32768 | 32767 |
Int |
32 | -2,147,483,648 (-231) | 2,147,483,647 (231 - 1) |
Long |
64 | -9,223,372,036,854,775,808 (-263) | 9,223,372,036,854,775,807 (263 - 1) |
浮動小数型
Type | Size (bits) | Significant bits | Exponent bits | Decimal digits |
---|---|---|---|---|
Float |
32 | 24 | 8 | 6-7 |
Double |
64 | 53 | 11 | 15-16 |
リテラル定数
- 整数値のリテラル
- 数値:
123
-
Long
型の数を表すには大文字のL
でタグ付けする:123L
-
- 16進数:
0x0F
- 8進数: サポートしない
- 2進数:
0b00001011
- 数値:
- 浮動小数のリテラル
- デフォルトではdouble型:
123.5
,123.5e10
- float型を表すには
f
orF
でタグ付けする:123.5f
- デフォルトではdouble型:
ボクシング
-
JVMプリミティブ型として数値が物理的に格納されている
- ジェネリクス・null許容型(例:
Int?
)が関与すると、ボクシングされる
val a: Int = 10000 // プリミティブな数値 print(a === a) // 'true'を出力する val boxedA: Int? = a // ボクシングされた数値 val anotherBoxedA: Int? = a // ボクシングされた数値 print(boxedA === anotherBoxedA) // !!! 'false'を出力する !!! : === 参照等価評価 print(boxedA == anotherBoxedA) // 'true'を出力する : 構造等価評価 (.equalsに該当)
- ジェネリクス・null許容型(例:
明示的な変換
-
小さな型は大きな型のサブタイプではない
- 明示的な変換なしで、
Byte
型の値をInt
型へ代入することができないことを意味する - 異なる型の算術演算においては、算出される型は推論される
val b: Byte = 1 val i: Int = b // エラー val i: Int = b.toInt() // OK val l = 1L + 3 // Long + Int => Long
- 明示的な変換なしで、
-
全ての数値型は次の変換をサポートしている
-
toByte(): Byte
,toShort(): Short
,toInt(): Int
,toLong(): Long
,toFloat(): Float
,toDouble(): Double
,toChar(): Char
-
真偽値
-
Boolean
型は真偽値を表し、true
false
の2つの値がある
文字
-
文字は、
Char
型で表され、数字として直接扱うことはできないfun check(c: Char) { if (c == 1) { // ERROR: 非互換の型 // ... } }
-
サポートされるエスケープシーケンス
-
\t
,\b
,\n
,\r
,\'
,\"
,\\
,\$
- Unicodeエスケープシーケンス:
\uFF00
-
文字列
- 文字列は、
String
型で表される- 文字列は不変(イミュータブル)
- 文字列の要素は、インデックスの演算でアクセスできる:
s[i]
文字列リテラル
-
2つの種類の文字列リテラル
-
エスケープされた文字列を持ちうるエスケープ済み文字列
val s = "Hello, world!\n"
-
改行と任意の文字を含む生文字列
val text = """ for (c in "foo") print(c) """
-
先頭の空白を
trimMargin()
関数で削除する例- デフォルトでは
|
はマージンの接頭辞として使用されるが、trimMargin(">")
のように、パラメータとして別の文字を渡すとそれを接頭辞として使用することができる
val text = """ |Tell me and I forget. |Teach me and I remember. |Involve me and I learn. |(Benjamin Franklin) """.trimMargin()
- デフォルトでは
-
-
文字列テンプレート
-
文字列はテンプレート式、すなわち、評価され、その結果が文字列と結合されるコードの断片を含むことができる
val i = 10 val s = "i = $i" // "i = 10"と評価される val s = "abc" val str = "$s.length is ${s.length}" // "abc.length is 3"と評価される // 生文字列内で $ を出力する例 val price = """ ${'$'}9.99 """
パッケージ
パッケージ定義
-
ソースファイルの先頭でパッケージを記述する。
-
Javaとは異なり、ディレクトリとパッケージが一致する必要はない。
package my.demo fun baz() {} class Goo {}
-
baz()
の完全名はfoo.bar.baz
であり、Goo
の完全名は`foo.bar.Goo- パッケージが指定されない場合は、ファイルの内容は名前を持たない
default
パッケージに属することになる
- パッケージが指定されない場合は、ファイルの内容は名前を持たない
インポート
-
単一の名前を指定してインポート
import foo.Bar // Barは許可無しでアクセス可能になります
-
あるスコープ(パッケージ、クラス、オブジェクト等)内の全てのアクセス可能なコンテンツのインポート
import foo.* // 'foo'内の全てがアクセス可能になります
-
名前の衝突がある場合、
as
キーワードを使用して衝突するエンティティを局所的にリネームimport foo.Bar // Barはアクセス可能 import bar.Bar as bBar // bBarは'bar.Bar'を意味する
-
Javaとは違って、Kotlinは
import static
構文を持っていない
制御フロー
if式
-
Kotlinでは、
if
は式であり、値を返す(if
を文ではなく式として使用する(例えば値を返したり変数に代入したりする)ならば、その式にはelse
分岐が必要)- 従って、三項演算子は存在しない
// 伝統的な文としての使用 var max = a if (a < b) max = b // else付き var max: Int if (a > b) max = a else max = b // 三項演算子のように式として利用 val max = if (a > b) a else b
-
if
の分岐はブロックにすることができ、最後の式がそのブロックの値となるval max = if (a > b) { print("Choose a") a } else { print("Choose b") b }
when式
-
文として利用した場合:
else
は必須ではないwhen (x) { 1 -> print("x == 1") 2 -> print("x == 2") else -> { print("x is neither 1 nor 2") } } when (x) { 0, 1 -> print("x == 0 or x == 1") else -> print("それ以外") }
-
式として利用した場合:
else
は必須fun valueToString(value: Int) = when(value) { 1 -> "One" 2 -> "Two" else -> "Unknown value (value = $value)" }
-
in
または!in
を使用すると、コレクションの 範囲 (range) が可能when (x) { in 1..10 -> print("xは範囲内") in validNumbers -> print("xは有効") !in 10..20 -> print("xは範囲外") else -> print("どれにも該当せず") }
-
特定の型を検査するために
is
または!is
を利用- スマートキャストの機能により、その型のメソッドやプロパティに追加のチェック無しでアクセスできることに着目
val hasPrefix = when(x) { is String -> x.startsWith("prefix") // x は String へキャストされている else -> false }
forループ
-
for
ループはイテレータによって提供されるものを繰り返し実行// ブロックなし for (item in collection) print(item) // ブロックあり for (item: Int in ints) { // ... }
-
ライブラリ関数の
withIndex
を使用することで、要素のインデクスを取得する例for ((index, value) in array.withIndex()) { println("$indexの要素は$value") }
whileループ
while (x > 0) {
x--
}
do {
val y = retrieveData()
} while (y != null) // y はここで可視(visible)
returnとジャンプ
- Kotlinの3つの構造的ジャンプ演算子
-
return
: デフォルトでは最近のクロージャ(関数閉包)や匿名関数から抜け出す -
break
: 最も近い外側のループを終わらせる -
continue
: 最も近い外側のループを次のステップに進ませる
-
breakとcontinueのラベル
-
Kotlinにおける任意の式を
label
でマークすることができる- ラベルは、
@
記号に続く識別子の形式をもつ:abc@
、fooBar@
が有効なラベル
loop@ for (i in 1..100) { for (j in 1..100) { if (...) break@loop } }
- ラベルは、
ラベルに復帰
// 明示的なラベルを利用
fun foo() {
ints.forEach lit@ {
if (it == 0) return@lit
print(it)
}
}
// 暗黙のラベルを利用
fun foo() {
ints.forEach {
if (it == 0) return@forEach
print(it)
}
}
例外
例外クラス
-
Kotlinの例外は、
Throwable
のサブクラスとなる- Javaと異なりチェック例外はない
// 例外のスロー throw MyException("Hi There!") // 例外のキャッチ try { // some code } catch (e: SomeException) { // handler } finally { // optional finally block } // try を式として利用する val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
クラスとオブジェクト
クラスと継承
クラス
-
Kotlinでのクラスは、
class
キーワードを使用して宣言class Invoice { } // 本体がない場合は {} を省略できる class Empty
コンストラクタ
- Kotlin内のクラスは、 プライマリコンストラクタ と1つまたは複数の セカンダリコンストラクタ を持つことがでる
プライマリコンストラクタ
-
プライマリコンストラクタ: クラスのヘッダーの一部
class Person constructor(firstName: String) { } // プライマリコンストラクタがアノテーション・可視装飾子を持たない場合 constructor キーワードを省略できる class Person(firstName: String) { }
-
プライマリコンストラクタは、コードを含めることができない
- 初期化コードは
init
キーワードを付与したブロック内に記述
class Customer(name: String) { init { logger.info("Customer initialized with value ${name}") } }
- 初期化コードは
-
プライマリコンストラクタの引数を使用して、プロパティを初期化する例
class Customer(name: String) { val customerKey = name.toUpperCase() // プロパティの宣言と初期化 }
-
プロパティの宣言と初期化をプライマリコンストラクタから行う例
- プロパティはプライマリコンストラクタの中で可変値(ミュータブル) (
var
) または固定値(イミュータブル) (val
) で宣言可能
// コンストラクタの引数は、プロパティとして定義される class Person(val firstName: String, val lastName: String, var age: Int) { // ... }
- プロパティはプライマリコンストラクタの中で可変値(ミュータブル) (
-
プライマリコンストラクタがアノテーションや可視性修飾子を持つ場合は、
constructor
キーワードが必要class Customer public @Inject constructor(name: String) { ... }
セカンダリコンストラクタ
-
セカンダリコンストラクタの宣言例
class Person { constructor(parent: Person) { parent.children.add(this) } }
-
クラスがプライマリコンストラクタを持つなら、それぞれのセカンダリコンストラクタはプライマリコンストラクタへ委譲する必要あり
- 同クラスの他コンストラクタへの委譲は
this
キーワードを利用する
class Person(val name: String) { constructor(name: String, parent: Person) : this(name) { parent.children.add(this) } }
-
非抽象クラスがコンストラクタの宣言をしない場合、引数なしのプライマリコンストラクタが
public
スコープで生成される- 引数なしのコンストラクタが必要でない場合、
private constructor
で宣言する
- 引数なしのコンストラクタが必要でない場合、
class DontCreateMe private constructor () { }
- 同クラスの他コンストラクタへの委譲は
インスタンスの生成
-
Kotlinは
new
キーワードを持たない- コンストラクタの呼び出しは、関数の呼び出しと同様に行う
val invoice = Invoice() val customer = Customer("Joe Smith")
クラスメンバ
- クラス含めることができるメンバ
- コンストラクタと初期化ブロック
- 関数
- プロパティ
- ネストされたインナークラス
- オブジェクトの宣言
継承
-
Kotlinの全てのクラスは共通の
Any
スーパークラスを持つ-
Any
はjava.lang.Object
ではない- 特に注意すべきは、
equals()
、hashCode()
、toString()
以外のメンバを持たない- 詳細については Javaとの相互運用性 を参照
- 特に注意すべきは、
class Example // Anyから暗黙の継承
-
-
クラスヘッダ内のコロンの後に型を書くと、明示的にスーパータイプを宣言できる
- スーパータイプのクラスがプライマリコンストラクタを持つなら、継承したクラスのプライマリコンストラクタの値を利用して、初期化しなければならない
open class Base(p: Int) class Derived(p: Int) : Base(p)
- スーパータイプがプライマリコンストラクタを持たないならば、セカンダリコンストラクタで
super
キーワードを利用して初期化する
class MyView : View { constructor(ctx: Context) : super(ctx) { } constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) { } }
-
クラスの
open
アノテーションは、Javaのfinal
と反対- デフォルトでは、Kotlinのすべてのクラスは Effective Java のアイテム17( 継承またはそれの禁止のためのデザインとドキュメント )に合致する
final
となる
- デフォルトでは、Kotlinのすべてのクラスは Effective Java のアイテム17( 継承またはそれの禁止のためのデザインとドキュメント )に合致する
オーバーライド
-
Kotlinは明示的であり、Javaとは異なりオーバーライドできるメンバには
open
キーワードを付与する- サブクラスでは、
override
キーワードを利用して、スーパークラスの関数をオーバーライドする -
open
キーワードのないクラスでは、open
なメンバは禁止されている
open class Base { open fun v() {} // オーバーライド可能 fun nv() {} // オーバーライド不可 } class Derived() : Base() { override fun v() {} // Base .v() をオーバーライド、 open を引き継ぐ } open class AnotherDerived() : Base() { final override fun v() {} // Base .v() をオーバーライド、 これ以上のオーバーライドは不可 }
- サブクラスでは、
-
プロパティのオーバライドもメソッドのオーバライドと同様
open class Foo { open val x: Int get { ... } } class Bar1(override val x: Int) : Foo() { }
抽象クラス
-
クラスとそのメンバは
abstract
キーワードを使用して抽象クラス・抽象メンバとして宣言できる- 抽象クラスや抽象関数に
open
キーワードを付ける必要はない
open class Base { open fun f() {} } abstract class Derived : Base() { override abstract fun f() }
- 抽象クラスや抽象関数に
コンパニオンオブジェクト
- Kotlinでは、JavaやC#とは異なり、クラスは
static
メソッドを持たない- ほとんどの場合、代替として、パッケージレベルの関数を使用することが推奨されている
- もしクラスインスタンスを持たずに呼べるがクラス内部(例えばファクトリメソッド)へのアクセスが要る関数を書く必要があれば、そのクラスの中で オブジェクト宣言 のメンバとして書くことができる
プロパティとフィールド
プロパティの宣言
-
Kotlinのクラスは、プロパティを持てる
-
var
キーワード: ミュータブル(可変) -
val
キーワード: イミュータブル(読み取り専用)
public class Address { public var name: String = ... public var street: String = ... public var city: String = ... public var state: String? = ... public var zip: String = ... }
-
-
プロパティの参照
- Javaのフィールド参照と同様
fun copyAddress(address: Address): Address { val result = Address() // 'new' キーワードは Kotlin にありません result.name = address.name // アクセサが呼ばれる result.street = address.street // ... return result }
ゲッターとセッター
-
プロパティを宣言するための構文
- イニシャライザ、ゲッターとセッターは必須ではない
// ミュータブル (var) の場合 var allByDefault: Int? // エラー:明示的なイニシャライザが必要、デフォルトのゲッターとセッターは暗黙 var initialized = 1 // これは Int 型を持ち、ゲッターとセッターも持つ // イミュータブル (val) の場合 : setter が生成されない val simple: Int? // Int 型を持ち、デフォルトゲッターを持つ。コンストラクタ内で初期化が必要 val inferredType = 1 // Int 型を持ち、デフォルトゲッターを持つ
-
カスタムアクセサの定義
// カスタム getter val isEmpty: Boolean get() = this.size == 0 // カスタム setter var stringRepresentation: String get() = this.toString() set(value) { setDataFromString(value) // 文字列をパースして他のプロパティへ値を代入する }
-
アクセサの可視性を変更したり、アノテーションを付ける必要があるが、デフォルトの実装を変更する必要がない場合
- 本体を定義せずにアクセサを定義することがでる
var setterVisibility: String = "abc" private set // セッターはプライベートでデフォルトの実装を持つ var setterWithAnnotation: Any? = null @Inject set // セッターに Inject でアノテーションを付ける
バッキングフィールド (Backing Fields)
-
Kotlinのクラスは、フィールドを持てない
- カスタムアクセサを使用するときにバッキングフィールドが必要になることがある
- この目的のために、Kotlinは自動バッキングフィールドを提供する
-
field
識別子を使用してアクセスすることができる
-
- この目的のために、Kotlinは自動バッキングフィールドを提供する
var counter = 0 // イニシャライザの value はバッキングフィールドへ直に書き込まれる set(value) { if (value >= 0) field = value // バッキングフィールドへ値を設定する }
- カスタムアクセサを使用するときにバッキングフィールドが必要になることがある
コンパイル時定数
-
値がコンパイル時にわかるプロパティは、
const
キーワードを使用して、 コンパイル時定数 (compile time constants) としてマークすることができる- 次の要件を満たす場合に利用できる
- トップレベルまたは
object
のメンバ -
String
型の値またはプリミティブ型で初期化される - カスタムゲッターが無い
- トップレベルまたは
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated" @Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
- 次の要件を満たす場合に利用できる
遅延初期化プロパティ
-
通常、非null型として宣言されたプロパティは、コンストラクタ内で初期化が必要
-
lateinit
修飾子でプロパティをマークすることで、コンストラクタで初期化ができないケースをカバー- カスタムゲッターやカスタムセッターを持たない
var
プロパティでのみ使用でき、プロパティの型が非nullかつ、プリミティブ型であってはならない -
lateinit
プロパティが初期化される前にアクセスした場合、アクセスされたプロパティと、それが初期化されていないことを特定するための例外がスローされる
- カスタムゲッターやカスタムセッターを持たない
public class MyTest { lateinit var subject: TestSubject @SetUp fun setup() { subject = TestSubject() } @Test fun test() { subject.method() // 参照先を直に見に行く(dereference directly) } }
-
継承
インターフェース
-
interface
キーワードを使用して定義interface MyInterface { fun bar() fun foo() { // 本体は任意 } }
インタフェースの実装
-
クラスやオブジェクトは、複数のインターフェイスを実装すできる
class Child : MyInterface { override fun bar() { // 本体 } }
インターフェイス内のプロパティ
-
プロパティの宣言が可能
interface MyInterface { val property: Int // abstract val propertyWithImplementation: String get() = "foo" fun foo() { print(property) } } class Child : MyInterface { override val property: Int = 29 }
オーバーライドの競合解決
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
}
可視性修飾子
- 4つの可視性修飾子 (visibility modifiers)
private
protected
internal
-
public
: デフォルト
パッケージ
-
関数、プロパティやクラス、オブジェクトやインターフェースは、「トップレベル」、つまり、パッケージ内部で直接宣言することができる
- 可視性修飾子を何も指定しない: どこからでも参照可、デフォルト
public
-
private
: その宣言を含むファイルの中でのみ参照可 -
internal
: 同一モジュール内から参照可 -
protected
: トップレベルの宣言では使用不可
// ファイル名: example.kt package foo private fun foo() {} // example.kt の中で見える public var bar: Int = 5 // プロパティはどこでも見える private set // セッターは example.kt の中でのみ見える internal val baz = 6 // 同じモジュール内でのみ見える
- 可視性修飾子を何も指定しない: どこからでも参照可、デフォルト
クラスとインタフェース
-
クラス内のメンバーで指定した場合
-
private
: そのクラス内のみ参照可-
注意 Javaのユーザーへ:Kotlinでは、外部クラスはその内部クラスの
private
メンバを参照不可
-
注意 Javaのユーザーへ:Kotlinでは、外部クラスはその内部クラスの
-
protected
:private
と同じ + サブクラス内のみ参照可-
注意 Javaのユーザーへ:Javaでは同一パッケージ内からでも
protected
のメンバ参照できるが、Kotlinでは不可
-
注意 Javaのユーザーへ:Javaでは同一パッケージ内からでも
-
internal
: 同一モジュール内から参照可 -
public
– どこからでも参照可
open class Outer { private val a = 1 protected open val b = 2 internal val c = 3 val d = 4 // デフォルトで public protected class Nested { public val e: Int = 5 } } class Subclass : Outer() { // a は見えない // b, c, d は見える // Nested と e は見える override val b = 5 // 'b' は protected } class Unrelated(o: Outer) { // o.a, o.b は見えない // o.c and o.d は見える(同じモジュール) // Outer.Nested, Nested::e は見えない }
-
モジュール
- モジュールとは
- Kotlin のファイルが一体としてコンパイルされるまとまりのこと
- 例
- IntelliJ IDEAモジュール
- MavenやGradleのプロジェクト
- 例
- Kotlin のファイルが一体としてコンパイルされるまとまりのこと
- Javaでは、外部のコードが同じパッケージ名でクラスを定義すれば元のコードのパッケージプライベートな宣言を参照できるが、kotlinでは不可
- 実装のカプセル化を防ぐための機能
拡張 (extension)
- クラスを新しい機能で拡張する機能
- 拡張関数 と 拡張プロパティ をサポート
拡張関数
-
拡張関数を宣言するには レシーバタイプ (receiver type) を関数名の前に付ける
- 拡張関数内での
this
キーワードは、レシーバオブジェクト(ドットの前に渡されたもの)に対応する
// MutableListにswap 関数を追加する例 fun <T> MutableList<T>.swap(index1: Int, index2: Int) { val tmp = this[index1] // 'this' はリストに対応する this[index1] = this[index2] this[index2] = tmp }
- 拡張関数内での
null許容レシーバー
-
拡張はnull許容なレシーバの型でも定義できる
- 対象の値が
null
の場合でも、オブジェクト変数で呼び出すことができ、かつその本体内でthis == null
をチェックすることが可能
fun Any?.toString(): String { if (this == null) return "null" // nullチェックの後だと、 'this' は非null型に自動キャストされるので、 // 下記の toString() は Any クラスのメンバであると解決される return toString() }
- 対象の値が
データクラス
-
データを保持するためだけのクラス (DTO, VO)
- プライマリコンストラクタで宣言されたすべてのプロパティから、コンパイラは自動的に次のメンバを推論
-
equals()
/hashCode()
のペア -
"User(name=John, age=42)"
形式のtoString()
- 宣言した順番でプロパティに対応する
componentN()
関数 -
copy()
関数
-
- 制約
- プライマリコンストラクタは、少なくとも1つのパラメータを持ち、
val
またはvar
としてマークする - データクラスは、
abstract
,open
,sealed
inner
にできない
- プライマリコンストラクタは、少なくとも1つのパラメータを持ち、
data class User(val name: String, val age: Int) // デフォルト値を指定することで、パラメータなしのコンストラクタを定義できる data class User(val name: String = "", val age: Int = 0)
- プライマリコンストラクタで宣言されたすべてのプロパティから、コンパイラは自動的に次のメンバを推論
コピー
-
自動的に生成される
copy()
関数は、対象のオブジェクトのクローンを生成するval jack = User(name = "Jack", age = 1) val olderJack = jack.copy(age = 2)
データクラスと分解宣言 (Destructuring Declarations)
-
分解宣言で特定のプロパティ値を変数に展開ができる
val jane = User("Jane", 35) val (name, age) = jane // Destructuring println("$name, $age years of age") // "Jane, 35 years of age" を出力する
標準データクラス
- 標準ライブラリは、
Pair
とTriple
を提供(tupleに相当)
ジェネリクス
-
Javaと同様に、型パラメータを持てる
class Box<T>(t: T) { var value = t } // インスタンスを生成する val box: Box<Int> = Box<Int>(1) // 推論により型引数を省略できる val box = Box(1)
分散
- Javaのジェネリクスにあるワイルドカード(JavaのジェネリックのFAQを参照)がない
- Javaでのワイルドカード例:
Collection<? extends Object>
,List<? super String>
-
extends
: 型共変 - Producerで利用する -> Kotlinout
修飾子 -
super
: 反変性 - Consumerで利用する -> Kotlinin
修飾子
-
- Javaでのワイルドカード例:
- Tips : Wikipedia 共変性と反変性 (計算機科学)より
-
共変 (covariant): 広い型(例:
double
)から狭い型(例:float
)へ変換すること -
反変 (contravariant) : 狭い型(例:
float
)から広い型(例:double
)へ変換すること - 不変 (invariant): 型を変換できないこと
-
共変 (covariant): 広い型(例:
宣言箇所分散
分散アノテーション
-
out
/in
修飾子をしない型パラメータは不変となるopen class Present(private val price: Int) class Sweets(price: Int, private val flavor: String) : Present(price) class HandCream(price: Int) : Present(price) // T はへ不変 class Box<T : Present>(private val contents: T) { fun take(): T = contents fun <R> take(predicate: (T) -> R): R = predicate(contents) } fun main() { var candidate1: Box<Present> = Box(Present(3000)) var candidate2: Box<Sweets> = Box(Sweets(2000, "ビターチョコ")) candidate1 = candidate2 // コンパイルエラー : Type mismatch }
out
修飾子
-
「読み取りはするが、変更はしない」状態にすることで、広い型から狭い型への変換が可能
// T は out 修飾子により共変 class Box<out T : Present>(private val contents: T) { fun take(): T = contents fun <R> take(predicate: (T) -> R): R = predicate(contents) } fun main() { var candidate1: Box<Present> = Box(Present(3000)) var candidate2: Box<Sweets> = Box(Sweets(2000, "ビターチョコ")) candidate1 = candidate2 // OK }
-
例) Kotlinの
List
は immutable であるため、out
修飾子が利用されているpublic interface List<out E> : Collection<E>
in
修飾子
-
「変更はするが、読み取りはしない」状態にすることで、狭い型から広い型への変換が可能
// T は in 修飾子により反変 class Box<in T : Present>(private var contents: T) { //他のTを引数に取り、プロパティのTを書き換える「だけの」メソッド (内部のT(contentsプロパティ)を参照してはいけない) fun changeContents(other: T) { contents = other } // 値を参照するような処理はできない //fun <R> take(predicate: (T) -> R): R = predicate(contents) } fun main() { val candidate1 = Box(Present(3000)) var candidate2 = Box(Sweets(2000, "ビターチョコ")) // 広い型を狭い型へ代入する = 狭い型を広い型に変換することが可能 candidate2 = candidate1 candidate2.changeContents(Sweets(5000, "抹茶チョコ")) }
タイププロジェクション(型投影)
-
例として、配列を表す
Array
は読み書きができるため 不変 となる// ある配列から別の配列へ、アイテムをコピーする関数 fun copy(from: Array<Any>, to: Array<Any>) { assert(from.size == to.size) for (i in from.indices) to[i] = from[i] } val ints: Array<Int> = arrayOf(1, 2, 3) val any = Array<Any>(3) {} copy(ints, any) // エラー: (Array<Any>, Array<Any>) が期待されている
-
out
修飾子を利用して、copy
関数がfrom
に変更を禁止することを保証する- Javaの
Array<? extends Object>
に対応
fun copy(from: Array<out Any>, to: Array<Any>) { ... } val ints: Array<Int> = arrayOf(1, 2, 3) val any = Array<Any>(3) {} copy(ints, any) // OK
- Javaの
-
in
修飾子を利用して、与えられたパラメータの読み込みを禁止することを保証する- Javaの
Array<? super String>
に対応
// dest の型パラメータに in を指定してるため dest は 引数に型パラメータを使うメソッドしか使えない fun fill(dest: Array<in String>, value: String) { // ... }
- Javaの
スタープロジェクション (star-projections)
-
Java において
Some<?>
の様に、型パラメータを指定しない場合、KotlinではSome<*>
と記述し、スタープロジェクションと呼ばれる。-
Some<out Any?>
と同じ意味を持つ
val ints: List<Int> = listOf(1, 2, 3) val otherInts: List<*> = ints // スタープロジェクション // otherIntList は何の型を内部に格納しているか不明なので、 Any? としてしか取り出せない val a: Any? = otherInts[0] // OK val n: Number = otherInts[0] // エラー val i: Int = otherInts[0]) // エラー
-
ネストクラス
クラスのネスト
-
クラス内にクラスを定義できる
class Outer { private val bar: Int = 1 class Nested { fun foo() = 2 } } val demo = Outer.Nested().foo() // == 2
内部クラス
-
inner
を付与することで、外部クラスのメンバーを参照できる- 内部クラスで
this
を利用する場合の曖昧さに注意 : 修飾された this 式
class Outer { private val bar: Int = 1 inner class Inner { fun foo() = bar } } val demo = Outer().Inner().foo() // == 1
- 内部クラスで
無名内部クラス
-
オブジェクト式を利用して、無名内部クラスのインスタンスを生成する
window.addMouseListener(object: MouseAdapter() { override fun mouseClicked(e: MouseEvent) { // ... } override fun mouseEntered(e: MouseEvent) { // ... } })
-
生成するオブジェクトがFunction Interface(インターフェースに抽象メソッドが1つだけ宣言されている)場合は、ラムダ式を使用してオブジェクトを作成できる
val listener = ActionListener { println("clicked") }
列挙型クラス (Enum Classes)
-
基本的な列挙型の例
enum class Direction { NORTH, SOUTH, WEST, EAST }
メンバの初期化
-
メンバを持つ列挙型の例
enum class Color(val rgb: Int) { RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF) }
無名クラス
-
独自の無名クラスを宣言する例
enum class ProtocolState { WAITING { override fun signal() = TALKING }, TALKING { override fun signal() = WAITING }; abstract fun signal(): ProtocolState }
列挙型に用意されたメソッド・プロパティ
-
Javaの
enum
と同様のメソッド・プロパティが提供される// emum class EnumClass を定義した場合の例 // 指定された名前が、クラスで定義されている列挙型定数のいずれとも一致しない場合、valueOf() メソッドは IllegalArgumentException をスロー EnumClass.valueOf(value: String): EnumClass EnumClass.values(): Array<EnumClass>
-
Javaと同様に、すべての列挙型定数は、列挙型クラス宣言でその名前と順序を取得するためのプロパティが提供される
- 定義された順に基づいたComparable インタフェースも提供される
val name: String val ordinal: Int
オブジェクト
オブジェクト宣言
-
Kotlinにおけるオブジェクトはシングルトンである
- 初回アクセス時に 遅延して 初期化される
// オブジェクトの宣言 object DataProviderManager { fun registerDataProvider(provider: DataProvider) { // ... } val allDataProviders: Collection<DataProvider> get() = // ... } // オブジェクトの参照 DataProviderManager.registerDataProvider(...) // スーパータイプを持つ宣言の例 object DefaultListener : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { // ... } override fun mouseEntered(e: MouseEvent) { // ... } }
コンパニオンオブジェクト (Companion Objects)
-
クラス内におけるオブジェクトの宣言は
companion
キーワードを使用する- Javaにおける
static
に相当する利用方法となる - 対応するクラスが読み込まれたときに初期化される
class MyClass { companion object Factory { fun create(): MyClass = MyClass() } } // コンパニオンオブジェクトのメンバーを呼び出す例 val instance = MyClass.create()
- Javaにおける
オブジェクト式
-
あるクラスをわずかに修正しただけのオブジェクトを、それのための新しいサブクラスを明示的に宣言せずに、作成する必要がある時、Javaでは 無名内部クラス で対応する
- Kotlinでは
object
キーワードを利用した無名クラスのオブジェクトの生成で対応する
// MouseAdapterのオブジェクトを生成する例 window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { // ... } override fun mouseEntered(e: MouseEvent) { // ... } }) // 複数のスーパータイプを持つ例 open class A(x: Int) { public open val y: Int = x } interface B {...} val ab: A = object : A(1), B { override val y = 15 } // 一時的なオブジェクトのみが必要な場合の例 val adHoc = object { var x: Int = 0 var y: Int = 0 } print(adHoc.x + adHoc.y)
- Kotlinでは
委譲 (Delegation)
クラスの委譲
-
実装継承はカプセル化を破るためDelegationパターンが代替として推奨される(もちろんケースバイケースではある)
interface Base { fun print() } class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } // スーパータイプのリスト中の by 節は、 b が Derived のオブジェクトに内部的に格納されることを示し、コンパイラは b に取り次ぐ Base のすべてのメソッドを生成 class Derived(b: Base) : Base by b fun main(args: Array<String>) { val b = BaseImpl(10) Derived(b).print() // 出力:10 }
委譲プロパティ (Delegated Properties)
-
委譲を行うことで、次の一般的なユースケースに対応できる
- 遅延プロパティ (lazy properties) :アクセス時に算出する
- オブザーバブルプロパティ (observable properties) :プロパティが変更された時に通知する
class Example { var p: String by Delegate() } class Delegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "$thisRef, thank you for delegating '${property.name}' to me!" } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { println("$value has been assigned to '${property.name} in $thisRef.'") } } val e = Example() println(e.p) // -> Example@33a17727, thank you for delegating ‘p’ to me!
関数とラムダ
関数
関数の宣言と呼び出し
-
fun
キーワードで宣言する// 宣言 fun double(x: Int): Int { } // 呼び出し val result = double(2)
中置記法
-
infix
キーワードを付与することで中置記法で関数を実行できる- 中置記法が使えるケース
- メンバ関数や拡張関数
- 単一のパラメータ
// Intにエクステンションを定義 infix fun Int.shl(x: Int): Int { ... } // 中置記法で実行 1 shl 2 // これは次と同じ 1.shl(2)
- 中置記法が使えるケース
引数のデフォルト値
-
関数パラメータは、対応する引数が省略されている際に使用されるデフォルト値を持てる
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size()) { ... }
名前付きパラメータ
-
関数の呼び出し時に、関数のパラメータに名前を付けて呼ぶことができる
- Javaバイトコードは関数パラメータの名を保存しないため、Java関数を呼び出すときに名前付きパラメータ構文は使用できない
fun reformat(str: String, normalizeCase: Boolean = true, upperCaseFirstLetter: Boolean = true, divideByCamelHumps: Boolean = false, wordSeparator: Char = ' ') { ... } // デフォルト引数を利用して関数を呼び出す reformat(str) // 全ての引数を指定して関数を呼び出す reformat(str, true, true, false, '_') // 名前付き引数で関数を呼び出す reformat(str, normalizeCase = true, upperCaseFirstLetter = true, divideByCamelHumps = false, wordSeparator = '_' ) // 名前付き引数を利用して一部の引数を指定して呼び出す reformat(str, wordSeparator = '_')
Unit
-
関数が任意の有用な値を返さない場合、その戻り値の型は
Unit
となる(Javaのvoid/Void
に相当)fun printHello(name: String?): Unit { if (name != null) println("Hello ${name}") else println("Hi there!") // `return Unit` または `return` は必須ではない } // Unitは省略できる(上記の関数宣言と同一となる) fun printHello(name: String?) { ... }
単一式関数
-
関数の内部が単一の式である場合(つまり1行だけの場合)、中括弧を省略できる
fun double(x: Int): Int = x * 2 // コンパイラが型推論できる場合は、戻り値の型宣言を省略できる fun double(x: Int) = x * 2
可変長パラメータ
-
関数のパラメータ(最後のひとつ)は、
vararg
修飾子で可変長パラメータにできる// ts 変数は Array<out T> 型 fun <T> asList(vararg ts: T): List<T> { val result = ArrayList<T>() for (t in ts) // ts は配列 result.add(t) return result } // 可変長パラメータを指定して関数を呼び出す val list = asList(1, 2, 3)
スコープ
ローカル関数
-
ある関数内に別の関数を定義できる
- ローカル関数は、外側の関数(すなわちクロージャ)のローカル変数にアクセス可能
fun dfs(graph: Graph) { val visited = HashSet<Vertex>() fun dfs(current: Vertex) { if (!visited.add(current)) return for (v in current.neighbors) dfs(v) } dfs(graph.vertices[0]) }
メンバ関数
- クラスやオブジェクトの内部で定義される関数(いわゆるメソッド)
ジェネリック関数
-
関数名の前に山括弧(<>のこと)を使用して、ジェネリックパラメータを持てる
fun <T> singletonList(item: T): List<T> { // ... }
末尾再帰関数
-
末尾再帰と呼ばれる関数型プログラミングのスタイルをサポート、通常の再帰と異なりスタックオバーフローのリスクがない
-
関数が
tailrec
修飾子でマークされており、必要な形式を満たしている場合、コンパイラは高速かつ効率的なループベースのバージョンコードを生成する- 必要な形式 : 関数は実行する最後の操作として自身を呼び出すこと
// 末尾再帰の関数 tailrec fun findFixPoint(x: Double = 1.0): Double = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x)) // 生成されるループベースのコード private fun findFixPoint(): Double { var x = 1.0 while (true) { val y = Math.cos(x) if (x == y) return y x = y } }
高階関数とラムダ
高階関数
- 高階関数はパラメータとして関数を取るか、関数を返す関数のことを指す
ラムダ
-
概要
- ラムダ式は、中括弧で囲まれる
- パラメータ(もしあれば)は、
->
の前で宣言される(パラメータの型を省略可能) - 本体が
->
に続く(存在する場合)
// body は関数型: () -> Tを持つ(パラメータを取らず、型 T の値を返す関数) fun <T> lock(lock: Lock, body: () -> T): T { lock.lock() try { return body() } finally { lock.unlock() } } // lock関数を呼び出す例 fun toBeSynchronized() = sharedResource.operation() val result = lock(lock, ::toBeSynchronized) // ::は関数の参照を示す // ラムダ式を利用してlock関数を呼び出す例 val result = lock(lock, { sharedResource.operation() }) // Kotlinでは、関数の最後のパラメータが関数である場合、そのパラメータは括弧の外に指定することができる val result = lock (lock) { sharedResource.operation() }
val sum = { x: Int, y: Int -> x + y } // sum変数に明示的に型を記載した場合 val sum: (Int, Int) -> Int = { x, y -> x + y }
it
: 単一パラメータの暗黙の名前
-
関数リテラルがパラメータを1つだけ持つ場合、その宣言を(
->
と一緒に)省略でき、暗黙のパラメータ名はit
となる// 高階関数 map fun <T, R> List<T>.map(transform: (T) -> R): List<R> { val result = arrayListOf<R>() for (item in this) result.add(transform(item)) return result } // ラムダ式を利用して呼び出す ints.map({ it -> it * 2 }) ints.map { it -> it * 2 } ints.map { it * 2 }
無名関数
-
ラムダ式の構文から一つ欠落しているのは、関数の戻り値の型を指定する機能
- ほとんどの場合は、戻り型を自動的に推論することができるので不要だが、明示的な型指定が必要な場合は無名関数を利用する
fun(x: Int, y: Int): Int = x + y // ブロックを使用した場合 fun(x: Int, y: Int): Int { return x + y } // 無名関数を別の関数のパラメータとして渡す ints.filter(fun(item) = item > 0)
ラムダ式と無名関数の違い
-
ラベルなし
return
の動作が異なるラムダ式 無名関数 return
は囲んでいる関数から返される無名関数の内部 return
は無名関数自体から返される
クロージャ
-
外側のスコープで宣言された変数にアクセスすることができ、Javaとは異なりクロージャに取り込まれた変数を変更することができる(Javaでは
final
宣言をしなければならない)var sum = 0 ints.filter { it > 0 }.forEach { sum += it } print(sum)
分解宣言 (Destructuring Declarations)
-
オブジェクトの各プロパティが保持している値を、別々の変数に一度に代入することができる
data class Person( var name: String, var age: Int ) val p = Person(name = "yuki.terai", age = 17) val (name, age) = p // name=>"yuki.terai" // 宣言の順番通りに変数に入れていくだけなので、名前の一致はk関係ないため注意 data class Person( var age: Int, var name: String ) val p = Person(name = "yuki.terai", age = 17) val (name, age) = p // name=>17
コレクション
-
Kotlinは可変コレクションと不変コレクション(
List
、Set
、Map
など)を区別する- Kotlinの
List<out T>
はsize
やget
などの参照のみ定義される - リストを変更するメソッドは、
MutableList<T>
インターフェースによって追加されている
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)
- Kotlinの
等価
-
Kotlinには2つの等価性がある
参照の等価性 (Javaの ==
)構造的な等価性 (Javaの equals
)===
演算子(および!==
)==
演算子(および!=
)
null
安全
Nullable と Non-Null
-
Kotlinでは、
null
を保持できる参照と、保持できない参照を区別する// Non-Null var a: String = "abc" a = null // compilation error a.length // ok // Nullable var b: String? = "abc" b = null // ok b.length // compilation error: variable 'b' can be null
Nullableに対する参照方法
null
をチェック
-
Nullable な型に対し、
null
のチェックを行うことで、メンバへのアクセスが可能になるvar b: String? = "abc" val l = if (b != null) b.length else -1 // ok if (b != null && b.length > 0) print("String of length ${b.length}") // このブロックは、null でないことが明白であることがコンパイラは知っている else print("Empty string")
?.
演算子
-
Nullable のオブジェクトのメンバにアクセスする際に
?.
を利用することで、安全に呼び出しが行えるvar b: String? = "abc" val l: Int? = b?.length // b が null の場合は l に null が入る // チェーンすることも可能 bob?.department?.head?.name // いずれかのプロパティが null であれば null が返る
エルビス演算子
-
null
でない場合はその値を返し、そうでない場合はnull
以外の値を使用する// if 利用した場合 val l: Int = if (b != null) b.length else -1 // エルビス演算子を利用した場合 val l = b?.length ?: -1
!!.
演算子
-
null
の時にNullPointerException
を発生させる(NPEの愛好家向け、利用はよく考えること)var b: String? = "abc" b = null val l = b!!.length() // NullPointerException が発生する
アノテーション
宣言
-
class
の前にannotation
を付与する- アノテーションの追加属性は、アノテーションクラスにメタアノテーションを付けることで指定できる
-
@Target
: アノテーションを付けることができる要素(クラス、関数、プロパティ、式など)を指定する -
@Retention
: アノテーションがコンパイルされたクラスファイルに保存されるかどうか、および実行時にリフレクションを通じて表示されるかどうかを指定する(デフォルトでは両方ともtrue
) -
@Repeatable
: 1つの要素に同じ注釈を複数回使用できる -
@MustBeDocumented
: アノテーションがパブリックAPIの一部であり、生成されたAPIドキュメントに示されているクラスまたはメソッドのシグネチャに含まれている必要があることを指定する
-
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION) @Retention(AnnotationRetention.SOURCE) @MustBeDocumented public annotation class Fancy // アノテーションの使用例 @Fancy class Foo { @Fancy fun baz(@Fancy foo: Int): Int { return (@Fancy 1) } }
- アノテーションの追加属性は、アノテーションクラスにメタアノテーションを付けることで指定できる
コンストラクタ
-
アノテーションには、パラメーターを受け取るコンストラクターを指定できる
- 許可されるパラメータタイプは次のとおりです。
- Javaプリミティブ型(Int、Longなど)に対応する型
- 文字列
- クラス(
Foo::class
); -
enum
型 - その他のアノテーション
- 上記のタイプの配列
annotation class Special(val why: String) @Special("example") class Foo {}
- 許可されるパラメータタイプは次のとおりです。
アノテーションを適用するJavaの要素
-
プロパティまたはプライマリコンストラクタパラメータに注釈を付ける場合、Kotlinから生成されたJavaバイトコード内の注釈の可能な場所は複数になる。そのため、アノテーションを適用する箇所を指定することができる
file
-
property
(このターゲットの注釈はJavaには表示されません) field
-
get
(プロパティゲッター) -
set
(プロパティセッター) -
receiver
(拡張関数またはプロパティのレシーバーパラメーター) -
param
(コンストラクターパラメーター) -
setparam
(プロパティセッターパラメーター) -
delegate
(委任されたプロパティの委任インスタンスを格納するフィールド)
class Example(@field:Ann val foo, // annotate Java field @get:Ann val bar, // annotate Java getter @param:Ann val quux) // annotate Java constructor parameter
リフレクション
-
::
構文を利用するclass MyClass // Class References val kClass: KClass<MyClass> = MyClass::class val javaClass: Class<MyClass> = MyClass::class.java // Function References fun isOdd(x: Int) = x % 2 != 0 val refFun1: (Int) -> Boolean = ::isOdd val refFun2: Function1<Int, Boolean> = ::isOdd // Property References val str = "a" val refProperty: KProperty<Int> = str::length val refJavaGetter: Method? = str::length.javaGetter val refJavaField: Field? = str::length.javaField // Constructor References val refConstructor: Function<MyClass> = ::MyClass
オペレータオーバーローディング
- KotlinにはJavaとは異なり、演算子の動作を変更することが可能