Scalaプログラミング(第20章:抽象メンバー)

6519 ワード

1.抽象メンバーの概要:クラスまたは特質のメンバーが現在のクラスで完全な定義を持っていない場合は、抽象的です.次の特質は、抽象タイプ(T)、抽象メソッド(transform)、val(initial)、var(current)の4つの抽象メンバーを宣言します.
trait Abstract{
  type T
  def transform(x:T):T
  val initial:T
  var current:T
}

Abstract特質の具体的な実装には、各抽象メンバーの定義を埋め込む必要がある.次のようになります.
class Concrete extends Abstract{
   type T = String
   def transform(x: T): T = x+x
   val initial: T = "hi"
   var current: T = initial
}

この例はScalaにどのような抽象的なメンバーがいるかについての大まかな概念を与えることができるはずです.
 
2.タイプメンバー:前節の例では、Scalaの抽象タイプとは、あるクラスまたは特質のメンバーとしてtypeキーワードで宣言される(定義は与えられない)タイプを指すことがわかります.クラス自体は抽象的であってもよいが,特質は本来定義的に抽象的であるが,クラスも特質もScalaでは抽象的なタイプとは呼ばない.
タイプメンバーを使用する理由の1つは、本名が冗長または意味が明らかでないタイプに、短く記述性の強い別名を定義することです.
 
3.抽象的val:抽象的valの宣言は抽象的な無参メソッド宣言のように見えます.
val initial:String

方コードを使用すると、valおよびメソッドを完全に同じ方法(すなわちobj.initial)で参照できます.しかし、initialが抽象的なvalである場合、使用者はobjに対する毎回の保証を得ることができる.Initialの参照は同じ値を渡します.initialが抽象的な方法である場合、この保証は成立しない.なぜなら、initialは、閉パケットなどの特定の毎回異なる値を返す方法によって実現されるからである.
言い換えれば、抽象valはその合法的な実現を制限している:いかなる実現もval定義でなければならない.defやvarではありません.一方、抽象メソッド宣言は、具体的なメソッド定義または具体的なval定義で実装することができる(より具体的になる):
abstract class Fruit{
  val v:String  //v   
  def m:String  //m    
}

class Apple extends Fruit {
  val v:String ="Value"
  val m:String ="Method" // val  (  )def OK 
}

class BadApple extends Fruit {
  def v:String ="Value"  //  :  v        ,     
  def m:String ="Method"
}

 
4.抽象的なvar:抽象valと同様に、抽象varは名前とタイプのみを宣言しますが、初期値は与えません.
trait AbstractTime{
  var hour:Int
  var minute:Int
}

クラスメンバーとして宣言されたvarのデフォルトにはgetterメソッドとsetterメソッドがあります.次の定義と同等です.
trait AbstractTime{
  def hour:Int         //hour getter  
  def hour_=(x:Int)    //hour setter  
  def minute:Int       //minute getter  
  def minute_:(x:Int)  //minute setter  
}

 
5.抽象的なvalを初期化する:抽象的なvalは、スーパークラスパラメータの機能を担う場合があります.サブクラスでスーパークラスで欠落している詳細を提供することができます.これは,特質がパラメータの構造方法を伝達していないため,特質にとって特に重要である.従って,通常,特質に対するパラメータ化は,サブクラスにおける抽象valの実現によって達成される.簡略化されたRational特質のように:
trait RationalTrait{
  val n:Int
  val d:Int
  println("trait")
  println(n+" "+d)
}

この特質の具体的なインスタンスをインスタンス化するには、抽象的なval定義を実装する必要があります.
new  RationalTrait{
      override val n: Int = 1
      override val d: Int = 2
      val anonymousClass=println("Anonymous class")
    }

//trait
//0 0
//Anonymous class

ここでのnewキーワードは,特質名RationalTraitの前に現れ,次いで括弧で囲まれたクラス定義体である.この式はprintlnタグによって特質が混入し定義体によって定義された匿名クラスのインスタンスを渡します.
この特定の匿名クラスのインスタンス化の役割はnew Rational(1,2)がインスタンスを作成する役割と似ている.しかし、式の初期化の順序にはいくつかのわずかな違いがあります.
new Rational(expr1,expr2)

expr 1とexpr 2の2つの式は、Rationalクラスの初期化前に評価され、expr 1とexpr 2の値はRationalクラスの初期化プロセスに表示されます.特質的には、状況は正反対です.
new  RationalTrait{
      override val n: Int = expr1
      override val d: Int = expr2
      val anonymousClass=println("Anonymous class")
    }

expr 1とexpr 2の2つの式は匿名クラス初期化プロセスの一部として評価されるが,匿名クラスはRationalTrait特質の後に初期化される.したがって、RationalTraitの初期化では、nとdの値は使用できません(より正確には、2つの値のいずれかの選択に対してタイプIntのデフォルト値、0が渡されます).6章のように前置条件require文を使用すると、
require(d != 0)

nとdが初期化されたときはデフォルト値0だったため、requireの呼び出しに失敗しました.Scalaは、この問題に対処するための2つのオプションスキームを提供する:プリ初期化フィールドと不活性val.
≪プリ初期化フィールド|Pre Initialization Fields|emdw≫:サブクラスのフィールドをスーパークラスが呼び出される前に初期化する第1のスキーム.フィールド定義でスーパークラスの構築方法の前にカッコを付けるだけです.
new {
      override val n: Int = 1
      override val d: Int = n
      val anonymousClass=println("Anonymous class")
    } with RationalTrait

//Anonymous class
//trait
//1 1

プリ初期化フィールドは匿名クラスに限らず、オブジェクトまたは名前付きサブクラスでも使用できます.
object twoThirds extends {
      override val n: Int = 2
      override val d: Int = 3
      val anonymousClass=println("Anonymous class")
    } with RationalTrait
//   

クラス定義の事前初期化フィールド:
    class RationalClass(n:Int,d:Int) extends {
      override val numerArg: Int = n
      override val denomArg: Int = d
      val anonymousClass=println("Anonymous class")
    } with RationalTrait {
      println("Class body")
    }

不活性なval:val定義の前にlazy修飾子を付けると、右側の初期化式はvalが初めて使用されたときにのみ評価されます.Demoオブジェクトを定義します.
object Demo{
  val x={println("initializing");"done"}
}
Demo
//  :initializing

xをlazyとして定義します.
object Demo{
  lazy val x={println("initializing");"done"}
}
Demo
//   
Demo.x
//  :initializing

現在、Demoの初期化はxの初期化には関与していない.xの初期化は、xへの最初のアクセスに遅延される.これはxがdefで無パラメトリックメソッドとして定義されている場合と同様である.しかし、defとは異なり、不活性なvalは永遠に何度も評価されません.実際,不活性なvalを最初に評価した後,その結果は保存され,後続の使用では,この同じvalが多重化される.RationalTraitの再作成:
trait RationalTrait{
  val numerArg:Int
  val denomArg:Int
  lazy val numer: Int = numerArg / g
  lazy val denom: Int = denomArg / g
  override def toString: String = numer + "/" + denom
  private lazy val g={
    require(denomArg!=0)
    gcd(numerArg,denomArg)
  }
  private def gcd(a: Int, b: Int): Int = {
    if (b == 0) a else gcd(b, a % b)
  }
}

TOStringメソッドを呼び出すときにnumerとdenomを呼び出し、gを呼び出し、初期化プロセスを完了する.
 
6.抽象タイプ:「type T」という抽象タイプの宣言で、Tは宣言時にまだ未知のタイプを指す.異なるサブクラスは異なるTの実装を提供することができる.AnimalがFoodを食べる抽象クラスを定義します.
class Food
abstract class Animal {
  type SuitableFood <:food def="" eat=""/>

Animalの定義があり、牛が草を食べることを定義することができます.
class Grass extends Food
class Cow extends Animal {
  type SuitableFood = Grass
  def eat(food:SuitableFood)={}
}

このようにして、特定のタイプの抽象タイプを作成することもできます.
class Grass(val s:String) extends Food
val c=new Cow
val g=new c.SuitableFood("Grass")
println(g.s)

//  :Grass

ただし、未確定タイプの抽象タイプは作成できません.
abstract class Animal {
  type SuitableFood <:food def="" eat="" suitable="new" suitablefood=""/>

 
7.列挙:JavaとC#を含む他の言語には、列挙タイプを定義するために組み込まれた構文構造があります.Scalaは列挙を表すために特別な構文を必要とせず、標準クラスライブラリにscalaというクラスを提供する.Enumeration.次のようになります.
object Direction extends Enumeration{
  val North= Value("North")
  val East= Value("East")
  val South= Value("South")
  val West= Value("West")
}
println(Direction.East.id)
println(Direction(1))

//1
//East