SubwayTooter-Android-App/app/src/test/java/jp/juggler/subwaytooter/TestKotlinFeature.kt

493 lines
14 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@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))
}
}