ぽりへどろんの初心者ゲーム開発日記

ぽんこつプログラマが同人ゲームを作りながら調べたこと考えたことを書くブログ

Scala: NothingとNullで遊ぼう

Scalaを使っていると触ることになるであろうNothingとNullで遊んでみた。

 

こちらを参考にいたしました。

Scala Anyクラスメモ(Hishidama's Scala Any Memo)

 

こちらによると

nullはNullというクラスの値になっており、Null型は全てのAnyRefの子クラスの下に位置づけられる。

であり、

AnyVal・AnyRefも含めて全てのクラスの子クラスとしてNothingが存在する。

とのこと。

Javaには「すべてのクラスの子クラス」というものはなかったので勉強も兼ねて遊んでみた。

 

1 Nothing型とNull型の持つメソッド・フィールドは?

「すべてのクラスの子クラス」ならば、Nothing型はAny型の想定しうる全メソッド・フィールドを、Null型はAnyRef型の想定しうる全メソッド・フィールドを、継承しているのか、という疑問が湧いた。

もっともそんなことができれば、無限種類のメソッド・フィールドを持っていることになるので、実際は違うはずである。

早速、REPLで試してみた。まずはメソッド。

scala> classOf[Nothing].getMethods.foreach(println)

public void java.lang.Throwable.printStackTrace()

public void java.lang.Throwable.printStackTrace(java.io.PrintWriter)

public void java.lang.Throwable.printStackTrace(java.io.PrintStream)

public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace()

public synchronized java.lang.Throwable java.lang.Throwable.getCause()

public synchronized java.lang.Throwable java.lang.Throwable.initCause(java.lang.Throwable)

public java.lang.String java.lang.Throwable.toString()

public java.lang.String java.lang.Throwable.getMessage()

public java.lang.String java.lang.Throwable.getLocalizedMessage()

public java.lang.StackTraceElement java.lang.Throwable.getStackTrace()

public void java.lang.Throwable.setStackTrace(java.lang.StackTraceElement)

public final synchronized void java.lang.Throwable.addSuppressed(java.lang.Throwable)

public final synchronized java.lang.Throwable[] java.lang.Throwable.getSuppressed()

public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException

public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException

public final void java.lang.Object.wait() throws java.lang.InterruptedException

public boolean java.lang.Object.equals(java.lang.Object)

public native int java.lang.Object.hashCode()

public final native java.lang.Class java.lang.Object.getClass()

public final native void java.lang.Object.notify()

public final native void java.lang.Object.notifyAll() 

 

scala> classOf[Null].getMethods.foreach(println)

public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException

public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException

public final void java.lang.Object.wait() throws java.lang.InterruptedException

public boolean java.lang.Object.equals(java.lang.Object)

public java.lang.String java.lang.Object.toString()

public native int java.lang.Object.hashCode()

public final native java.lang.Class java.lang.Object.getClass()

public final native void java.lang.Object.notify()

public final native void java.lang.Object.notifyAll()

当たり前といえば、当たり前だけどすべてのメソッドは持っていなかった。

実際にnullの値から適当なメソッドを呼ぼうとしても怒られる。

scala> null.hoge()

<console>:12: error: value hoge is not a member of Null

       null.hoge()

続いて、フィールド。

scala> classOf[Null].getFields.foreach(println)

 

 

scala> classOf[Nothing].getFields.foreach(println)

 

それぞれフィールドは1つも持っていなかった。

今まで私は「クラスXがクラスYの子クラスであること」と「クラスXがクラスYを継承していること」が同値であると思っていて、「クラスYを継承したクラスXはYの全メソッド・フィールドを持っている」と思っていたが、どうにも違うということだ。

 

2 Nothing型とNull型の継承元は?

上の話を考えると「クラスXがクラスYの子クラスであること」と「クラスXがクラスYを継承していること」が同値ではないというのが実態なのだろう。

ではそれを確認するために、Nothing型とNull型の継承元を調べてみる。

scala> classOf[Nothing].getSuperclass

res8: Class[_] = class java.lang.Throwable

 

scala> classOf[Nothing].getInterfaces

res9: Array[Class[_]] = Array()

 

scala> classOf[Null].getSuperclass

res10: Class[_ >: Null] = class java.lang.Object

 

scala> classOf[Null].getInterfaces

res11: Array[Class[_]] = Array()

結果としてはNothing型はThrowableの子クラス、Null型はObject型の子クラスのようだ。それぞれ実装しているインターフェースは無し。

これを見る限り、確かに「クラスXがクラスYの子クラスであること」と「クラスXがクラスYを継承していること」は同値ではないようだ。

しかし、Nothing型がThrowableを継承しているのは意外だった。なぜなんだろう。

 

3 isInstanceOfは継承関係とクラスの親子関係のどちらを見ているのか?

「クラスXがクラスYの子クラスであること」と「クラスXがクラスYを継承していること」は同値ではないことを確認したところで次の疑問が湧いてきた。

それはisInstanceOfメソッドはクラスの親子関係と継承関係のどちらを判定しているのかということである。

REPLで試してみる。

scala> class Hoge extends AnyRef

defined class Hoge

 

scala> null.isInstanceOf[Hoge]

res15: Boolean = false

nullはNull型であり、Null型はHogeの子クラスだが、Hogeを継承していない。

この時に、null.isInstanceOf[Hoge]がfalseということはisInstanceOfは継承関係を見ているということだ。

 

4 Nothing型とNull型の子クラスは作れるか?

これは上までとは別の疑問。

「すべてのクラスの子クラス」というものに子クラスを作れるだろうか?という疑問である。できるとしたら、以下のようになってクラスの親子関係がループしてしまうだろう。

  1. Nothing型に子クラスが存在するとする。これをSubNothing型と呼ぶ
  2. SubNothing型はAny型の子クラスであるので、「Nothing型はすべてのAny型の子クラスである」という前提からNothing型はSubNothing型の子クラスである。

Null型についても同じである。

試してみる。

scala> class SubNothing extends Nothing

<console>:11: error: illegal inheritance from final class Nothing

       class SubNothing extends Nothing

                                ^

 

scala> class SubNull extends Null

<console>:11: error: illegal inheritance from final class Null

       class SubNull extends Null

                             ^

どっちもfinal classなので子クラスを作れないとのこと、納得。

 

5 Nothing型を作れるか?

これもまた別の疑問。まずは参照先サイトの以下の記述について。

「値が無い」という意味では、Nothingというクラスがある。
こちらは本当に値が無くて、Nothingを返すよう宣言したメソッドでは、値を返す事が出来ない。

つまり、Nothing型に属するインスタンスはないということだろう。

おそらく、Nothing型のコンストラクタが存在しない、もしくは存在しても呼べないということではないだろうか。

試してみる。

scala> classOf[Nothing].getConstructors.foreach(println)

public scala.runtime.Nothing$()

おぉ、Nothing型にコンストラクタは存在するようだ。

scala> classOf[Nothing].getConstructor()

res23: java.lang.reflect.Constructor[Nothing] = public scala.runtime.Nothing$()

しかも、コンストラクタを取得できる。

scala> classOf[Nothing].getConstructor().newInstance()

java.lang.InstantiationException

  at sun.reflect.InstantiationExceptionConstructorAccessorImpl.newInstance(InstantiationExceptionConstructorAccessorImpl.java:48)

  at java.lang.reflect.Constructor.newInstance(Constructor.java:423)

  ... 32 elided

しかし、コンストラクタを実行するとInstantiationExceptionとなってしまった。やはりNothing型のインスタンスは作れないようだ。

 

ちなみにNull型については

scala> classOf[Null].getConstructors.foreach(println)

 

そもそもコンストラクタが無いようだ。

 

まとめ

ScalaのNothing型とNull型について色々と試行錯誤。

Nothing型もNull型もすべての親クラスのメソッド・フィールドを持っているわけでは無い。

「クラスの親子関係」と「クラスの継承関係」はイコールでは無い。

Nothing型はThrowableを継承した、すべてのAny型の子クラス。

Null型はObjectを継承した、すべてのAnyRef型の子クラス。

isInstaceOfは継承関係をチェックする。

Nothing型とNull型は継承できない。

Nothing型はコンストラクタを持つがインスタンスを作ろうとすると例外となる。

 

Nothing型がThrowableな理由は後で調べたいなぁ。