493 lines
14 KiB
Kotlin
493 lines
14 KiB
Kotlin
@file:Suppress(
|
||
"USELESS_CAST", "unused", "DEPRECATED_IDENTITY_EQUALS", "UNUSED_VARIABLE",
|
||
"UNUSED_VALUE", "ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE", "VARIABLE_WITH_REDUNDANT_INITIALIZER",
|
||
"ReplaceCallWithComparison"
|
||
)
|
||
|
||
package jp.juggler.subwaytooter
|
||
|
||
import android.view.View
|
||
import org.junit.Test
|
||
|
||
import org.junit.Assert.*
|
||
import kotlin.concurrent.thread
|
||
|
||
//import kotlin.test.*
|
||
|
||
typealias TestLambdaCallback = (x : Int) -> Int
|
||
|
||
class TestKotlinFeature {
|
||
|
||
private val CODE_A = 1
|
||
private val CODE_A2 = 2
|
||
|
||
@Test
|
||
fun testWhenExpression() {
|
||
|
||
// ifを式として扱えるように、whenも式として扱える
|
||
run {
|
||
val code = 2
|
||
val s = "prefix:" + when(code) {
|
||
CODE_A, CODE_A2 -> "a"
|
||
else -> "b"
|
||
} + ":suffix"
|
||
|
||
assertEquals("prefix:a:suffix", s)
|
||
}
|
||
|
||
// tryも式として扱える
|
||
run {
|
||
val p = try {
|
||
if(stringNull.isNullOrEmpty()) throw RuntimeException("foo")
|
||
true
|
||
} catch(ex : RuntimeException) {
|
||
false
|
||
}
|
||
assertEquals(false, p)
|
||
}
|
||
}
|
||
|
||
private val stringNull : String? = null
|
||
private val stringEmpty : String? = ""
|
||
private val stringBlank : String? = " "
|
||
private val stringABC : String? = "ABC"
|
||
|
||
@Test
|
||
fun testNullString() {
|
||
|
||
// Nullable型には値がnullでも呼び出せるメソッドがある
|
||
assertEquals(true, stringNull.isNullOrEmpty())
|
||
assertEquals(true, stringEmpty.isNullOrEmpty())
|
||
assertEquals(false, stringBlank.isNullOrEmpty())
|
||
assertEquals(false, stringABC.isNullOrEmpty())
|
||
|
||
assertEquals(true, stringNull.isNullOrBlank())
|
||
assertEquals(true, stringEmpty.isNullOrBlank())
|
||
assertEquals(true, stringBlank.isNullOrBlank())
|
||
assertEquals(false, stringABC.isNullOrBlank())
|
||
|
||
// ?. 演算子の後に プリミティブ型を返すメソッドを呼び出して、そのままifの条件式に指定する
|
||
// 「stringNull?.isNotEmpty()」の型はBoolean? なので trueと比較できるが、 ==true を省略することはできない
|
||
// true,false,null の3値論理になるので、条件を反転させたい場合は == false と != true で結果が異なる
|
||
assertEquals(0, if(stringNull?.isNotEmpty() == true) 1 else 0)
|
||
assertEquals(0, if(stringEmpty?.isNotEmpty() == true) 1 else 0)
|
||
assertEquals(1, if(stringBlank?.isNotEmpty() == true) 1 else 0)
|
||
assertEquals(1, if(stringABC?.isNotEmpty() == true) 1 else 0)
|
||
|
||
// isNotBlank で空白のみを含む文字列をfalseにできる
|
||
assertEquals(0, if(stringNull?.isNotBlank() == true) 1 else 0)
|
||
assertEquals(0, if(stringEmpty?.isNotBlank() == true) 1 else 0)
|
||
assertEquals(0, if(stringBlank?.isNotBlank() == true) 1 else 0)
|
||
assertEquals(1, if(stringABC?.isNotBlank() == true) 1 else 0)
|
||
|
||
if(! stringABC.isNullOrEmpty()) {
|
||
// このブロック内部で string がまだNullableとして扱われるのが残念極まりない
|
||
}
|
||
}
|
||
|
||
// コンパイラに予測できない方法でIntの10を生成する
|
||
private fun generate10A() : Int {
|
||
return ("1" + "0").toInt()
|
||
|
||
}
|
||
|
||
// コンパイラに予測できない方法でIntの10を生成する
|
||
private fun generate10B() : Int {
|
||
var i = 0
|
||
while(true) {
|
||
++ i
|
||
if(i % 5 == 0 && i % 2 == 0) return i
|
||
}
|
||
}
|
||
|
||
@Test
|
||
fun testOperator() {
|
||
// is 演算子と !is 演算子
|
||
open class A(val name : String = "nanasi")
|
||
|
||
class B(name : String = "nanasi", val count : Int = 0) : A(name)
|
||
class C
|
||
|
||
val b = B("b", 10)
|
||
val c = C()
|
||
assertEquals(true, b as Any is A)
|
||
assertEquals(true, c as Any !is A)
|
||
|
||
// in 演算子と !in 演算子
|
||
val range1 = 0 .. 10
|
||
val range2 = 0 until 10 // 10を含まない。 until は言語機能ではなく infix関数
|
||
assertEquals(true, 10 in range1)
|
||
assertEquals(true, 10 !in range2)
|
||
|
||
// ==,=== 演算子とプリミティブ型
|
||
val long10 = 10L
|
||
val int10 = 10
|
||
val int10b : Int = generate10A()
|
||
val int10c : Int = generate10B()
|
||
|
||
// IntとLongを直接比較しようとするとコンパイルエラーになる
|
||
// assertEquals( true, int10 == long10 )
|
||
|
||
// 型が分からないようにするとビルドできるが、intの10とlongの10は異なると判断される
|
||
// Int.equals も同じ結果
|
||
assertEquals(false, int10 as Any == long10 as Any)
|
||
|
||
// Long.equals でも同じ結果
|
||
assertEquals(false, long10 as Any == int10 as Any)
|
||
|
||
// 同じ型に変換すると数値が同じだと判定できる
|
||
assertEquals(true, int10.toLong() == long10)
|
||
assertEquals(true, int10.toLong() == long10)
|
||
|
||
// === 演算子 とプリミティブ型
|
||
|
||
// 型が違うとコンパイルエラー
|
||
// assertEquals( true, int10 === long10 )
|
||
|
||
// 型が同じでも記述した時点で deprecatedという警告が出る。
|
||
// クラスファイルを読むと if_icmpne を使って数値比較していた。equalsではない
|
||
assertEquals(true, int10 === int10b)
|
||
assertEquals(true, int10 === int10c)
|
||
assertEquals(true, int10 === 10)
|
||
assertEquals(true, int10 === generate10A())
|
||
|
||
// Any型に変換して比較。警告はでない
|
||
// Anyへのキャストは java/lang/Integer.valueOf でboxingしてから checkcast java/lang/Objectして if_acmpne していた
|
||
assertEquals(true, int10 as Any === int10b as Any)
|
||
assertEquals(true, int10 as Any === int10c as Any)
|
||
assertEquals(true, int10 as Any === 10 as Any)
|
||
assertEquals(true, int10 as Any === generate10A() as Any)
|
||
|
||
// valueOfは-128..127は内部でキャッシュを行うから、値によっては同じアドレスだったり異なったりする
|
||
var intA = 0
|
||
var intB = 0
|
||
for(i in 126 .. 127) {
|
||
println("i=$i")
|
||
intA = i
|
||
intB = i
|
||
assertEquals(true, intA as Any === intB as Any)
|
||
}
|
||
for(i in 128 .. 130) {
|
||
println("i=$i")
|
||
intA = i
|
||
intB = i
|
||
assertEquals(false, intA as Any === intB as Any)
|
||
}
|
||
|
||
/*
|
||
蛇足だが、クラスファイルを読むのは
|
||
app/build/tmp/kotlin-classes/*UnitTest\**/TestKotlinFeature.class を
|
||
javap.exe -c TestKotlinFeature.class > javap.warning とすると逆アセンブルできる
|
||
*/
|
||
|
||
}
|
||
|
||
@Test
|
||
fun testNullableIf() {
|
||
|
||
// Boolean? は true false null の3値論理となる
|
||
// stringNull?.isNotEmpty() など、 ?.演算子を使うと Nullable型が発生するシチュエーションがよくある
|
||
|
||
val nullableTrue : Boolean? = true
|
||
val nullableFalse : Boolean? = false
|
||
val nullableNull : Boolean? = null
|
||
|
||
// ifの条件式部分は Boolean? の値を直接扱うことはできない。コンパイルエラーとなる
|
||
// assertEquals( 1 , if( nullableTrue ) 1 else 0 )
|
||
// assertEquals( 1 , if( nullableFalse ) 1 else 0 )
|
||
// assertEquals( 1 , if( nullableNull ) 1 else 0 )
|
||
|
||
// == != 演算子はnullを取り扱える
|
||
assertEquals(1, if(nullableTrue == true) 1 else 0)
|
||
assertEquals(0, if(nullableTrue == false) 1 else 0)
|
||
assertEquals(0, if(nullableTrue == null) 1 else 0)
|
||
assertEquals(0, if(nullableTrue != true) 1 else 0)
|
||
assertEquals(1, if(nullableTrue != false) 1 else 0)
|
||
assertEquals(1, if(nullableTrue != null) 1 else 0)
|
||
|
||
assertEquals(0, if(nullableFalse == true) 1 else 0)
|
||
assertEquals(1, if(nullableFalse == false) 1 else 0)
|
||
assertEquals(0, if(nullableFalse == null) 1 else 0)
|
||
assertEquals(1, if(nullableFalse != true) 1 else 0)
|
||
assertEquals(0, if(nullableFalse != false) 1 else 0)
|
||
assertEquals(1, if(nullableFalse != null) 1 else 0)
|
||
|
||
assertEquals(0, if(nullableNull == true) 1 else 0)
|
||
assertEquals(0, if(nullableNull == false) 1 else 0)
|
||
assertEquals(1, if(nullableNull == null) 1 else 0)
|
||
assertEquals(1, if(nullableNull != true) 1 else 0)
|
||
assertEquals(1, if(nullableNull != false) 1 else 0)
|
||
assertEquals(0, if(nullableNull != null) 1 else 0)
|
||
}
|
||
|
||
interface MyKotlinInterface {
|
||
|
||
fun method(x : Int) : Int
|
||
}
|
||
|
||
@Test
|
||
fun testSAM() {
|
||
|
||
// 定義例(文脈あり)
|
||
Thread { println("SAM 1") }.start()
|
||
Thread { println("SAM 2") }.start()
|
||
|
||
// 定義例(文脈不明)
|
||
val a = Runnable { println("SAM a") }
|
||
|
||
// 参照型の定義
|
||
val ref : Runnable = a
|
||
|
||
// 参照型の呼び出し
|
||
ref.run()
|
||
|
||
// Nullableな参照型の定義
|
||
val refNullable : Runnable? = a
|
||
if(refNullable != null) {
|
||
// 呼び出し
|
||
Thread(refNullable).start()
|
||
}
|
||
|
||
View.OnClickListener { println("clicked") }.onClick(null)
|
||
|
||
// kotlinで定義したインタフェースに対してSAMコンストラクタを使えるか?
|
||
// ダメでした
|
||
// val ki = MyKotlinInterface{
|
||
// it * it
|
||
// }
|
||
}
|
||
|
||
@Test
|
||
fun testLambda() {
|
||
// 定義例(文脈あり)
|
||
thread(start = true) { println("testLambda") }
|
||
println(10.let { x -> x * x })
|
||
println(10)
|
||
// 定義例(文脈不明)
|
||
val a = { println("testLambda") }
|
||
|
||
// 参照型の定義
|
||
val ref : (x : Int) -> Int = { it * it }
|
||
|
||
// 参照型の呼び出し
|
||
println(ref(10))
|
||
|
||
// 参照型の定義(Nullable)
|
||
val refNullable : TestLambdaCallback? = { it * it }
|
||
if(refNullable != null) {
|
||
refNullable(10)
|
||
}
|
||
|
||
}
|
||
|
||
@Test
|
||
fun testAnonymousFunction() {
|
||
// 定義例(文脈あり)
|
||
thread(start = true, block = fun() { println("testAnonymousFunction") })
|
||
println(10.let(fun(x : Int) = x * x))
|
||
println(10)
|
||
// 定義例(文脈不明)
|
||
val a = fun(x : Int) = x * x
|
||
|
||
// 参照型の定義
|
||
val ref : (x : Int) -> Int = a
|
||
|
||
// 参照型の呼び出し
|
||
println(ref(10))
|
||
|
||
// 参照型の定義(Nullable)
|
||
val refNullable : TestLambdaCallback? = fun(i : Int) = i * i
|
||
if(refNullable != null) {
|
||
refNullable(10)
|
||
}
|
||
}
|
||
|
||
@Test
|
||
fun testObjectExpression() {
|
||
// 定義例(文脈あり)
|
||
abstract class Base {
|
||
|
||
abstract fun method(x : Int) : Int
|
||
}
|
||
|
||
val a = object : Base() {
|
||
override fun method(x : Int) : Int {
|
||
return x * x
|
||
}
|
||
}
|
||
|
||
// 定義例(文脈の有無で変化しない)
|
||
// 参照型の定義
|
||
val ref : Base = a
|
||
|
||
// 参照型の定義(Nullable)
|
||
val refNullable : Base? = a
|
||
if(refNullable != null) {
|
||
val v = refNullable.method(10)
|
||
println("OE v=$v")
|
||
}
|
||
|
||
|
||
fun caller(b : Base) {
|
||
val v = b.method(10)
|
||
println("OE b $v")
|
||
}
|
||
caller(object : Base() {
|
||
override fun method(x : Int) : Int {
|
||
return x * x * x
|
||
}
|
||
|
||
})
|
||
}
|
||
|
||
private fun member(x : Int) = x * x
|
||
|
||
@Test
|
||
fun testMemberReference() {
|
||
fun caller(a : (receiver : TestKotlinFeature, x : Int) -> Int) {
|
||
val v = a(this, 10)
|
||
println("testMemberReference caller $v")
|
||
}
|
||
caller(TestKotlinFeature::member)
|
||
|
||
val b = TestKotlinFeature::member
|
||
val a : (receiver : TestKotlinFeature, x : Int) -> Int = TestKotlinFeature::member
|
||
}
|
||
|
||
fun methodNotInline(callback : (x : Int) -> Int) : Int {
|
||
return callback(3)
|
||
}
|
||
|
||
inline fun methodInline(callback : (x : Int) -> Int) : Int {
|
||
return callback(5)
|
||
}
|
||
|
||
@Test
|
||
fun testReturn() {
|
||
|
||
// loop@ for( i in 1..2) {
|
||
// // 関数の引数以外の場所で定義したラムダ式
|
||
// var x = { x : Int ->
|
||
// break // コンパイルエラー
|
||
// break@loop // コンパイルエラー
|
||
// // return // コンパイルエラー
|
||
// x * x
|
||
// }(10)
|
||
// println("testReturn A:$x")
|
||
//
|
||
// // 非インライン関数の引数として定義したラムダ式
|
||
// x = methodNotInline { x : Int ->
|
||
// break // コンパイルエラー
|
||
// break@loop // コンパイルエラー
|
||
//
|
||
// // return // コンパイルエラー
|
||
//
|
||
// return@methodNotInline x * x
|
||
// }
|
||
// println("testReturn B:$x")
|
||
//
|
||
// // インライン関数の引数として定義したラムダ式
|
||
// methodInline { x : Int ->
|
||
// break // コンパイルエラー
|
||
// break@loop // コンパイルエラー
|
||
//
|
||
// return 10 // できる
|
||
//
|
||
// return@methodInline 10 // できる
|
||
// }
|
||
// }
|
||
}
|
||
|
||
private fun <A, B> A.letNotInline(code : (A) -> B) : B {
|
||
return code(this)
|
||
}
|
||
|
||
@Test
|
||
fun testInline0() {
|
||
var result : Int
|
||
val n = 11
|
||
for(i in 1 .. 10) {
|
||
println(n.letNotInline { v ->
|
||
val rv = v * i
|
||
result = rv
|
||
rv
|
||
})
|
||
}
|
||
}
|
||
|
||
@Test
|
||
fun testInline1() {
|
||
var result : Int
|
||
val n = 12
|
||
for(i in 1 .. 10) {
|
||
println(n.let { v ->
|
||
val rv = v * i
|
||
result = rv
|
||
rv
|
||
})
|
||
}
|
||
}
|
||
|
||
@Test
|
||
fun testInline2() {
|
||
var result : Int
|
||
val n = 13
|
||
for(i in 1 .. 10) {
|
||
val rv = n * i
|
||
result = rv
|
||
println(rv)
|
||
}
|
||
}
|
||
|
||
@Test
|
||
fun testRawArray() {
|
||
// サイズを指定して生成
|
||
val a = IntArray(4)
|
||
for(i in a.indices) {
|
||
a[i] = i * 2
|
||
}
|
||
println(a.joinToString(","))
|
||
|
||
// サイズと初期化ラムダを指定して生成
|
||
val b = IntArray(4) { index -> index * 3 }
|
||
println(b.joinToString(","))
|
||
|
||
// 可変長引数で初期化するライブラリ関数
|
||
var b2 = intArrayOf(0, 1, 2, 3)
|
||
|
||
// 参照型の配列だと初期化ラムダが必須
|
||
val c = Array<CharSequence>(4) { (it * 4).toString() }
|
||
println(c.joinToString(","))
|
||
val d = Array<CharSequence?>(4) { if(it % 2 == 0) null else (it * 5).toString() }
|
||
println(d.joinToString(","))
|
||
|
||
// ラムダ式の戻り値の型から配列の型パラメータが推測される
|
||
val e = Array(4) { if(it % 2 == 0) null else (it * 6).toString() }
|
||
println(e.joinToString(","))
|
||
|
||
// 可変長引数で初期化するライブラリ関数
|
||
var e2 = arrayOf(null, 1, null, 2)
|
||
|
||
}
|
||
|
||
@Test
|
||
fun testOutProjectedType() {
|
||
fun foo(args : Array<out Number>) {
|
||
val sb = StringBuilder()
|
||
for(s in args) {
|
||
if(sb.isNotEmpty()) sb.append(',')
|
||
sb
|
||
.append(s.toString())
|
||
.append('#')
|
||
.append(s.javaClass.simpleName)
|
||
|
||
}
|
||
println(sb)
|
||
println(args.contains(6)) // 禁止されていない。inポジションって何だ…?
|
||
// args[0]=6 //禁止されている
|
||
}
|
||
foo(arrayOf(1, 2, 3))
|
||
foo(arrayOf(1f, 2f, 3f))
|
||
}
|
||
}
|