6.関数オブジェクト


第6章では,関数型オブジェクトの変更不可能な状態特徴について述べた.そのため、本の中で点数(有理数)を例に挙げて説明します.

変更不可オブジェクトのメリットとデメリット


長所
  • オブジェクトのライフサイクル内の状態は変わらないため、オブジェクトの内部値を推定しやすい.
  • の内部状態は変更できないため、メソッドパラメータを使用して自由に渡すことができます.
  • の内部状態は変更できないため、複数のスレッドからアクセスできます.(同期問題X)
  • オブジェクトは、セキュアなハッシュ・テーブル・キーとして使用することができる.(HashSetの可変オブジェクトをキーに設定します.ステータスを変更すると、HashSetでオブジェクトが見つからない場合があります.)
  • 短所
  • ステータスを変更するには、各ステータスに新しいオブジェクトを作成する必要があります.オブジェクトの作成にコストがかかる場合、パフォーマンスのボトルネックが発生する可能性があります.
  • スコアクラスの詳細

  • 分数(有理数)はnとdが整数、d>0の場合、n/dは
  • を表す.
  • nは分子であり、dは分母
  • である.
  • 分数の4則演算から,数学の分数に変更可能な状態はないことが分かる.(各被演算子を演算すると、新しいスコアが生成されます.)元の2つの点数自体は変わっていない.
  • したがって,被演算子となる各スコアは異なる従来のオブジェクトである.2つの従来のオブジェクトを加算すると、それに対応する新しい従来のオブジェクトが生成されます.
    この章を終了すると、Rational Classを使用して次の作業を行うことができます.
    scala〉 val oneHalf = new Rational(l, 2)
    oneHalf: Rational = 1/2
    
    scala〉 val twoThirds = new Rational(2, 3)
    twoThirds: Rational = 2/3
    
    scala> (oneHalf / 7) + (1 - twoThirds)
    resO: Rational = 17/42
    

    条件の作成


    従来のオブジェクトは変更できないオブジェクトとして決定されます.
    変更できないオブジェクトは、インスタンスの作成に必要なすべての情報(分数オブジェクトの場合は分子、分母値)を指定します.
    →生成後に値を変更できる場合は、変更可能なオブジェクトになります.
    class Rational(n: Int, d:Int)	// n, d는 클래스 파라미터
    ▼スカラコンパイラは内部で2つのクラスパラメータを統合し、クラスパラメータなどの2つのパラメータを受信するPrimary Constructorを作成します.
    ▼Javaではクラスに受容因子の生成者があり、スカラーではクラスが受容因子である.これらのスカラーはクラス内でパラメータを直接使用できるため、より簡潔です.
    文を指定することなく、フィールドを定義し、作成者のパラメータをフィールドにコピーできます(Javaとは異なり、他の作成者を作成する必要はありません).
    ▼スカラーコンパイラはクラス内にあり、フィールドやメソッド定義にないコードをメインジェネレータ内に押し込む.
    たとえば、デバッグメッセージを出力コードとして記述できます.
    class Rational(n: Int, d:Int){
    	println("Created " + n + "/" + d)
    }
    
    ====================
    scala > new Rational(1, 2)
    Created 1/2
    res0: Rational = Rational02591e0c9
  • スカラーコンパイラはprintlnを呼び出すこのコードをRationalクラスのプライマリジェネレータに入れます.したがって、新しい従来のインスタンスを作成するたびにprintlnから
  • デバッグメッセージが出力されます.

    TOStringメソッドの再実装


    デフォルトではClassはjavaです.lang.ObjectクラスのtoStringメソッドを呼び出します.
    @表示、16進数を出力します.ex) Rational@2591e0c9
    これらの方法はoverride修飾子によって再定義できます.
    class Rational(n: Int, d: Int) {
        override def toString = n + "/" + d
    }

    前提条件の確認


    スコアの分母はゼロにできません.
    したがって、オブジェクトを作成するときは、情報が有効であることを確認する必要があります.
    前提条件の定義による解決
  • の前提条件は何ですか?
    メソッドまたは作成者による入力値の制約
    コール側が遵守すべき要求
  • 前提条件はrequire文で作成できます.
    class Rational(n: Int, d: Int) {
        require(d != 0)     // d == 0 이라면 IllegalArgumentException 예외 발생
        override def toString = n + "/" + d
    }

    フィールドの追加


    加算機能


    従来は変更できないオブジェクトであるため、addメソッドではオブジェクト自体の値を変更することはできません.
    したがって、追加の値を含む新しい一般オブジェクトを返す必要があります.
    // 아래 코드는 컴파일 에러가 발생한다.
    class Rational(n: Int, d: Int) {
        require(d != 0)
        override def toString = n + "/" + d
     
        def add(that: Rational): Rational =
            new Rational( n * that.d + that.n * d, d * that.d )
    }
  • クラスパラメータn,dはそのクラス内部でのみ
  • にアクセス可能である.
  • addメソッドなどの他のオブジェクトのクラスパラメータは
  • にアクセスできません.
    したがって、可変オブジェクトのクラスパラメータにアクセスするには、フィールドを個別に宣言する必要があります.
    class Rational(n: Int, d: Int) {
        require(d != 0)
        val numer: Int = n
        val denom: Int = d
        override def toString = numer + "/" + denom
     
        def add(that: Rational): Rational =
            new Rational( numer * that.denom + that.numer * denom, denom * that.denom )
    }

    自己参照


    現在実行中のメソッドの呼び出し先インスタンスの参照を自己参照(self reference)と呼びます.
    作成
    class Rational(n: Int, d: Int) {
        require(d != 0)
        val numer: Int = n	// 분자
        val denom: Int = d	// 분모
        override def toString = numer + "/" + denom
     
        def add(that: Rational): Rational =
            new Rational( numer * that.denom + that.numer * denom, denom * that.denom )
         
         // 인자로 받은 Rational과 비교해 더 작은지 여부를 확인
         // 자기 스스로를 참조한다.
        def lessThan(that: Rational) =
            this.numer * that.denom < that.numer * this.denom
     
        def max(that: Rational) =
            if (this.lessThan(that)) that else this
    }

  • this.numerはlessthanメソッドが属するオブジェクトの分子を表す.

  • 省略できない場合は...
  • max関数では、最初のthisは冗長です.
    → this.lessthan(that)をlessthan(that)に変更してもかまいません
  • ですが、2つ目は実行結果がfalseの場合、実行結果として返される値です.省略不可(戻り値消失)
  • ほじょジェネレータ


    クラスに複数のコンストラクション関数が必要な場合は、→アシストコンストラクション関数を使用します.
    ex)分母はあらかじめ1に設定しておき,分子のみをパラメータとする生成器
  • スカラーのアシストジェネレータはdef this(...)
  • class Rational(n: Int, d: Int){
    
        require(d!=0)
        val numer : Int = n
        val denom: Int = denom
    
    
        def this(n: Int) = this(n, 1)       // 보조 생성자
    
        override def toString = numer + "/" + denom
        def add(that: Rational) : Rational = 
            new Rational(
                numer * that.denom + that.numer * denom,
                denom * that.denom
            )
    }
    scala> val y = new Rational(3)
    y: Rational = 3/1

    専用フィールドとメソッド


    専用フィールドとメソッドを持つRailクラス
  • 非公開変数g
  • 非公開方法gcd
  • スカラーコンパイラは、Ratialクラスの3フィールドに関する初期化コードをソースコードの順にメインジェネレータにセット→gの初期化コードgcd(n.abs,d,abs)を他の2つの初期化コードより先に実行する
    class Rational(n: Int, d: Int){
    
        require(d!=0)
        private val g = gcd(n.abs, d,abs)       // 비공개 변수
        val numer : Int = n / g
        val denom: Int = denom / g
    
    
        def this(n: Int) = this(n, 1)       // 보조 생성자
    
        override def toString = numer + "/" + denom
        def add(that: Rational) : Rational = 
            new Rational(
                numer * that.denom + that.numer * denom,
                denom * that.denom
            )
    
        override def toString = numer + "/" + denom
    
        private def gcd(a: Int, b:Int): Int =       // 비공개 메소드
            if(b == 0) a else gcd(b, a%b)
    }
    
    

    演算子の定義


    既存のadd法の代わりに数学記号を用いることができる.
    つまり、演算子を再定義できます.
    class Rational(n: Int, d: Int){
    
        require(d!=0)
        private val g = gcd(n.abs, d,abs)       // 비공개 변수
        val numer : Int = n / g
        val denom: Int = denom / g
    
    
        def this(n: Int) = this(n, 1)       // 보조 생성자
    
        override def toString = numer + "/" + denom
    
        def + (that: Rational) : Rational = 
            new Rational(
                numer * that.denom + that.numer * denom,
                denom * that.denom
            )		// + 연산자 정의
        
        def * (that: Rational) : Rational = 
            new Rational(numer * that.numer, denom * that.denom)		// * 연산자 정의
    
        override def toString = numer + "/" + denom
    
        private def gcd(a: Int, b:Int): Int =       // 비공개 메소드
            if(b == 0) a else gcd(b, a%b)
    }
    
    scala> val x = new Rat onal(l, 2)
    x: Rational = 1/2
    
    scala> val y = new Rat onal(2, 3)
    y: Rational = 2/3
    
    scala> x + y
    res7: Rational = 7/6

    スカラの識別子


  • アルファベットしきべつし
    -アルファベットまたはアンダースコア(_)で始まり、2番目のアルファベットからアルファベット、数値、およびアンダースコアが使用可能
    -特殊文字$度文字ですが、予約文字なのでX
    -javaの慣例に従ってcamel-caseでマークします.( ex) toString, HashSet )
    -フィールド、メソッドパラメータ、領域変数、および関数は小文字で始まる
    -クラスは大文字で始まる
    -定数は大文字のみで表されます(Javaの慣例とは異なります).
    -スカラでは定数は大文字のみで表されます.
    一般的に、X_OFFSETのようなJavaスタイルの定数タグに対して、Scaraは、XOffsetのようなCape−Caseを使用する.

  • 演算子識別子
    -1つ以上の演算子文字で構成されます.
    -スカラーコンパイラは、内部で$を使用して演算子識別子を分解し、適切なjava識別子を再生成します.
    −識別子:->の内部は$colon$minus$greaterに変換される.

  • ハイブリッド識別子
    -アルファベットと数値からなる識別子の後ろに下線があり、後ろに演算子識別子があります.
    -unary_+は、単項演算子+を定義するメソッドの名前です.

  • テキスト識別子
    -逆引用符`...`を含む任意の文字列.
    -実行時に認識できる文字列は、すべて逆引用符の間に配置できます.
    -スカラ予約語も文字識別子として定義することができる.
    ex)yieldはscalaの保持語であるため、java Threadクラスの静的メソッドyieldにアクセスしたい場合、Thread.完成品()と一緒に使用できません.
    Thread. メソッド名は`yield()`のように指定できます.
  • メソッドオーバーロード


    メソッドを追加しすぎる従来のクラス
    メソッドを呼び出すと、コンパイラはjavaと同様のメソッドを選択します.このメソッドのパラメータタイプは、リロードメソッドに一致します.
    class Rational(n: Int, d: Int){
    
        require(d!=0)
        private val g = gcd(n.abs, d,abs)       // 비공개 변수
        val numer : Int = n / g
        val denom: Int = denom / g
    
    
        def this(n: Int) = this(n, 1)       // 보조 생성자
    
        override def toString = numer + "/" + denom
    
        def + (that: Rationa): Rational = 
        new Rational(numer * that.denom + that.numer * denom, denom * that.denom)
    
        def + ( : Int): Rational =
        new Rational(numer + i * denom, denom)
        
        def - (that: Rational): Rational =
         new Rational(numer * that.denom - that.numer * denom, denom * that.denom)
    
        def - ( : Int): Rational =
        new Rational(numer - i * denom, denom)
    
        def * (that: Rational): Rational =
        new Rational(numer * that.numer, denom * that.denom)
    
        def * ( : Int): Rational =
        new Rat onal(numer * i, denom)
    
        def / (that: Rational): Rational =
        new Rational(numer * that.denom, denom * that.numer)
        
        def / (i: Int): Rational =
        new Rational(numer, denom * i)
        override def toString = numer + "/" + denom
    
        private def gcd(a: Int, b:Int): Int =       // 비공개 메소드
            if(b == 0) a else gcd(b, a%b)
    }
    

    暗黙型変換

    val x = new Rational(2, 3)
    x * 2   // Int를 인자로 받는 * 메소드를 정의하였기 때문에 사용가능
    2 * x   // Error 발생
  • 2 xは`2です.(x)`と同様であるため、最終的にIntメソッドを呼び出すと問題が発生する
  • スカラは、必要に応じて暗黙的なタイプ変換を指定して問題を解決することができる
  • .
    // int를 Rational로 변환하는 메소드 정의
    // implicit 수식자는 컴파일러에서 몇몇 상황에 해당 메소드를 활용해 변환을 수행하라고 알려줌
    implicit def inToRational(x: Int) = new Rational(x)
    
    val r = new Rational(2,3)
    println(2 * r)
    ===========================================
    4/3

  • 暗黙型変換を有効にするには、対応するスコプアンネ変換方法が必要です.

  • 暗黙型変換メソッドが従来のクラスで定義されている場合、変換は存在しません.なぜなら、メソッドを呼び出すscopeは存在しないからです.