Nettyのコードを写経したりといった、Java関連のコードを置き換えている時にいつも気になっていたので。
*コメントいただいたので、もう少し内容を見直しました
Scalaでは、Javaでいうinstanceof演算子とキャストがそれぞれAny#isInstanceOf、Any#asInstanceOfというメソッド形式に置き換えられています。よって、nullに対して適用したりすると、危なさそうなのですが実際コケたところを見たことがありません。
これを、ちゃんと見てみることにしました。
まずは動作確認。以下のようなコードを用意します。
instanceof_test.scala
val msg: Any = "Hello World" println(msg.isInstanceOf[String]) println(msg.asInstanceOf[String])
実行してみます。
$ scala instanceof_test.scala
true
Hello World
では、これをこう変えてみます。
//val msg: Any = "Hello World" val msg: Any = null println(msg.isInstanceOf[String]) println(msg.asInstanceOf[String])
実行してみます。
$ scala instanceof_test.scala
false
null
…普通に動きましたね。まあ予想通りです。
これをちゃんと見てみましょう。まず、Anyのソースコードから。
src/library-aux/scala/Any.scalaから抜粋
/* __ *\ ** ________ ___ / / ___ Scala API ** ** / __/ __// _ | / / / _ | (c) 2002-2010, LAMP/EPFL ** ** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** ** /____/\___/_/ |_/____/_/ | | ** ** |/ ** \* */ package scala /** Class `Any` is the root of the Scala class hierarchy. Every class in a Scala * execution environment inherits directly or indirectly from this class. */ abstract class Any { 〜省略〜 /** Test whether the dynamic type of the receiver object is `T0`. * * Note that the result of the test is modulo Scala's erasure semantics. * Therefore the expression `1.isInstanceOf[String]` will return `false`, while the * expression `List(1).isInstanceOf[List[String]]` will return `true`. * In the latter example, because the type argument is erased as part of compilation it is * not possible to check whether the contents of the list are of the specified type. * * @return `true` if the receiver object is an instance of erasure of type `T0`; `false` otherwise. */ def isInstanceOf[T0]: Boolean = sys.error("isInstanceOf") /** Cast the receiver object to be of type `T0`. * * Note that the success of a cast at runtime is modulo Scala's erasure semantics. * Therefore the expression `1.asInstanceOf[String]` will throw a `ClassCastException` at * runtime, while the expression `List(1).asInstanceOf[List[String]]` will not. * In the latter example, because the type argument is erased as part of compilation it is * not possible to check whether the contents of the list are of the requested type. * * @throws ClassCastException if the receiver object is not an instance of the erasure of type `T0`. * @return the receiver object. */ def asInstanceOf[T0]: T0 = sys.error("asInstanceOf") }
とまあ、sys.errorに渡してすぐエラーになるようになっています。しかも、final宣言されていない割には、ScalaのAPIを見ると両方のメソッドともfinalと書かれているので何かトリックがあるんだろうと…。まあ、これが実際に使われているわけではないんでしょうね。
こうなったらJDで逆コンパイルだ!ということで、以下のようなソースを用意。
object Sample { def main(args: Array[String]): Unit = { val msg: Any = null println("isInstanceOf => %s".format(msg.isInstanceOf[String])) println("asInstanceOf => %s".format(msg.asInstanceOf[String])) } }
これをfscにかけた後に、JDを適用してみます。このコードだとクラスファイルが2つできます。
まずはどうでもいい方から。
import scala.reflect.ScalaSignature; @ScalaSignature(bytes="\006\0015:Q!\001\002\t\006\025\taaU1na2,'\"A\002\002\017q*W\016\035;z}\r\001\001C\001\004\b\033\005\021a!\002\005\003\021\013I!AB*b[BdWmE\002\b\025I\001\"a\003\t\016\0031Q!!\004\b\002\t1\fgn\032\006\002\037\005!!.\031<b\023\t\tBB\001\004PE*,7\r\036\t\003'Yi\021\001\006\006\002+\005)1oY1mC&\021q\003\006\002\f'\016\fG.Y(cU\026\034G\017C\003\032\017\021\005!$\001\004=S:LGO\020\013\002\013!)Ad\002C\001;\005!Q.Y5o)\tq\022\005\005\002\024?%\021\001\005\006\002\005+:LG\017C\003#7\001\0071%\001\003be\036\034\bcA\n%M%\021Q\005\006\002\006\003J\024\030-\037\t\003O)r!a\005\025\n\005%\"\022A\002)sK\022,g-\003\002,Y\t11\013\036:j]\036T!!\013\013") public final class Sample { public static final void main(String[] paramArrayOfString) { Sample..MODULE$.main(paramArrayOfString); } }
続いて、中身の方へ。
import scala.Predef.; import scala.ScalaObject; import scala.collection.immutable.StringLike; import scala.runtime.BoxesRunTime; public final class Sample$ implements ScalaObject { public static final MODULE$; static { new (); } public void main(String[] args) { null; Object msg = null; Predef..MODULE$.println(Predef..MODULE$.augmentString("isInstanceOf => %s").format(Predef..MODULE$.genericWrapArray(new Object[] { BoxesRunTime.boxToBoolean(msg instanceof String) }))); Predef..MODULE$.println(Predef..MODULE$.augmentString("asInstanceOf => %s").format(Predef..MODULE$.genericWrapArray(new Object[] { (String)msg }))); } private Sample$() { MODULE$ = this; } }
よーく見ると、isInstanceOfが
msg instanceof String
となっていて、asInstanceOfが
(String)msg
となっていますね。
(追記)
<ご指摘いただいた内容を、Scalaの仕様書を含めて追記します>
こんな挙動をするため、これらのメソッドはnullそのものに対しても起動することができます。
println(null.isInstanceOf[String]) // => false println(null.asInstanceOf[String]) // => null
この辺りの話は、Scalaの仕様書の75ページ目「6.3 The Null Value」に記載があります。
http://www.scala-lang.org/docu/files/ScalaReference.pdf
ここで、nullオブジェクトに対して適用した場合は、isInstanceOfは常にfalseを返し、asInstanceOfは適用先がAnyRefならばnullに、そうでなければNullPointerExceptionをスローすると書いてあります。
Javaで、以下のようなコードを実行するとNullPointerが発生するのと似たような感じなんでしょうね、仕様的には。
public class CastTest { public static void main(String[] args) { try { Integer integer = null; int i = (int)integer; // NullPointerException } catch (Exception e) { e.printStackTrace(); } try { Integer integer = null; if (integer == 0) { // NullPointerException System.out.println("Hello World"); } } catch (Exception e) { e.printStackTrace(); } } }
んで、コメントいただいている内容ですと、AnyValに対するこれらの挙動が怪しいということでしたので、確認してみましょう。
val i: Any = null println(i.isInstanceOf[Int]) // => false println(i.asInstanceOf[Int]) // => 0
確かに、null.asInstanceOf[Int]が0になりました…。
なので、以下のコードはキャストするかどうかで結果が変わります。
println(i.asInstanceOf[Int] == 0) // => true println(i == 0) // => false
こういう目で見ると、ちょっと微妙な気持ちになります。
さて、このあたりはいったいどうなっているかというと??ということで、今度はこんなコードを用意。
object Sample { def main(args: Array[String]): Unit = { val i: Any = null println(i.isInstanceOf[Int]) println(i.asInstanceOf[Int]) println(i.asInstanceOf[Int] == 0) println(i == 0) } }
このコンパイル結果を、JDで見てみます。今回は、isInstanceOf、asInstanceOfの結果を抜粋します。printlnの部分は省略しています。
null; Object i = null; i instanceof Integer; BoxesRunTime.unboxToInt(i); BoxesRunTime.unboxToInt(i) == 0; BoxesRunTime.equals(i, BoxesRunTime.boxToInteger(0));
というわけで、BoxesRunTime#unboxToIntの結果、こういうことになってそうですね。
んじゃあ、BoxesRunTime#unboxToIntの実装は?
src/library/scala/runtime/BoxesRunTime.javaより抜粋
/* __ *\ ** ________ ___ / / ___ Scala API ** ** / __/ __// _ | / / / _ | (c) 2006-2011, LAMP/EPFL ** ** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** ** /____/\___/_/ |_/____/_/ | | ** ** |/ ** \* */ package scala.runtime; import java.io.*; import scala.math.ScalaNumber; /** An object (static class) that defines methods used for creating, * reverting, and calculating with, boxed values. There are four classes * of methods in this object: * - Convenience boxing methods which call the static valueOf method * on the boxed class, thus utilizing the JVM boxing cache. * - Convenience unboxing methods returning default value on null. * - The generalised comparison method to be used when an object may * be a boxed value. * - Standard value operators for boxed number and quasi-number values. * * @author Gilles Dubochet * @author Martin Odersky * @contributor Stepan Koltsov * @version 2.0 */ public final class BoxesRunTime { 〜省略〜 public static int unboxToInt(Object i) { return i == null ? 0 : ((java.lang.Integer)i).intValue(); } 〜省略〜
そりゃあ、0になりますよね…。