Scala伴生オブジェクトの実現原理

8868 ワード

前言:この面接の出镜率はとても高くて、私达はすべて使うことができますが、しかしあなたはscalaがどうしてこのように设计することを考えたことがありますか?有名なjava 23種類の設計モデルを考えたことがありますが、scalaはどのように設計して応用していますか?
本文は主に伴生類と伴生対象の実現方法を分析する.伴生オブジェクトとは,オブジェクトキーワードを用いて修飾されたScalaの一例オブジェクトでもある.また、classキーを使用して定義された同名クラスもあります.このクラスは、同じファイルに存在します.このクラスは、この単一オブジェクトの伴生クラスと呼ばれ、相対的に、この単一オブジェクトは伴生クラスの伴生オブジェクトと呼ばれます.
単一オブジェクトの例ですが、この単一オブジェクトの伴生クラスを定義するだけです.コードは次のとおりです.
 class Test{
      var field = "field"
      def doSomeThing = println("do something")
    }
     
    object  Test {
        val a = "a string";
        def printString = println(a)
    }

伴生クラスにはフィールドfieldとメソッドdoSomethingがあります.
このファイルをコンパイルし、同じように2つのclass、1つのTESTを生成します.ClassとTest$class . 前述したように、このTest$です.classはフィクションクラスと呼ばれています.次に、フィクションクラスを逆コンパイルして、伴生クラスが加わった後、コンパイルされたフィクションクラスが前のブログと同じかどうかを見てみましょう.次は逆コンパイルの結果です.(定数プールなどの冗長な情報は削除)
 public final class Test$
      SourceFile: "Test.scala"
        Scala: length = 0x0
      minor version: 0
      major version: 50
      flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
     
    {
      public static final Test$ MODULE$;
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
     
     
      private final java.lang.String a;
        flags: ACC_PRIVATE, ACC_FINAL
     
     
      public static {};
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=0, args_size=0
             0: new           #2                  // class Test$
             3: invokespecial #12                 // Method "":()V
             6: return
     
      public java.lang.String a();
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: getfield      #17                 // Field a:Ljava/lang/String;
             4: areturn
     
      public void printString();
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=1, args_size=1
             0: getstatic     #24                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
             3: aload_0
             4: invokevirtual #26                 // Method a:()Ljava/lang/String;
             7: invokevirtual #30                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
            10: return
     
      private Test$();
        flags: ACC_PRIVATE
        Code:
          stack=2, locals=1, args_size=1
             0: aload_0
             1: invokespecial #31                 // Method java/lang/Object."":()V
             4: aload_0
             5: putstatic     #33                 // Field MODULE$:LTest$;
             8: aload_0
             9: ldc           #35                 // String a string
            11: putfield      #17                 // Field a:Ljava/lang/String;
            14: return
    }

フィクションクラスには何の変化もなく、ソースコードの単一のオブジェクトのフィールドとメソッドは、フィクションクラスに対応するフィールドとメソッドがあることがわかります.また、単一のオブジェクトのフィールドに対応するメソッドが生成されます.説明のポイントは、この例には伴生クラスが追加されており、伴生クラスにもフィールドとメソッドがあるが、このフィールドとメソッドはフィクションクラスに対応して現れていないことである.すなわち,フィクションクラスの情報は単一のオブジェクトにのみ関係し,単一のオブジェクトの伴生クラスはフィクションクラスの内容に影響を及ぼさない.フィクションクラスの実装の詳細については、前のブログを参照してください.ここでは繰り返しません.
次は逆コンパイルclass . 逆コンパイルの結果は次のとおりです.
 public class Test
      SourceFile: "Test.scala"
      RuntimeVisibleAnnotations:
        0: #6(#7=s#8)
        ScalaSig: length = 0x3
         05 00 00
      minor version: 0
      major version: 50
      flags: ACC_PUBLIC, ACC_SUPER
     
    {
      private java.lang.String field;
        flags: ACC_PRIVATE
     
     
      public static void printString();
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=0, args_size=0
             0: getstatic     #16                 // Field Test$.MODULE$:LTest$;
             3: invokevirtual #18                 // Method Test$.printString:()V
             6: return
     
      public static java.lang.String a();
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=0, args_size=0
             0: getstatic     #16                 // Field Test$.MODULE$:LTest$;
             3: invokevirtual #22                 // Method Test$.a:()Ljava/lang/String;
             6: areturn
     
      public java.lang.String field();
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: getfield      #26                 // Field field:Ljava/lang/String;
             4: areturn
     
      public void field_$eq(java.lang.String);
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=2, args_size=2
             0: aload_0
             1: aload_1
             2: putfield      #26                 // Field field:Ljava/lang/String;
             5: return
     
      public void doSomeThing();
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=1, args_size=1
             0: getstatic     #37                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
             3: ldc           #39                 // String do something
             5: invokevirtual #43                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
             8: return
     
      public Test();
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=1, args_size=1
             0: aload_0
             1: invokespecial #46                 // Method java/lang/Object."":()V
             4: aload_0
             5: ldc           #47                 // String field
             7: putfield      #26                 // Field field:Ljava/lang/String;
            10: return
    }

単一のオブジェクトの各フィールドまたはメソッドは、Testクラスの静的同名メソッドに対応します.上記の逆コンパイルの結果から,これらの静的手法は依然として存在することが分かった.方法は次のとおりです.
 public static void printString();
 public static java.lang.String a();

これらの静的手法の挙動は,前回のブログで解析したものと同じである.ここでは二度と繰り返さない.
この2つの静的メソッドに加えて、Testクラスには静的ではなくメンバーメソッドである他のフィールドとメソッドが存在します.これらのフィールドとメソッドは次のとおりです.
private java.lang.String field;
public java.lang.String field();
public void field_$eq(java.lang.String);
public void doSomeThing();
public Test();

これらのフィールドとメソッドは、伴生クラスのフィールドとメソッドに対応します.フィールドfieldに関連メソッドpublic javaが追加されます.lang.String field();とpublic void field_$eq(java.lang.String); . フィールドに同じ名前のgetterメソッドとxxx_$を追加eqのようなsetterメソッドはscalacコンパイラのデフォルトの動作であり,この問題は以前のブログ学習Scala:Scalaのフィールドとメソッドで詳細に説明されており,ここでは繰り返さない.
まとめ:
1クラスで定義されたフィールドとメソッドで、クラスのメンバーフィールドとメンバーメソッドに対応します.2伴生オブジェクトで定義されたフィールドとメソッドは、同名クラスの静的メソッドに対応するため、Scalaのobjectキーワードは静的の別の表現であると考えられ、scalaはこれらの静的なものもオブジェクトにカプセル化しているだけである.3仮想クラスのメンバーフィールドとメソッドに対応する、オブジェクトで定義されたフィールドとメソッド.4同じ名前のクラスの静的メソッドで、単一の例の架空のクラスオブジェクトにアクセスし、関連する論理を架空のクラスのメンバーメソッドに呼び出します.フィクションクラスは単一の例であるため,伴生オブジェクトのフィールドが一意であることを保証できる.すなわち,架空クラスの一例性は,伴生オブジェクト(すなわちscalaにおけるobject修飾の一例オブジェクト)における情報の一意性を保証する.
次の検証を行います.
    object Main {       def main(args : Array[String]){         var a = Test.a;         var a1 = Test.a;         println("a eq a1 : "+ (a eq a1))       }     }
上記の例では、単一のオブジェクトのaプロパティに2回アクセスし、同じオブジェクトであるかどうかを比較し、出力情報は次のようになります.
a eq a1 : true
5伴生オブジェクトの論理は,いずれも架空クラスに移行して6伴生クラスの論理を処理し,同名クラスのメンバメソッドに移行する.7要注意,伴生类は単例ではありません!!!この伴生クラスに他の場所でアクセスできる限り、複数のオブジェクトを作成できます.次の検証を行います.
    object Main {       def main(args : Array[String]){         var a = new Test         var a1 = new Test         println("a eq a1 : "+ (a eq a1))       }     }
印刷結果:
a eq a1 : false
作成されたオブジェクトは同じオブジェクトではないので、Test伴生クラスは一例ではありません.
8どのようにしてScalaで単例モードを使用しますか?前述したように、単一のオブジェクトの属性は永遠に一意であるため、伴生クラスのすべての論理をすべて単一のオブジェクトに移動し、伴生クラスを除去し、この単一のオブジェクトを孤立オブジェクトにし、この孤立オブジェクトは天然に単一のオブジェクトである.本例のインスタンスコードを例にとると、伴生クラスと伴生オブジェクトを統合し、伴生クラスを除去し、孤立オブジェクトを以下のようにする.
   /*class Test{       var field = "field"      def doSomeThing = println("do something")     }     object  Test {         val a = "a string";         def printString = println(a)     }*/          object Test {       var field = "field"      def doSomeThing = println("do something")             val a = "a string";       def printString = println(a)     }
9もし伴生類が存在しなければならないならば、どのように伴生類が単例であることを保証しますか?伴生クラスのコンストラクタをプライベート化し、伴生オブジェクトに伴生クラスのオブジェクトを作成できます.このオブジェクトは一意です.コードは次のとおりです.
    class Test private {       var field = "field"      def doSomeThing = println("do something")     }           object Test {       val single = new Test       val a = "a string"      def printString = println(a)     }
次の検証を行います.
    object Main {       def main(args : Array[String]){         var a = Test.single         var a1 = Test.single;         println("a eq a1 : "+ (a eq a1))       }     }
印刷結果:a eq a 1:true
出力結果から分かるように、Testに2回アクセスする.singleが得たのは同じオブジェクトです.また、このオブジェクトは唯一であり、外部では伴生クラスのオブジェクトを作成することはできません.そのコンストラクタはプライベートなので、彼は単一の例です.