Kotlinプログラミング・メモ
by K.I
2021/09/17
Index
- Kotlinのプログラミングメモ
- Javaの改良版みたいなので、 Javaとの比較もしている
- Androidのプログラミング言語としては、普通のJavaより記述しやすそうな感じがする
- でも思ったよりJavaとは文法が違うので、勉強しなおす必要はありそう。
- でも、ここ数年プログラムから遠ざかっていて、Javaもうろ覚えなので丁度良いかも
- 自分用メモなので、いろいろ勘違いや間違いがあると思う。
- Javaよりシンプルに記述できる
- 安全性が高い(Nullの扱い等)
- 行末の;を省略できる
- 型宣言を後ろに記述する
- Javaと同様にJava仮想マシンで動作するので、Projectで混在できる
- KotlinのコードをJavaに変換可能らしいが、文法は結構違う
- Googleが正式採用しているので将来性はあると思うが、今のところAndroid以外ではあまり使えない
[top]
型 | 意味 | 値の範囲 |
Boolean | 論理値 | true, false |
| | |
Char | 2byteUnicode文字 | \u0000〜\uFFFF |
| | |
Byte | 1byte整数 | -128〜127 |
Short | 2byte整数 | -32768〜32767 |
Int | 4byte整数 | -2147483648〜2147483647 |
Long | 8byte整数 | -9223372036854775808L〜9223372036854775807L |
| | |
Float | 4byte浮動小数点数 | ±(3.4028235E+38F〜1.401298E-45F) |
Double | 8byte浮動小数点数 | ±(4.94065645841246544E-324〜1.79769313486231570E+308) |
- なんとUnsignedが使える(Kotlin1.3以降)
UByte | 1byte整数 | 0u〜255u |
UShort | 2byte整数 | 0u〜65535u |
UInt | 4byte整数 | 0u〜4294967295u |
ULong | 8byte整数 | 0uL〜18446744073709551615uL |
- Kotlinの型は、原則的にはNullを許容しないNull非許容型(non-null型)だが、
- Nullを許容するNull許容型(nullable型)を使うことも可能で、型名の後に?が付く
- Any型は、基本的な任意の型(non-null)を示す
- Nullを許容する任意の型(nullable)は、Any?型になる
- 但し暗黙的な型変換はされないので、明示的に型変換する必要がある
var b: Byte = 123 //Kotlin
var a: Int = b //暗黙的な型変換はエラー
var a: Int = b.toInt() //明示的に型変換すればOK
byte b = 123; //Java
int a = b;
- Kotlinでは、原則的にnullを許容しないので、non-null型の変数となるが、
val ABC: Int = 123 //Kotlin
val ABC = 123 //型省略可能
final int ABC = 123 //Java
- { 引数 -> 式 } という感じに、中括弧で括って表す
fun add (x: Int, y: Int): Int = x + y
fun (x: Int, y: Int): Int = x + y
無名関数を変数に入れてみる
val addfunc = fun (x: Int, y: Int): Int = x + y
val addfunc: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
val addfunc: (Int, Int) -> Int = { x, y -> x + y }
- 或いは型宣言を省略して ⇒ 引数をラムダ式で渡す場合は、この形で指定する
val addfunc = { x: Int, y: Int -> x + y }
- Kotlinでは、無名関数を1行で書けるので、無理にラムダ式を使わなくてもよさそうだが、
- ラムダ式で条件式等を渡すメソッドが多いので、使い方を覚えておこう
- arrayOfで、配列を生成する
- 配列のインデックスは0から始まる
val nums = arrayOf(1,2,3)
val nams = arrayOf("abc","def","ghi")
nums.size // 3
nums.count() // 3
nums.count{it<3} // 2
nams.indexOf("def") // 1
val aaa = IntArray(10) // プリミティブは要素数だけ指定できる(0で初期化)
val bbb = IntArray(10,{0})
val ccc = Array(10,{0})
val nnn = Array(10,{i->i})
val sss = Array(10,{i->"$i"})
- 配列の大きさだけ確保したい場合、値がnullの配列を作ることもできる
val nnnn: Array<Int?> = arrayOfNulls(10)
val ssss: Array<String?> = arrayOfNulls(10)
arrayOf(1,2,3).all{it<4} // true
arrayOf(1,2,3).all{it<3} // false
arrayOf(1,2,3).any{it<2} // true
arrayOf(1,2,3).contains(3) // true
arrayOf(1,2,3).filter{it<3} // [1, 2]
arrayOf(1,2,3,2,3,5,6,5,7).distinct() // [1, 2, 3, 5, 6, 7]
- IntArray ⇒ Javaのint配列・プリミティブ型
- Array ⇒ JavaのInteger配列・ジェネリック型
val a: IntArray = intArrayOf(1, 2, 3)
val b: Array<Int> = arrayOf(1, 2, 3)
- IntArrayの方がプリミティブ型なので、処理は速い
a = b.toIntArray()
b = a.toTypedArray()
- 同じように使えるが単純に代入できないので、変換は必要
println(a.contentToString())
println(b.contentToString())
- listyOfで、Listを生成する
- Listのインデックスは0から始まる
val nums = listyOf(1,2,3)
val nams = listOf("abc","def","ghi")
var lll = nums.size
arrayOf(1,2,3).joinToString(",") // "1,2,3"
List⇒String
listOf(1,2,3).joinToString(",") // "1,2,3"
String⇒List
"1,2,3".split(",") // [1, 2, 3]
String⇒CharArray
"12345".toCharArray() // [1, 2, 3, 4, 5]
List⇒Array
listOf(1,2,3).toTypedArray()
"1.2.3".replace(",","*") // "1*2*3"
"1.2.3".replace(".".toRegex(),"*") // 正規表現による置換
"1.2.3".replace(Regex("."),"*") // "*****"
文字列の切り出し
"abcde".substring(2) // "cde"
"abcde".substring(2,4) // "cd"
Regex("abc").containsMatchIn("abcde") // true
Regex("abcde").matches("abcde") // true
前方一致・後方一致
println("abcde".startsWith("abc")) // true
println("abcde".endsWith("abc")) // false
"abcdeabcde".indexOf("cd") // 2
"abcdeabcde".lastIndexOf("cd") // 7
" abc def ghi ".trim() // "abc def ghi"
" abc def ghi ".replace(Regex("^ |$ "),"") // "abc def ghi"
" abc def ghi ".replace(Regex(" {2}")," ") // " abc def ghi "
val vShaderCode = "attribute vec2 vpos;\n" +
"void main() {\n" +
" gl_Position = vec4(vpos, 0.0, 1.0);\n" +
"}"
- トリプルクォート(""")で括ることで
- マージンが要らない時は、マージンの終わりを'|'で指定、
- Kotlinは、原則的にNullを許容しないので、比較的安全
- しかしながら、KotlinからJavaのメソッドを呼んだ場合、Kotlinは強制的にnon-nullな型となる
- 不用意にnullable変数を使うと、nullableな変数ばかりになってしまうので注意
- エルビス演算子によるアンラップ、nullの場合は別の値を返す
var ss = if (msg != null) msg else "No message"" //このような nullチェックは、
var ss = msg ?: "No messege" //エルビス演算子を使うと、こんな風に記述できる
- 纏めると、Kotlinは原則的にnullを許容しないので比較的安全だが、nullの扱いはやはり注意が必要
- Javaと較べて、nullを扱うためのオペレータはいろいろ用意されている
- セーフコール ⇒Nullでなければ処理を実行する
- オブジェクト?.メソッド
- オブジェクト≠null ⇒ メソッド呼び出し
- オブジェクト=null ⇒ nullを返す
- エルビス演算子 ⇒Nullかどうかで処理を切り分ける
- 式1 ?: 式2
- 式1≠null ⇒ 式1を評価
- 式1=null ⇒ 式2を評価
- 強制アンラップ ⇒nullableなオブジェクトを、not-nullなオブジェクトとして参照する
- KotlinはNullセーフなのは良いんだけど、
- lateinitを使うと、後で初期化することが出来る
lateinit var shape: Shape
- 初期化前に lateinit 変数を参照すると、UninitializedPropertyAccessException が発生する
- 確実に初期化される場合以外は、むやみに使わない方が良い
- 優先順位が分かっていないと、括弧だらけの式になったりするので、
Precedence | Title | Symbols |
Highest | Postfix | ++, --, ., ?., ? |
| Prefix | -, +, ++, --, !, label |
| Type RHS | :, as, as? |
| Multiplicative | *, /, % |
| Additive | +, - |
| Range | .. |
| Infix function | simpleIdentifier |
| Elvis | ?: |
| Named checks | in, !in, is, !is |
| Comparison | <, >, <=, >= |
| Equality | ==, !=, ===, !== |
| Conjunction | && |
| Disjunction | ¦¦ |
| Spread operator | * |
Lowest | Assignment | =, +=, -=, *=, /=, %= |
- 演算子を定義する
- Javaでは演算子の定義が出来なかったが、Kotlinでは自前のクラスに演算子定義できるのは嬉しい
演算子 | メソッド名 |
+ | plus |
- | minus |
* | times |
/ | div |
% | rem |
+= | plusAssign |
-= | minusAssign |
*= | timesAssign |
/= | divAssign |
%= | remAssign |
++ | inc |
-- | dec |
+(単項) | unaryPlus |
-(単項) | unaryMinus |
.. | rangeTo |
! | not |
in | contains |
[] | get / set |
() | invoke |
== | equals |
>、< | compareTo |
1まぁシェーダには改行は要らないんだけど。
[top]
if (x > y) {
println("x>y")
} else {
println("x<=y")
}
- Kotlinは、三項演算子は無いみたいだが
- むしろ、この方が分かりやすいかもしれない
- Javaでは、'=='はCと同様にインスタンスの比較になる2
- Kotlinでは、'==='がインスタンスの比較になる
while (nnn != null) {
nnn = nnn.next
}
- do-whileは判定が最後なので、少なくとも1回は必ず実行される
do {
nnn = nnn.next
} while (nnn != null)
- Javaのswitch文に近い感じで、条件毎に処理を切り分けて実行する
fun describe(obj: Any): String =
when (obj) {
1,2 -> "One or Two"
in 3..5 -> "from three to five"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}
- 上から順に評価される様だ
- こちらのコードそのまま(とても分かりやすかったので)
- 指定した範囲、配列、List等で繰り返し処理を行う
for (i in 1..10) println(i)
for (i in 10 downTo 1) print(i)
var sss = arrayOf("abc","def","ghi")
for (s in sss) println(s);
- 使い慣れたforの 初期化式;条件式;反復式 の形式で書けない。。
- forEach的な使い方しかできないのは、けっこう不便に感じる
- これは普通のforの記述方法を採用して欲しかったかな。。
var sss = arrayOf("abc","def","ghi")
sss.forEach {
value -> println(value)
}
sss.forEachIndexed {
index,value -> println("$index : $value")
}
- アロー演算子に見えて、ちょっと -> という記述に違和感がある。慣れだろうけど。。
2Cの場合は、ポインタの比較なのかもしれないが。。
[top]
- Kotlinでは、普通のclassはサブクラスを作成することが出来ない
- サブクラスを作成するには、クラス名の頭にopenを付加する
- メソッドをoverrideする場合も、メソッドの頭にopenを付加する
- クラスのインスタンス生成時にnewは必要ない
open class Parent { // 基底クラスはopenが必要
var myField = 123 // メンバ変数の定義
open fun myProc() { // overrideするメソッドにもopenが必要
println("Parent: $myField")
}
}
class Child: Parent() { // 継承時に、基底クラスに()が必要
var myField2 = 456 // メンバ変数の追加
override fun myProc() { // 親クラスのメソッドを上書き
println("Child: $myField")
}
fun myProc2() {
println("Child: $myField2") // メソッドの追加
}
}
fun main() {
var xxx = Child() // 継承時にnewは必要ない
xxx.myField = 100
xxx.myProc()
}
一応、動くけど、コンストラクタはちゃんと書いた方が良い気がする
- でも、JavaのOverrideは@にちょっと違和感あったので、この方が良いかも
- 基底クラスの継承時に、何故か基底クラス名に()が必要なようだ3
- 多分、基底クラスのプライマリコンストラクタが自動的に作成されているんだけど、
- 何処からも参照されていないから、それを呼び出す必要があるんじゃないかなと思う
- javaでは、クラス名と同じ名前のメソッドでクラスの初期化を行うが、
- Kotlinでは、クラス名の後の括弧内に、設定可能なメンバ変数を列挙することで、
- プライマリコンストラクタとなるようだ。たぶん。。
open class Parent (_myField: Int) { // ()内プライマリコンストラクタ
var myField: Int = _myField // メンバ変数
open fun myProc() {
println("Parent: $myField")
}
}
class Child(_myField: Int, _myField2: Int): Parent(_myField) { //プライマリコンストラクタで、基底クラスも初期化
var myField2: Int = _myField2 // メンバ変数追加
override fun myProc() { // 親クラスのメソッドを上書き
println("Child: $myField")
}
fun myProc2() {
println("Child: $myField2") // メソッドの追加
}
}
- この解釈も記述方法もちょっと自信なし
- プライマリコンストラクタ()内でvarで記述することで、メンバ変数を定義することもできるようだ
open class Parent(var myField:Int = 123) { //プライマリコンストラクタで、varを付けると定義もできる
init{}
open fun myProc() {
println("Parent: $myField")
}
}
class Child(_myField: Int = 123, var myField2: Int = 456): Parent(_myField) { // 基底クラスも初期化
override fun myProc() { // 親クラスのメソッドを上書き
println("Child: $myField")
}
fun myProc2() {
println("Child: $myField2") // メソッドの追加
}
}
fun main() {
var xxx = Child(100,200)
xxx.myProc()
}
ちょっと複雑な処理をする場合、initで記述すれば良いと思う
- でも、この記述方法は違和感があるなぁ。なにか間違えているのかもしれないけど。。
- プライマリコンストラクタ以外のコンストラクタは、セカンダリコンストラクタという
- 従来のJavaの書き方のように、プライマリコンストラクタを記述しないで、やってみる
open class Parent { // プライマリコンストラクタは記述しない
var myField: Int // メンバ変数
constructor() { // 引数が無い場合
myField = 123
}
constructor(_myField: Int) { // 引数が1つの場合
myField = _myField
}
open fun myProc() {
println("Parent: $myField")
}
}
class Child: Parent { // やはりプライマリコンストラクタは記述しない
var myField2: Int // メンバ変数追加
constructor(): super() { // 引数が無い場合
myField2 = 456
}
constructor(_myField: Int): super(_myField) { // 引数が1つの場合
myField2 = 456
}
constructor(_myField: Int, _myField2: Int): super(_myField) { // 引数が2つの場合
myField2 = _myField2
}
override fun myProc() {
println("Child: $myField") // 親クラスのメソッドを上書き
}
fun myProc2() {
println("Child: $myField2") // メソッドの追加
}
}
fun main() {
var xxx = Child(100,200)
xxx.myProc()
}
- この場合は基底クラス名に()が必要ないようだ。
- プライマリコンストラクタって、簡単なクラスを記述する場合は簡潔に書けて良いかもしれない
- でも、従来のやり方の様に記述するほうが個人的には全然やりやすい
- プライマリコンストラクタを記述しないのは、普通じゃないのかもしれないけど、
- 自分としては、この方がしっくりくるので、この形式で記述しようと思う
- Kotlinで、Cloneはどうやるのか分からなかったので、
- JavaのCloneのコードをAndroidStudioに貼り付けてみた。
public class xylist extends Point implements Cloneable {
public xylist next;
public static xylist zero = new xylist(0,0,null);
public xylist() {
this.x = 0;
this.y = 0;
this.next = null;
}
public xylist(int x, int y, xylist next) {
this.x = x;
this.y = y;
this.next = next;
}
public xylist clone() {
try {
xylist result = (xylist) super.clone();
if (this.next != null) result.next = this.next.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new InternalError(e.toString());
}
}
}
- それで、変換されたKotlinのコードがこんな感じになった
class xylist : Point, Cloneable {
var next: xylist?
constructor() {
x = 0
y = 0
next = null
}
constructor(x: Int, y: Int, next: xylist?) {
this.x = x
this.y = y
this.next = next
}
public override fun clone(): xylist {
return try {
val result = super.clone() as xylist
if (next != null) result.next = next!!.clone()
result
} catch (e: CloneNotSupportedException) {
throw InternalError(e.toString())
}
}
companion object {
var zero = xylist(0, 0, null)
}
}
- Kotlinは、Cloneableクラスを継承して、cloneメソッドをoverrideすれば良いみたい。
- Javaでは、implementsなので、ちょっと違うけど、大体同じ考え方で良いのかな
- 未だ完全には理解していないので、とりあえずメモ書きとして
- Javaでは、キャストする前に型を確認してから、改めてキャストしてアクセスする必要がある
- Kotlinでは、型を確認したら改めてキャストしなくても、自動的にキャストされる
- それから、staticなものは、companion objectになるようだ
companion object {
var zero = xylist(0, 0, null)
}
- Kotlinには、staticというものは無いらしい
- Kotlinでは、Javaのフィールドをプロパティと呼ぶらしい
class Person() {
var firstName: String = ""
var lastName: String = ""
var fullName: String
get() {
return firstName + " " + lastName
}
set(value) {
firstName = value.split(" ")[0]
lastName = value.split(" ")[1]
}
fun print() { println("fullName = $fullName") }
fun printFirst() { println("firstName = $firstName") }
fun printLast() { println("lastName = $lastName") }
}
fun main() {
val person = Person()
person.firstName = "Taro"
person.lastName = "Suzuki"
person.print()
person.fullName = "Jiro Yamada"
person.print()
person.printFirst()
person.printLast()
}
Javaではフィールドのgetter/setterを記述することを推奨されているが、
- Kotlinでは、getter/setterが暗黙的に設定されるような感じなのかな
プロパティの直下にget/setで記述することで、明示的に設定することも出来るようだ
- 自分は、Javaのgetter/setterが面倒でちゃんと書かないので、この仕様は有難い
- Javaでは、パッケージのトップレベルに関数を定義することは出来なかったが、
- Kotlinでは、クラスの外に関数やプロパティを定義することが出来る
- TopLevel関数やプロパティは、どのクラスからも参照可能だが、
- privateを付けると、同じファイルからのみ参照可能となる
- TopLevel関数は、クラスのprivateメンバにはアクセス出来ないので、
- クラス内部にアクセスする場合は、companion objectを使う。
- クラスを継承することなく、メソッドを追加したように見せかけることが出来る
- 拡張関数は、メソッドの中でさえ定義可能
- 例えばTop Levelで定義すれば、どこでも使えて便利なんだけど、
- 元々の関数を拡張する場合は、使う場所を制限したいので、interfaceで括ると良い
- Javaのstaticなメソッドは、拡張関数で定義しても良いんじゃないかと思う
- Kotlinで構造体を定義するには、Javaと同じでclassにしなきゃいけないんだけど、
- Kotlinでは、クラスの中の好きな場所で、クラスを定義することも問題なくできる
class sss(
var I: Int // プリミティブはそのまま
var P: Point, // クラスもただ記述するだけ
var C: Array<Color?>? = arrayOfNulls(3), // クラスのArrayは要素数を指定する
var W: DoubleArray = DoubleArray(3), // プリミティブも要素数の指定は面倒
var result: Boolean
)
- プリミティブやクラスは、そのまま記述するだけ、
- でも特にArrayなんかは、ちょっと面倒くさい記述方法になる
- でも、CだったらArrayは簡単に記述できるのに、もっと簡単に記述できないものか。。
- Kotlinには2つ、或いは3つの値を保持するPair、Tripleクラスがある
val pair = Pair(10,20)
val triple = Triple(10,20,30)
- 分解宣言という記述方法で、分解して定義することもできる
val (x,y) = pair
val (a,b,c) = triple
(x,y) = pair
(a,b,c) = triple
ちょっと冗長だが、こういう書き方は可能
x = pair.component1()
y = pair.component2()
fun xxx2(): Pair<Int,Int> {
return Pair(1,2)
}
fun xxx3(): Triple<Int,Int,Int> {
return Triple(1,2,3)
}
- でも、多値を返す関数はあまり良くないので
- 複数の値を纏めた型をdata classで定義した方が良い
data class Position(val x:Int, val y:Int, val z:Int)
fun xxx3(): Position {
return Position(1,2,3)
}
- Kotlinには、data classというのがあるらしい。
- 条件としては、
- プライマリコンストラクタに1つ以上の引数を持つこと
- 抽象クラス、openクラス、インナークラスではないこと
- データクラスは、データを扱うのに向いているクラス
- 通常のクラスでも使える、equals(),hashCode(),toString()の動作が異なる
- それ以外に、componentN(),copy()が使えるようになる
関数名 | データクラス | クラス |
equals() | プロパティの比較 | インスタンスの比較 |
hashCode() | プロパティ値からのHashコード | インスタンスからのHashコード |
toString() | クラス名、プロパティ名、値をちゃんと表示 | クラス名とインスタンスのHashを16進表示 |
| | |
componentN() | プロパティにconponent1のようにアクセス可能 | |
copy() | インスタンスのコピー生成 | |
- equals()やtoString()は、プライマリコンストラクタの引数のみ行われる
- またArrayはequals()では同一性のチェックになるので注意
- copy()はシャローコピーになる
- これらは必要な場合はoverrideする必要がある
class XXX(var r:Double, var g:Double, var b:Double, val ary: Array<Int>) // 通常のクラス
data class YYY(var r:Double, var g:Double, var b:Double,val ary: Array<Int>) // データクラス
fun main() {
var xxx = XXX(1.1,2.2,3.3,arrayOf(1,2,3))
var yyy = YYY(1.1,2.2,3.3,arrayOf(1,2,3))
var zzz = YYY(1.1,2.2,3.3,arrayOf(1,2,3))
var yyy1 = yyy.copy() // シャローコピー
var yyy2 = yyy.copy(ary=arrayOf(5,6,7)) // 一部変更してcopyすることもできる
println("yyy.hashCode()="+yyy.hashCode())
println("yyy.toString()="+yyy.toString())
println("yyy1.toString()="+yyy1.toString())
println("yyy2.toString()="+yyy2.toString())
println("(yyy==zzz)="+yyy.equals(zzz))
println("(yyy==yyy1)="+yyy.equals(yyy1))
println("(yyy==yyy2)="+yyy.equals(yyy2))
println("xxx.hashCode()="+xxx.hashCode())
println("xxx.toString()="+xxx.toString())
}
結果は
yyy.hashCode()=1531306177
yyy.toString()=YYY(r=1.1, g=2.2, b=3.3, ary=[1, 2, 3])
yyy1.toString()=YYY(r=1.1, g=2.2, b=3.3, ary=[1, 2, 3])
yyy2.toString()=YYY(r=1.1, g=2.2, b=3.3, ary=[5, 6, 7])
(yyy==zzz)=false // 内容は同じでも別Arrayなので違う
(yyy==yyy1)=true // シャローコピーなので、同じになる
(yyy==yyy2)=false // 変更というか入れ替えなので当然違う
xxx.hashCode()=746292446
xxx.toString()=XXX@2c7b84de
enum class COLOR {
RED, GREEN, BLUE
}
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
- Javaのenumはやたら面倒だったので、かなり簡単になった
3セカンダリコンストラクタだけ記述した場合は、括弧が無くても良いみたいだけど。
[top]
[top]
[プログラムの部屋に戻る]