遅延初期化

コンピュータプログラミングにおいて遅延初期化とは、オブジェクトの生成、の計算、その他の高負荷な処理を、実際に必要になるまで遅延させる手法です。これは遅延評価の一種であり、特にオブジェクトやその他のリソースのインスタンス化を指します。

これは通常、キャッシュとして機能するプライベートメンバーが既に初期化されているかどうかを確認するアクセサメソッド(またはプロパティゲッター)を拡張することで実現されます。初期化されている場合は、すぐに返されます。初期化されていない場合は、新しいインスタンスが作成され、メンバー変数に格納され、最初に使用されるタイミングで呼び出し元に返されます。

オブジェクトにあまり使用されないプロパティがある場合、起動速度が向上する可能性があります。プログラムの平均パフォーマンスは、メモリ(条件変数用)と実行サイクル(条件変数のチェック用)の観点から若干低下する可能性がありますが、オブジェクトのインスタンス化の影響はシステムの起動フェーズに集中するのではなく、時間的に分散(「償却」)されるため、平均応答時間が大幅に向上します。

マルチスレッドコードでは、競合状態を防ぐために、遅延初期化されたオブジェクト/状態へのアクセスを同期する必要があります。

「怠け者の工場」

ソフトウェア設計パターンの観点から見ると、遅延初期化はファクトリーメソッドパターンと組み合わせて使用​​されることがよくあります。これは、以下の3つの考え方を組み合わせたものです。

  • ファクトリーメソッドを使用してクラスのインスタンスを作成する(ファクトリーメソッドパターン
  • インスタンスをマップに保存し、同じパラメータを持つインスタンスの各リクエストに同じインスタンスを返す(マルチトンパターン
  • 遅延初期化を使用して、オブジェクトが最初に要求されたときにインスタンス化する (遅延初期化パターン)

アクションスクリプト3

以下は、 ActionScriptで遅延初期化を実装したクラスの例です

パッケージexamples.lazyinstantiation { public class Fruit { private var _typeName : String ; private static var instancesByTypeName : Dictionary = new Dictionary ( ) ; public function Fruit ( typeName : String ) : void { this._typeName = typeName ; } public function get typeName ( ) : String { return _typeName ; } public static function getFruitByTypeName ( typeName : String ) : Fruit { return instancesByTypeName [ typeName ] ||= new Fruit ( typeName ); } public static function printCurrentTypes ( ) : void { for each ( var fruit : Fruit in instancesByTypeName ) { // 各値を反復処理しますtrace ( fruit.typeName ) ; } } } }                                        

基本的な使い方:

パッケージ{ import examples.lazyinstantiation ; public class Main { public function Main ( ) : void { Fruit.getFruitByTypeName ( "Banana" ) ; Fruit.printCurrentTypes ( ) ; Fruit.getFruitByTypeName ( " Apple " ) ; Fruit.printCurrentTypes ( ) ; Fruit.getFruitByTypeName ( " Banana " ) ; Fruit.printCurrentTypes ( ) ; } } }        

C

Cでは、遅延評価は通常、静的変数を使用して 1 つの関数または 1 つのソース ファイル内に実装されます

関数内:

#include <stddef.h> #include <stdlib.h> #include <stdio.h> #include <string.h>    typedef struct Fruit { char * name ; struct Fruit * next ; int number ; // その他のメンバー} Fruit ;            Fruit * getFruit ( char * name ) { static Fruit * fruitList ; static int seq ; Fruit * f = fruitList ;              while ( f ) { if ( ! strcmp ( name , f -> name )) { return f ; } f = f -> next ; }              if ( ! ( f = malloc ( sizeof ( Fruit )))) { NULLを返します; }        if ( ! ( f -> name = strdup ( name ))) { free ( f ); NULLを返す; }         f -> number = ++ seq ; f -> next = fruitList ; fruitList = f ; return f ; }          // サンプルコードint main ( int argc , char * argv []) { Fruit * f ;        if ( argc < 2 ) { fprintf ( stderr , "使用法: 果物 fruit-name [...] \n " ); return 1 ; }          for ( int i = 1 ; i < argc ; i ++ ) { if ( f = getFruit ( argv [ i ])) { printf ( "果物 %s: 数値 %d \n " , argv [ i ], f ->数値); } }                    0を返す; } 

代わりに 1 つのソース ファイルを使用すると、関係のない関数からは状態を隠したまま、複数の関数間で状態を共有できます。

Fruit.h :

#プラグマワンスtypedef struct Fruit { char * name ; struct Fruit * next ; int number ; // その他のメンバー} Fruit ;            Fruit * getFruit ( char * name ); void printFruitList ( FILE * file );    

フルーツ.c :

#include <stddef.h> #include <stdlib.h> #include <stdio.h> #include <string.h>    #include "Fruit.h" 静的Fruit * fruitList ;静的int seq ;    構造体Fruit * getFruit ( char * name ) { Fruit * f = fruitList ;         while ( f ) { if ( ! strcmp ( name , f -> name )) { return f ; } f = f -> next ; }              if ( ! ( f = malloc ( sizeof ( Fruit )))) { NULLを返します; }        if ( ! ( f -> name = strdup ( name ))) { free ( f ); NULLを返す; }         f -> number = ++ seq ; f -> next = fruitList ; fruitList = f ; return f ; }          void printFruitList ( FILE * file ) { Fruit * f = fruitList ; while ( f ) { fprintf ( file , "%4d %s \n " , f -> number , f -> name ); f = f -> next ; } }                  

メイン.c :

#include <stdlib.h> #include <stdio.h>  #include "Fruit.h" int main ( int argc , char * argv []) { Fruit * f ;        if ( argc < 2 ) { fprintf ( stderr , "使用法: 果物 fruit-name [...] \n " ); return 1 ; }          for ( int i = 1 ; i < argc ; i ++ ) { if ( f = getFruit ( argv [ i ])) { printf ( "果物 %s: 数値 %d \n " , argv [ i ], f ->数値); } }                    printf ( "次の果物が生成されました: \n " ); printFruitList ( stdout ); return 0 ; }   

C#

.NET Framework 4.0では、System.Lazy遅延読み込みに使用できるクラスがMicrosoftによって提供されています。以下は、クラスの遅延読み込みを実行するダミーコードです。Fruit

システムを使用します Lazy < Fruit > lazyFruit = new (); Fruit fruit = lazyFruit . Value ;      

以下はC#のダミーの例です

クラスFruit自体はここでは何もしません。クラス変数 は、インスタンス_typesDictionaryを格納するために使用される辞書/マップですFruittypeName

名前空間Wikipedia.Examples ; Systemを使用します。System.Collectionsを使用します。System.Collections.Generic使用します   クラスFruit { private string _typeName ; private static IDictionary < string , Fruit > _typesDictionary = new Dictionary < string , Fruit > ();              プライベートFruit (文字列typeName ) { this . _typeName = typeName ; }        パブリック静的フルーツGetFruitByTypeName (文字列) {フルーツフルーツ;        if ( ! _typesDictionary . TryGetValue ( type , out fruit )) { // 遅延初期化fruit = new Fruit ( type );          _typesDictionary . Add ( type , fruit ); }   フルーツを返す; }   public static void ShowAll () { if ( _typesDictionary.Count > 0 ) { Console.WriteLine ( "作成インスタンスの数 = {0}" , _typesDictionary.Count ) ; foreach ( KeyValuePair < string , Fr ​​uit > kvp in _typesDictionary ) { Console.WriteLine ( kvp.Key ) ; } Console.WriteLine ( ) ; } } public Fruit ( ) { //サンプルをコンパイルするために必要} }                               パブリッククラスProgram { static void Main ( string [] args ) { Fruit . GetFruitByTypeName ( "Banana" ); Fruit . ShowAll ();          Fruit.GetFruitByTypeName ( "Apple " ) ; Fruit.ShowAll ( ) ;  // "Banana" を持つ Fruit が最初に作成されたときの既存のインスタンスを返しますFruit . GetFruitByTypeName ( "Banana" ); Fruit . ShowAll ();    コンソール.ReadLine ( ); } } 

これは、型に列挙型を使用している点を除けば、Lazy Initializationデザインパターンのかなり単純な「空欄を埋める」例です。

名前空間Wikipedia.Examples ; Systemを使用します。System.Collections.Generic使用します  public class LazyFactoryObject { // 項目の内部コレクション// IDictionary はそれらが一意であることを確認しますprivate IDictionary < LazyObjectSize , LazyObject > _LazyObjectList = new Dictionary < LazyObjectSize , LazyObject > ();             // 必要なサイズの名前を渡すための列挙型// 文字列を渡すことを回避し、先行する LazyObject の一部になりますpublic enum LazyObjectSize { None Small Big Bigger Huge }            // 構築されるオブジェクトの標準タイプpublic struct LazyObject { public LazyObjectSize Size ; public IList < int > Result ; }            // サイズを取得して「高価な」リストを作成しますprivate IList < int > Result ( LazyObjectSize size ) { IList < int > result = null ;          switch ( size ) { case LazyObjectSize.Small : result = CreateSomeExpensiveList ( 1 , 100 ) ; break ; case LazyObjectSize.Big : result = CreateSomeExpensiveList ( 1 , 1000 ) ; break ; case LazyObjectSize.Bigger : result = CreateSomeExpensiveList ( 1 , 10000 ) ; break ; case LazyObjectSize.Huge : result = CreateSomeExpensiveList ( 1 , 100000 ) ; break ; case LazyObjectSize.None : result = null ; break ; default : result = null ; break ; }                                           結果を返す; }   // 作成コストの高いアイテムではありませんが、要点は理解できます// 必要なときまでコストの高いオブジェクトの作成を遅らせますprivate IList < int > CreateSomeExpensiveList ( int start , int end ) { IList < int > result = new List < int > ();              for ( int counter = 0 ; counter < ( end - start ); counter ++ ) { result . Add ( start + counter ); }                結果を返す; }   public LazyFactoryObject () { // 空のコンストラクタ}     public LazyObject GetLazyFactoryObject ( LazyObjectSize size ) { // はい、これは文盲で不正確であることは承知していますLazyObject noGoodSomeOne ;        // out 経由でリストから LazyObjectSize を取得し、そうでない場合は作成してリストに追加します。if ( ! _LazyObjectList . TryGetValue ( size , out noGoodSomeOne )) { noGoodSomeOne = new LazyObject (); noGoodSomeOne . Size = size ; noGoodSomeOne . Result = this . Result ( size );                _LazyObjectList . Add ( size noGoodSomeOne ); }   noGoodSomeOneを返します; } }  

C++

この例はC++です

stdをインポートします テンプレート< typename K typename V >、HashMap = std :: unordered_map < K V >を使用しますテンプレート< typename T >、SharedPtr = std :: shared_ptr < T >を使用します。String = std :: string使用します                クラスFruit { private : static HashMap < String , SharedPtr < Fruit >> types = {};文字列型;           // 注:コンストラクターはプライベートなので、static getFruit を使用する必要があります。explicit Fruit ( const String & type ) : type { type } {} public : // 遅延ファクトリーメソッド。特定の型に関連付けられた Fruit インスタンスを取得します。//必要に応じて新しいインスタンスを作成します。static SharedPtr < Fruit > getFruit ( const String & type ) { auto [ it , inserted ] = types . emplace ( type , nullptr ); if ( inserted ) { it - > second = std :: make_shared < Fruit > ( type ); } return it -> second ; }                                // パターンの動作を確認するための例として。static void printCurrentTypes () { std :: println ( "Number of instances made = {}" , types . size ()); for ( const auto & [ type , fruit ] : types ) { std :: println ({}, type ); } std :: println (); } };                   int main ( int argc , char * argv []) { Fruit :: getFruit ( "Banana" ); Fruit :: printCurrentTypes ();        フルーツ:: getFruit ( "リンゴ" );フルーツ:: printCurrentTypes ();  // "Banana" を持つFruitが初めて作成されたときの既存のインスタンスを返します。Fruit :: getFruit ( "Banana" ); Fruit :: printCurrentTypes (); }   // 出力: // // 作成されたインスタンスの数 = 1 // バナナ// // 作成されたインスタンスの数 = 2 // リンゴ// バナナ// // 作成されたインスタンスの数 = 2 // リンゴ// バナナ//

結晶

クラスFruitプライベートゲッタータイプ: String @@types = {} of String => Fruit              def初期化( @type )終了   def self.get_fruit_by_type ( type : String ) @@ types [ type ] || = Fruit.new ( type )終了        def self . show_all puts "作成されたインスタンスの数: #{ @@types . size } " @@types . each do | type , fruit | puts " #{ type } " end puts end             def self . size @@types . size終了終了   Fruit . get_fruit_by_type ( "バナナ" ) Fruit . show_allフルーツ.get_fruit_by_type ( "リンゴ" )フルーツ.show_allFruit . get_fruit_by_type ( "バナナ" ) Fruit . show_all

出力:

作成されたインスタンスの数: 1バナナ作成されたインスタンスの数: 2バナナりんご作成されたインスタンスの数: 2バナナりんご

ハックス

この例はHaxeにあります。[1]

クラスFruit {プライベート静的var _instances = new Map < String Fruit >();           パブリック変数(デフォルトnull ):文字列;    パブリック関数new ( name : String ) { this . name = name ; }        パブリック静的関数getFruitByName ( name : String ): Fruit { if ( ! _instances . exists ( name )) { _instances . set ( name , new Fruit ( name )); } return _instances . get ( name ); }               パブリック静的関数printAllTypes () {トレース([ for ( key in _instances . keys ()) key ]); } }         

使用法

class Test { public static function main ( ) { var banana = Fruit.getFruitByName ( " Banana" ); var apple = Fruit.getFruitByName ( "Apple" ); var banana2 = Fruit.getFruitByName ( "Banana" ); trace ( banana == banana2 ) ; // true. 同じバナナFruit.printAllTypes ( ) ; // ["Banana","Apple " ] } }                             

ジャワ

この例はJavaです。

パッケージorg.wikipedia.examples ; java.util.HashMapをインポートします。java.util.Mapインポートします  enum FruitType { NONE APPLE BANANA }     class  Fruit { private static Map < FruitType , Fruit > types = new HashMap <> (); /**  * プライベートコンストラクタを使用してファクトリーメソッドの使用を強制します。 * @param type  */ private Fruit ( FruitType type ) {                } /**  * 遅延ファクトリメソッド。特定のタイプに関連付けられた Fruit インスタンスを取得します 。必要に応じて新しいインスタンスを生成します。 * @param type 許可されたフルーツタイプ (例: APPLE)  * @return そのタイプに関連付けられた Fruit インスタンス。 */ public static Fruit getFruitByTypeName ( FruitType type ) { Fruit fruit ; // これには同時実行の問題があります。ここでは、types への読み取りが同期されていないため、 // types.putとtypes.containsKey が同時に呼び出される可能性があります。//データが破損しても驚かないでください。if ( ! types.containsKey ( type )) { // 遅延初期化fruit = new Fruit ( type ); types.put ( type , fruit ); } else { // OK、現在は利用可能ですfruit = types.get ( type ); } return fruit ; } /** *遅延ファクトリメソッド。特定のタイプに関連付けられ Fruit インスタンスを取得します 。必要に応じて新しいインスタンスを生成します。 高度な並行環境で使用するために、ダブルチェック ロック パターンを使用します。  * @param type 許可されているフルーツの種類 (例: APPLE )  * @return その種類に関連付けられている Fruit インスタンス。 */ public static Fruit getFruitByTypeNameHighConcurrentVersion ( FruitType type ) { if ( ! types . containsKey ( type )) { synchronized ( types ) { // ロックを取得した後、インスタンスが別のスレッドによって作成されていないことを再度確認します。 if ( ! types . containsKey ( type )) { // 遅延初期化types . put ( type , new Fruit ( type )); } } } return types . get                                                                ( type ); } /**  * 入力されたすべての果物を表示します。 */ public static void showAll () { if ( types . size () > 0 ) { System . out . printf ( "Number of instances made = %d%n" , types . size ()); for ( Map . Entry < FruitType , Fruit > entry : types . entrySet ()) { String fruit = entry . getKey (). toString (); fruit = Character . toUpperCase ( fruit . charAt ( 0 )) + fruit . substring ( 1 ); System . out . println ( fruit ); } System . out . println (); } } }                                       パブリッククラスプログラム{    /**  * @param args  */ public static void main ( String [] args ) {フルーツ. getFruitByTypeName ( FruitType . BANANA );フルーツすべて表示();フルーツgetFruitByTypeName ( FruitType . APPLE );フルーツすべて表示();フルーツgetFruitByTypeName ( FruitType . BANANA );フルーツすべて表示(); } }             

出力

作成されたインスタンスの数 = 1バナナ作成されたインスタンスの数 = 2バナナりんご作成されたインスタンスの数 = 2バナナりんご

JavaScript

この例はJavaScriptです。

var Fruit = ( function () { var types = {}; function Fruit () {};            // オブジェクト内の独自のプロパティを数えるfunction count ( obj ) { return Object . keys ( obj ). length ; }       var _static = { getFruit : function ( type ) { if ( typeof types [ type ] == 'undefined' ) { types [ type ] = new Fruit ; } return types [ type ]; }, printCurrentTypes : function () { console . log ( '作成されたインスタンスの数: ' + count ( types )); for ( var type in types ) { console . log ( type ); } } };                                      _staticを返します })();フルーツgetFruit ( 'Apple' );フルーツprintCurrentTypes ();フルーツgetFruit ( 'バナナ' );フルーツprintCurrentTypes ();フルーツgetFruit ( 'Apple' );フルーツprintCurrentTypes ();

出力

作成されたインスタンスの数: 1りんご作成されたインスタンスの数: 2りんごバナナ作成されたインスタンスの数: 2りんごバナナ

PHP

以下はPHP 7.4での遅延初期化の例です。

<?phpヘッダー( 'Content-Type: text/plain; charset=utf-8' );クラス Fruit { プライベート 文字列 $type ; プライベート 静的 配列 $types  =  array (); プライベート 関数 __construct (文字列 $type )  {  $this -> type  =  $type ;  } public  static  function  getFruit ( string  $type ) :  Fruit  {  // ここで遅延初期化が行われます if  ( ! isset ( self :: $types [ $type ]))  {  self :: $types [ $type ]  =  new  Fruit ( $type );  } 戻り :: $types [ $type ];  } public  static  function  printCurrentTypes () :  void  {  echo  '作成されたインスタンスの数:'  .  count ( self :: $types )  .  " \n " ;  foreach  ( array_keys ( self :: $types )  as  $key )  {  echo  " $key \n " ;  }  echo  " \n " ;  } }フルーツ:: getFruit ( 'Apple' );フルーツ:: printCurrentTypes ();フルーツ:: getFruit ( 'バナナ' );フルーツ:: printCurrentTypes ();フルーツ:: getFruit ( 'Apple' );フルーツ:: printCurrentTypes ();/*出力:作成されたインスタンスの数: 1 Apple作成されたインスタンスの数: 2アップルバナナ作成されたインスタンスの数: 2アップルバナナ*/

パイソン

この例はPythonです。

クラスFruit : def __init__ ( self , item : str ) - > None : self.item = item           クラスFruitCollection : def __init__ ( self ) -> None : self . items : dict [ str , Fruit ] = {}           def get_fruit ( self , item : str ) -> Fruit : if itemself .itemsない場合: self .items [ item ] = Fruit ( item )               自分自身返す。items [ item ]if  __name__  ==  "__main__" :  fruits :  FruitCollection  =  FruitCollection ()  print ( fruits . get_fruit ( "Apple" ))  print ( fruits . get_fruit ( "Lime" ))

ルビー

これはRubyで書かれた例で、Google などのリモートサービスからの認証トークンを遅延初期化するものです。@auth_token をキャッシュする方法もメモ化の例です。

'net/http'クラスBlogger が必要ですdef auth_token @auth_token ||= ( res = Net :: HTTP . post_form ( uri , params )) && get_token_from_http_response ( res ) end              # get_token_from_http_response、uri、paramsはクラスの最後で定義されますb = Blogger.new b.instance_variable_get ( : @ auth_token ) # nil を返すb.auth_token # tokenを返すb.instance_variable_get ( : @ auth_token ) # token を返す     

さび

錆びはありますstd::cell::LazyCell[2]

std :: cell :: LazyCellを使用します 遅延: LazyCell = LazyCell :: new ( || 42 ) ;     

スカラ

Scalaには遅延変数初期化のサポートが組み込まれています。[3]

 scala > val x = { println ( "Hello" ); 99 }        Hello x : Int = 99 scala > lazy val y = { println ( "Hello!!" ); 31 } y : Int = < lazy > scala > y Hello !! res2 : Int = 31 scala > y res3 : Int = 31                               

雑談

この例はSmalltalkで、遅延初期化を使用して変数の値を返す 一般的なアクセサー メソッドです。

 高さ ^高さ ifNil: [高さ :=  2.0 ] 

「遅延のない」代替案は、オブジェクトの作成時に実行される初期化メソッドを使用し、その後、より単純なアクセサー メソッドを使用して値を取得することです。

  高さ  2.0に初期化する 高さ ^高さ

遅延初期化は非オブジェクト指向言語でも使用できることに注意してください

理論計算機科学

理論計算機科学の分野では遅延初期化[4]遅延配列とも呼ばれる)は、初期化する必要のないメモリで機能するデータ構造を設計する手法である。具体的には、 n 個の初期化されていないメモリセル(番号1からn )のテーブルTにアクセスでき、この配列のm個のセルを割り当てると仮定する。たとえば、すべてのk iが異なるペア(k 1v 1)、...、(k mv m)にT [ k i ] := v i を割り当てるとします。遅延初期化手法を使用すると、最初にすべての配列セルを初期化するために O( m + n )操作を費やすのではなく、わずか O( m ) 操作でこれを実行できます。この手法は、ペア(k iv i)を任意の順序で格納するテーブルVを割り当て、セルT [ k i ] 内の各iに対して、キーk iが格納されているV内の位置を書き込み、 Tの他のセルを初期化しないままにするだけです。これは、次のような方法でクエリを処理するために使用できます。あるkについてセルT [ k ] を参照する場合、 T [ k ] が範囲 {1, ..., m } 内にあるかどうかを確認します。範囲内にない場合、T [ k ] は初期化されていません。範囲内にない場合、 V [ T [ k ]]をチェックし、このペアの最初の要素がkと等しいことを確認します。範囲内にない場合、T [ k ] は初期化されていません(偶然に範囲 {1, ..., m } 内に収まっただけです)。そうでない場合、 T [ k ] は確かに初期化されたセルの1つであり、対応する値はペアの2番目の要素であることがわかります。

参照

参考文献

  1. ^ 「遅延初期化 - デザインパターン - Haxeプログラミング言語クックブック」2018年1月11日. 2018年11月9日閲覧
  2. ^ “LazyCell in std::cell - Rust”. doc.rust-lang.org . 2025年1月18日閲覧
  3. ^ ポラック、デイビッド (2009年5月25日). Scala入門. Apress. ISBN 9781430219897
  4. ^ Moret, BME; Shapiro, HD (1991). PからNPへのアルゴリズム 第1巻:設計と効率性. Benjamin/Cummings Publishing Company. pp.  191– 192. ISBN 0-8053-8008-6
  • Philip Bishop と Nigel Warren による記事「Java Tip 67: Lazy インスタンス化 - パフォーマンスとリソース使用量のバランス」
  • Javaコードの例
  • 遅延初期化を使用してリソースを節約する
  • ポートランドパターンリポジトリからの説明
  • アプリケーションサーバーサービスの遅延初期化
  • JavaScriptにおける遅延継承
  • C# における遅延継承
「https://en.wikipedia.org/w/index.php?title=Lazy_initialization&oldid=1320827387」から取得