にほんごのれんしゅう

日本語として伝えるための訓練を兼ねたテクログ

scalaの継承ではまった話

scalaの継承ではまった話

scalaというか,JVM一般にいえることなのだろうけど,変なところではまって数時間を無駄に過ごした.
継承を用いたポリモフィズムは一般的であるが,最近は型クラスのマッチングなどを用いて処理していたりして,流行でない感がする.
そんな中どうでもいい,処理ではまったので紹介する.

派生クラスでoverrideすると基底クラスの同名の変数がnullになる

何を言っているかわからないと思うが以下のコードで再現できる.

package MAIN
class Base{
  val hoo = "Base"
  val init = {
    hoo.split('a') // hoo is null, so throw exception
  }
}
class Child extends Base{
  override val hoo = "Child" //Base object's instance will be null
  val bar = "Base"
  def p() = {
    println(bar)
    println(hoo)
  }
}
object MAIN {
  def main(args: Array[String]) = {
    val c = new Child()
    c.p()
  }
}

とりあえず,回避法を考えてみた (xuweiさんよりご指摘をいただいた部分)

 try...catchステートメントを使いたくなかったので,lambdaで回避する方法を紹介.
lambdaでメンバ変数をキャプチャして,プロシージャを無理やり継続させる.

package MAIN

class Base{
  val hoo = "Base"
  val init = {
    ((x:String)=> if(x != null) x.split('a') else null )(hoo) //これはscala的には関数にするかlazy valにしたほうがいいらしい
  }
  println(init) //null is output
}
class Child extends Base{
  override val hoo = "Child" //Base object's instance will be null
  val bar = "Base"
  def p() = {
    println(bar)
    println(hoo)
  }
}
object MAIN {
  def main(args: Array[String]) = {
    val c = new Child()
    c.p()
  }
}

try...catchステートメントを使わず,プロシージャは継続可能になったが,,,

xuweiさんのご指摘による修正

詳しくはコメントを抜粋していただけると助かる.

package MAIN

class Base{
  val hoo = "Base"
  lazy val init = {
    hoo.split('a').toList
  }
  println(init.mkString("!"))
}
class Child extends Base{
  override final val hoo = "ChildFromBase" //ここ修正 finalとは,,,いったい
  val bar = "Base"
  def p() = {
    println(bar)
    println(hoo)
  }
}
object MAIN {
  def main(args: Array[String]) = {
    val c = new Child()
    c.p()
  }
}

C++とかほかの言語も気になる

Javaは宗教上の理由により書けないのでC++でみてみる.

class CBase{
  public:
  std::string hoge;
  CBase():hoge("Base foo"){
    std::cout << hoge << std::endl;
  }
  virtual ~CBase(){};
};
class CChild: public CBase{
  public:
  std::string hoge;
  CChild():hoge("Child foo"){
    std::cout << hoge << std::endl;
  }
};
int main(int argc, char const *argv[])
{
  /**
   * @brief keishou test
   * */
  auto cb = std::shared_ptr<CBase>(new CBase());
  auto cc = std::shared_ptr<CChild>(new CChild());
}

コンパイラの設定は下記の通りである.かなり,ワーニングを殺しているが,下記の状態では,何も警告は出なかった.

g++-4.8 -std=c++11 core.cpp -o a.o -I./include -Wl,-rpath,./lib  -L./lib -lpthread -I/usr/include/boost -Wno-deprecated -Wno-return-local-addr

このコンパイルされた内容で,問題なく動作することを確認した.

gkobayas@ubuntu:~/dev/YUTORI/YUTORI$ ./a.o
Base foo #基底クラスのインスタンス作成時のコール
Base foo #派生クラスのインスタンス作成時のコール
Child foo #派生クラスのインスタンス作成時のコール

結論

scalaの入門にも立てていなかったので精進する.精進したところで言語仕様が理解できるかは不明だけども. 独習はやめるべきかな,,,