Classes
Smart casts
問題
以下の Java コードを スマートキャストと when 式 を使用して書き換えてください。
public int eval(Expr expr) {
if (expr instanceof Num) {
return ((Num) expr).getValue();
}
if (expr instanceof Sum) {
Sum sum = (Sum) expr;
return eval(sum.getLeft()) + eval(sum.getRight());
}
throw new IllegalArgumentException("Unknown expression");
}
解答
fun eval(expr: Expr): Int =
when (expr) {
is Num -> expr.value
is Sum -> eval(expr.left) + eval(expr.right)
else -> throw IllegalArgumentException("Unknown expression")
}
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
解説
Smart casts
ほとんどの場合、明示的なキャスト演算子を使用する必要はありません。コンパイラが自動的にオブジェクトをキャストしてくれるからです。これをスマートキャストと言います。コンパイラは、イミュータブルな値に対する型チェックと明示的キャストを追跡し、必要に応じて暗黙的な(安全な)キャストを自動的に挿入します。
fun demo(x: Any) {
if (x is String) {
print(x.length) // x は自動的に String 型にキャストされる
}
}
コンパイラは、否定的なチェックが返り値に繋がる場合、キャストが安全であることも認識できるほど賢いです。
if (x !is String) return
print(x.length) // x は自動的に String 型にキャストされる
Control flow
スマートキャストは、if
条件式だけでなく、when
式や while
ループでも機能します。
when (x) {
is Int -> print(x + 1)
is String -> print(x.length + 1)
is IntArray -> print(x.sum())
}
if
、when
、または while
の条件で変数を Boolean
型として宣言すると、コンパイラがその変数について収集した情報が対応するブロック内でスマートキャストのために利用できるようになります。
これは、ブール条件を変数に抽出するような操作に役立ちます。変数に意味のある名前を付けることで、コードの可読性が向上し、後でその変数を再利用できるようになります。
class Cat {
fun purr() {
println("Purr purr")
}
}
fun petAnimal(animal: Any) {
val isCat = animal is Cat
if (isCat) {
// コンパイラは isCat に関する情報にアクセスできるため、
// animal が Cat 型にスマートキャストされたことを認識します。
// そのため、purr() 関数を呼び出せます。
animal.purr()
}
}
fun main(){
val kitty = Cat()
petAnimal(kitty)
// Purr purr
}
おわりに
スマートキャストは、型チェックをしたあとに、その型を自動的に適切に型キャストしてくれる仕組みなんですね。難しく感じるのは Java を知らないからなんだろうなー。
is
はプリミティブ型でもオブジェクト型でも型チェックに使える点は JavaScript より便利そうだなって感じる。