sid-coll-2a

4 Scala コレクションフレームワーク

(The Scala Collections Framework)

前章では、異なる複数の種類のコレクション上に一般的に存在する、多数のコレクション操作とさらに多くのコレクション実装を列挙しました。 すべてのコレクション型に対してあらゆるコレクション操作を新たに実装することは、過度のコード重複を招きます。 そのようなコード重複は、コレクションライブラリの一部の操作を追加・修正し、他の部分でそうしなければ、時間と共に矛盾を生みます。 新しいコレクションフレームワークの重要な設計目標は、いかなる重複も避け、1 つの場所ですべての操作を定義することでした。 その設計手法は、コレクションテンプレート中でほとんどの操作を実装し、それを個々の基底クラスと実装から自由自在に継承することでした。これらテンプレートクラスは、パッケージ scala.collection.generic ですべて定義されています。 この章では、最も重要なクラスとそれらがサポートする構築原理を説明します。


4.1 ビルダー (Builders)


ほとんどのコレクション操作は、トラバーサルとビルダーによって実装されています。 トラバーサル(渡り歩き)は Traversable の foreach メソッドによって処理され、新しいコレクションの構築はクラス Builder のインスタンスによって処理されます。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       package scala.collection.generic
       class Builder[-Elem, +To] {
           def +=(elem: Elem): this.type
           def result(): To
           def clear()
           def mapResult(f: To => NewTo): Builder[Elem, NewTo] = ...
       }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
図 12 : Builder クラスの概観

図 12 はこのクラスの少し単純化した概要を示しています。

ビルダー b に要素 x を b += x として追加できます。 一度に 1 つ以上の要素を追加する構文もあります。たとえば b += (x, y) です。b ++= xs は、バッファに対してすべて機能します(実際、バッファはビルダーのリッチバージョンです)。 result() メソッドは、ビルダーからのコレクションを返します。 ビルダーの状態は、その結果をとった後は未定義です。しかし clear() を使って新たな空状態へリセットできます。 ビルダーは、要素型 Elem と、それらが返すコレクションの型 To の両方についてジェネリックです。

ビルダーは、コレクションの要素を集めるために他のビルダーを参照できますが、次に他のビルダーの結果を変換したいことがよくあります。たとえば、異なる型にするといったことです。 この仕事は、クラス Builder 中のメソッド mapResult によって簡単にできます。 たとえば、ArrayBuffer コレクションのビルダー bldr を仮定すると、次のように、それを Arrays のビルダーに変えることができます。:

    bldr mapResult (_.toArray)


4.2 共通操作のくくり出し (Factoring common operations)


コレクションライブラリ再設計の主な設計目標は、自然な型と同時に、最大限の共有にありました。 たとえば、filter 操作は、すべてのコレクション型上で、同じコレクション型のインスタンスをもたらすべきです。aList filter p はリストを、aMap filter p はマップを ...等々もたらすべきです。 これは、いわゆる実装トレイト中の、コレクション上のジェネリックなビルダーとトラバーサルによって達成されています。 Traversable あるいは Vector のようなコレクションクラスは、実装トレイトからそれらのすべての具象メソッド実装を継承します。これらのトレイトは末尾に Like の名前がついています。 たとえば、VectorLike は Vector の実装トレイト、TraversableLike は Traversable の実装トレイトです。 標準的なコレクションは型パラメータを 1 つとりますが、実装トレイトは 2 つとります。 それらは、コレクションの要素型だけではなく、コレクションの表現型についてもパラメータ化しています。 たとえば、次はトレイト TraversableLike のヘッダーです:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       trait TraversableLike[+Elem, +Repr] { ... }
   
       package scala.collection
       class TraversableLike[+Elem, +Repr] {
           def newBuilder: Builder[Elem, Repr] // 延期(継承先で実装)
           def foreach[U](f: Elem => U)        // 延期(継承先で実装)
           ...
           def filter(p: Elem => Boolean): Repr = {
               val b = newBuilder
               foreach { elem => if (p(elem)) b += elem }
               b.result
           }
       }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
図 13 : TraversableLike における filter 実装

型パラメータ Elem は、トラバース可能な要素型を表し、型パラメータ Repr はその表現を表します。 Repr には制限がありません。 特に、Repr はそれ自身 Traversable のサブ型でない型にインスタンス化できます。 このようにして、String や Array のようなコレクション階層外のクラスもまた、コレクション実装トレイトで定義されたすべての操作を使用できます。

filter を例にとると、この操作はトレイト TraversableLike 中のすべてのコレクションクラスに対して、同じ方法で定義されています。 図 13 に関連するコードの概観が示されています。 そのトレイトは 2 つの抽象メソッド newBuilder と foreach を宣言しています。 それらは具象コレクションクラス中で実装されます。 filter 操作はこれらのメソッドを使って、すべてのコレクションに対して同じ方法で実装されます。

コレクション上の map 操作はより複雑です。: たとえば、f が String から Int への関数で、xs が List[String] とすると、xs map f は List[Int] を与えるべきです。 同じく、もし ys が Array[String]なら、ys map f は Array[Int] を与えるべきです。 問題は、リストと配列中で map メソッドの定義を重複させない方法です。 図 13 に示した newBuilder/foreach フレームワークは、これには十分ではありません。 なぜなら、それは、同じコレクション型の新しいインスタンス生成ができるだけであり、他方、map は、異なる要素型が可能な、同じコレクション型のコンストラクタのインスタンスを必要とするからです。

最初私達は、この解決のキーは、高階型(higher-kinded types)にあると考えました。 たとえば、map は、コレクション C[A] 上の操作時、A から B への関数をとり、C[B] をもたらします。 高階型により、型コンストラクタ C にわたる抽象化ができ、すべてのコレクションクラス C に対してただ一つの map 実装を与えることを想像できます。 しかし高階型が、コレクション型のすべての継承に対して同じパラメータ形を要求するという点で、あまりにも融通が利かないことが分かりました。 これは制限が大きすぎます。 たとえば、String(あるいはむしろ、その背景クラス RichString)は、Chars のシーケンスと見れますが、しかしそれでもジェネリックなコレクション型ではありません。にもかかわらず、文字から RitchSting 上の文字マップへのマッピングは、次の Scala REPL での実行のように、再び RichString をもたらすべきです。

    scala> "abc" map (x => (x + 1).toChar)
    res1: scala.runtime.RichString = bcd

しかしもし、Char から Int への関数を文字列に適用するとどうなるでしょう? その場合、結果として文字列を作り出すことができず、代わりに Int 要素の何らかのシーケンスとならざる得ません。 実際、次のようになります。

    "abc" map (x => (x + 1))
    res2: scala.collection.immutable.Vector[Int] = Vector(98, 99, 100)

ですから、map は、渡された関数引数の結果型が何であるかに応じて、異なる型をもたらすことが分かります! 単純な高階型を使って実現することは不可能です。

String の問題は他と切り離されたケースではありません。 次は、マップ上の関数を map する、REPL での 2 つの例です。:

 scala> Map("a" -> 1, "b" -> 2) map { case (x, y) => (y, x) }
 res3: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> a, 2 -> b)

 scala> Map("a" -> 1, "b" -> 2) map { case (x, y) => y }
 res4: scala.collection.immutable.Iterable[Int] = List(1, 2)

最初の関数は、キー/値ペアの 2 つの引数を交換します。 この関数のマッピング結果は再びマップですが、しかし違う方向で入っています。 実際、逆にできるなら、最初の関数は元のマップの逆をもたらします。 2 つめの関数は、キー/値ペアを整数、すなわち、その値成分にマップします。 そのような場合、我々は処理結果から Map を形成できませんが、しかしまだ Map の基底トレイトである Iterable を形成できます。 同じ種類のコレクションを返すことができるように map を制限すればよい、と思うかもしれません。 たとえば、map は、文字列上では文字から文字への関数だけを受け入れ、マップ上ではペアからペアへの関数だけ受け入れるということです。 そのような制限は Liskov 置換原則に違反します。 Map は Iterable ですから、Iterable 上で正しいすべての動作は、Map 上でも同じく正しくなければなりません。

我々はオーバーロードを用いてこの問題を解決します。 (十分な柔軟性をもたない)Java から継承したオーバーロードの単純な形ではなく、暗黙のパラメータによって提供されるオーバーロードのより体系的な形を用います(*1)。

(*1) Haskell において、型クラス(type classes)が似たような役割を果たします。

図 14 はトレイト TraversableLike におけるの map の実装を示しています。 これは、図 13 で示された filter 実装に大変よく似ています。 主な違いは、filter が、クラス TraversableLike 内で抽象 newBuilder メソッドを使ったのに対し、map は、暗黙のパラメータとして追加で渡されるビルダーファクトリを使うことです。

図 15 はトレイト BuilderFactory の定義を示します。 ビルダーファクトリは 3 つの型パラメータを持っています。: Elem は構築すべきコレクションの要素型を示し、To は構築すべきコレクションの型を、From はこのビルダーファクトリを適用する型を示します。 適切な暗黙のビルダーファクトリを定義することで、必要とされる正しい型付けをあつらえることができます。 クラス RichString を例にとると、そのコンパニオンオブジェクトは型 BuilderFactory[Char, RichString, RichString] のビルダーファクトリを含みます。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   def map[B, That](p: Elem => B)
           (implicit bf: BuilderFactory[B, That, This]): That = {
     val b = bf(this)
     for (x <- this) b += f(x)
     b.result
   }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
図 14 : TraversableLike 中の map 実装


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   package scala.collection.generic
   trait BuilderFactory[-Elem, +To, -From] {
       def apply(from: From): Builder[Elem, To]  // 新ビルダーの生成
   }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
図 15 : BuilderFactory トレイト

これは、RichString上の操作は、構築するコレクションの型が Char なら、他の RichString を構築できることを意味します。 もしこれが当てはまらない場合は、いつでも別の暗黙のビルダーファクトリに戻ることができ、今度は Iterable のコンパニオンオブジェクト内で実装できます。 次はこの、より汎用的なビルダーファクトリの型です。

   BuilderFactory[A, Iterable[A], Iterable[_]],

ここで A は ジェネリックな型パラメータです。 これは、(存在型 Iterable[_] によって表わされる)任意の Iterable 上の操作のときは、要素型 A が何であっても、再び Iterable を作れることを意味します。 ビルダーファクトリのこれら 2 つの実装を与えられ、適切かつ最も特化したものを選ぶ、暗黙の解決 についての Scala 規則を当てにできます。

このように暗黙の解決は、map のようなトリッキーなコレクション操作に正しい静的な型を提供します。 しかしダイナミックな型はどうでしょうか? 特に、たとえば、その静的な型が Iterable であるリスト値があって、ある関数をその値上でマップするとします。:

   scala> val xs: Iterable[Int] = List(1, 2, 3)
   xs: Iterable[Int] = List(1, 2, 3)
   
   scala> xs map (x => x * x)
   res6: Iterable[Int] = List(1, 4, 9)


上記 res6 の静的な型は、期待どおり Iterable です。 しかし、そのダイナミックな型は、まだ List です! (そしてそうであるべきです)。 この振る舞いは、もう 1 つの回り道によって達成されます。 BuilderFactory 中の apply メソッド は、引数として元のコレクションを渡されます。 ジェネリックな traversables 用のほとんどのビルダーファクトリは(実際、leaf クラスのビルダーファクトリ以外すべて)、呼び出しをコレクションのメソッド genericBuilder へ転送します。genericBuilderは今度は、それが定義されているコレクションに属するビルダーを呼び出します。 このように、静的な暗黙の解決を map の型の制約を解決するのに使い、それらの制約に対応する最適なダイナミック型を選ぶのに仮想ディスパッチを使います。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   package scala.collection
   package immutable
   import generic.GenericTraversableTemplate
   trait Vector[+A] extends immutable.Seq[A]
                           with scala.collection.Vector[A]
                           with VectorLike[A, Vector[A]]
                           with GenericTraversableTemplate[A, Vector] {
       override def companion: Companion[Vector]  = Vector
   }
   
   object Vector extends SeqFactory[Vector] {
       class Impl[A](buf: ArrayBuffer[A]) extends Vector[A] {
           def length = buf.length
           def apply(idx: Int) = buf.apply(idx)
       }
       implicit def builderFactory[A]: BuilderFactory[A, Vector[A], Vector[_]] =
           new VirtualBuilderFactory[A]
       def newBuilder[A]: Builder[A, Vector[A]] =
           new ArrayBuffer[A] mapResult (buf => new Impl(buf))
   }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
図 16 : コレクション実装のサンプル


4.3 新コレクションとの統合 (Integrating new collections)


すべての事前定義された操作を正しい型で定義して利益を得られるように、新しいコレクションクラスを統合したいなら、どうしたらよいでしょうか? 例として、図 16 は、イミュータブルベクトル(実際のイミュータブルベクトルの実装はアルゴリズム的にもっと洗練されています!)のシンプルではあるが完全な実装を示します。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   trait GenericTraversableTemplate[+A, +CC[X] <: Traversable[X]] {
     def companion: Companion[CC]
     protected[this] def newBuilder: Builder[A, CC[A]] = companion.newBuilder[A]
     def genericBuilder[B]: Builder[B, CC[B]] = companion.newBuilder[B]
   }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
図 17 : トレイト GenericTraversableTemplate

Vector トレイトは他の 4 つのトレイトを継承します。それは immutable.Seq を継承します。なぜなら、すべてのベクトルがそれらの対応するシーケンスクラスを継承するからです。 それは、イミュータブルとミュータブルなベクトルの両方の基底トレイトである collection.Vector も継承します。 それは VectorLike トレイトからほとんどのメソッド実装を継承します。 他のトラバース可能なテンプレートと同様に VectorLike は、延期された newBuilder と genericBuilder メソッドを実装するときに、ビルダーの実装が与えられていることを必要とします。 これらのメソッドは、トレイト GenericTraversableTemplate で定義されていて、呼び出しを Vector のコンパニオンオブジェクトの newBuilder メソッドへ転送します。 図 17 はこのトレイトの定義を示しています。

このトレイトは、それを継承するトレイトやクラスが実装する必要のある、抽象メソッド companion を定義します。 このメソッドは、Vector のコンパニオンオブジェクトを参照するために、クラス Vector で実装されます。 GenericTraversableTemplate は次に、動物形の(訳注:裸の) newBuilder メソッドとジェネリックな genericBuilder メソッドを定義し、両方とも companion.newBuilder へ転送します。

GenericTraversableTemplate が高階型である -- 型パラメータとして型コンストラクタ CC をとる -- ことに注意してください。 Vector トレイト中で Vector 自身を使ってその型パラメータをインスタンス化します。

companion の定義は、トレイト Vector 中の唯一の定義です。 すべてのシーケンスと同様、ベクトルは 2 つの抽象メソッド apply と length を宣言しており、それらはサブクラス中で定義される必要があります。 他のすべての操作は、継承されるトレイトで既に定義されています。

さて今度は、Vector のコンパニオンオブジェクトの定義に取りかかりましょう。 その主な仕事は、抽象 Vector トレイトの標準的な実装を定義し、newBuilder メソッド中でこの実装のビルダーを提供することです。 標準的な実装はクラス Impl が提供します。 それは apply と lenght の呼び出しを、基盤となっている配列バッファに転送するだけです。 Vector.newBuilder は、(ビルダーの特殊化した類である)配列バッファを生成し、このバッファから得られる結果を Impl のインスタンスに変換します。 これは、GenericTraversableTemplate のインスタンスが提供する必要のある最小の機能です。

さらに便利にするために、Vector オブジェクトは、クラス SeqFactory を継承します。 これによりベクトルに対して図 11 中で記述したすべての生成メソッドが利用可能となります。 決めておくべきもう 1 つの重要なことは、map のような操作に、再び immutable.Vector を返させることです。4.2 節で説明したように、このために、あなたは暗黙のビルダーファクトリを必要とします。 Vector によって提供された builderFactory メソッドは VirtualBuilderFactory の新しいインスタンスを生成します。

このファクトリクラスは、基盤となっているコレクションの genericBuilder メソッドを呼び返すことで、4.2 節で記述したダイナミックな型適合を実装するビルダーを作り出します。 次は VirtualBuilderFactory の定義です。:

   class VirtualBuilderFactory[A] extends BuilderFactory[A, CC[A], CC[_]] {
       def apply(from: Coll) = from.genericBuilder[A]
   }

クラスは、Vector オブジェクトによって継承される、SeqFactory クラスの内部のクラスとして定義されています。 CC 型パラメータは、構築されるコレクションの高階型を参照します。

まとめると、もしあなたが新しいコレクションクラスをフレームワークに完全に統合したいなら、次の点に注意を払う必要があります。:

  1. コレクションがミュータブルであるか、あるいはイミュータブルであるか決めてください。
  2. コレクションのふさわしい基底クラスを選択してください。
  3. ほとんどののコレクション操作を実装するために、正しいテンプレート(実装)トレイトを継承してください。
  4. もし、独自のコレクション型のインスタンスを返す map や map に似た操作が欲しいなら、コンパニオンオブジェクト中で暗黙のビルダーファクトリを提供してください。
  5. もしコレクションに、map や map に似た操作に対するダイナミックな型適合を持たせたいなら、GenericTraversableTemplate も継承するか、あるいは等価の機能を実装してください。

最後の点は、サブクラス中で特化(specialized)される、抽象コレクションクラスについてのみ重要です。 もしコレクションクラスが最終の実装であることを求めるなら、VirtualBuilder クラスによって提供される仮想ディスパッチを通さないで、直接その newBuilder メソッドを実装します。

もし独自のコレクション型を返す map や filter のような大量の操作が必要なければ、もっと簡単なやり方もあります。 そのような場合、単に、Seq あるいは Map のような一般的なコレクションクラスを継承し、操作を追加実装すればよいのです。


5 新コレクションへの移行 (Migrating to the new collections)


新しいコレクションは、2.8 以前とは大きく異なります。 変更は新しいコレクションの実装者に大きな影響を与えますが、コレクションクラスのクライアントはそれほど影響を受けないでしょう。

2.8 以前のコレクションとの後方互換性を十分に確保するために 2 つの手段をとりました。: Scala パッケージオブジェクトと廃止メソッドです。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   package object scala {
       type Iterable[+A] = scala.collection.Iterable[A]
       val Iterable = scala.collection.Iterable
   
       /** @deprecated use Iterable instead */
       @deprecated type Collection[+A] = Iterable[A]
       /** @deprecated use Iterable instead */
       @deprecated val Collection = Iterable
   
       type Seq[+A] = scala.collection.Seq[A]
       val Seq = scala.collection.Seq
   
       type RandomAccessSeq[+A] = scala.collection.Vector[A]
       val RandomAccessSeq = scala.collection.Vector
   
       type Iterator[+A] = scala.collection.Iterator[A]
       val Iterator = scala.collection.Iterator
   
       type BufferedIterator[+A] = scala.collection.BufferedIterator[A]
   
       type List[+A] = scala.collection.immutable.List[A]
       val List = scala.collection.immutable.List
   
       val Nil = scala.collection.immutable.Nil
   
       type ::[A] = scala.collection.immutable.::[A]
       val :: = scala.collection.immutable.::
   
       type Stream[+A] = scala.collection.immutable.Stream[A]
       val Stream = scala.collection.immutable.Stream
   
       type StringBuilder = scala.collection.mutable.StringBuilder
       val StringBuilder = scala.collection.mutable.StringBuilder
   }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
図 18 : Scala パッケージオブジェクト


5.1 Scala パッケージオブジェクト (The Scala package_object)


2.8 以前のいくつかの重要なコレクションクラスは、現在、異なる名前で異なるパッケージに存在します。 移行を順調に進めるために、Scala 2.8 はパッケージオブジェクトを定義しています。それは、Scala パッケージ中に前にあった共通のコレクションクラスに対する別名を定義しています。

パッケージオブジェクトは Scala の新しい概念です。 これにより、特別なオブジェクトに、パッケージのメンバーとして扱われる定義を加えることができます。 たとえば、ファイル Scala\package.scala 内にあるパッケージオブジェクト scala は、パッケージ scala の追加メンバーを定義します。 これらのメンバーをパッケージオブジェクトの外では定義できないことに注意してください。なぜなら、型エイリアスと val 定義は、トップレベル定義としては許されていないからです。

図 18 は Scala パッケージオブジェクトの定義を示します。 中期的には、このオブジェクト中の少なくともいくつかのエイリアスは廃止されます。 特に、Collection はもう役割を果たしていません --- それは Iterable に組み込まれました。 同様に、RandomAccessSeq は collection.Vector に、RandomAccessSeq.Mutable は collection.mutable.Vector に組み込まれました。


5.2 廃止メソッド(Deprecated methods)


新しいコレクションフレームワーク中には取り除かれる予定のメソッドとクラスが、可能な場合にはまだありますが、今は、@deprecated とマークされています。 @deprecated シンボルにアクセスすると、Scala コンパイラは警告を発します。 ScalaDoc ドキュメンテーションは、たいていの場合、適切な置換えを提案します。

Scala の方針としては、少なくとも 1 つのメジャーバージョンについては @deprecated メソッドを保持します。 ですから、それらメソッドは Scala 2.8.x バージョンでは存在が保証されていますが、それ以上は保証はありません。


5.3 既知の移植問題 (Known migration issues)


次では、廃止メソッドをしのぐ、移植問題をいくつかリストしています。
  • scala.collection.jcl は、もう存在しません。その代わりに、Scala コレクションと java.util コレクション間の暗黙の型変換を含むオブジェクト scala.collection.JavaConversions があります。 jcl.WeakHashMap と jcl.WeakhashSet クラスは、直接に collection.mutable に統合されました。
  • Projections(射影、投射) はビューによって置き換えられ、機能のいくつかは異なります。
  • バッファ付きイテレータは、単純化されました。それらは今は、ただ一つの要素先読みだけを提供します。 BufferedIterator.Advanced クラスは削除されました。
  • カウンタ付きイテレータは削除されました。 それらはとにかく無くなりました。私はそれらがそんなに使われていなかったと思っています。
  • クラス Collection は無くなりました。size メソッドは、クラス Traversable 内で、今は具象です(これは、カウンタを使いつつコレクションを単純にトラバースします)。 このことは、size メソッドを定義するコレクション(たとえば、セット、マップ、シーケンス)のすべての実装において、定義中に override 修飾子がなければならないことを意味します。
  • クラス Iterable 中の elements メソッドは iterator とリネームされました。 elements は廃止メソッドとしてまだ利用可能ですので、廃止警告はでますが、コレクションクラスのクライアントは動作させることができます。しかし、コレクションクラスのあらゆる実装も、今は、elementes の代わりに iterator を実装する必要があります。 もし elements を実装するなら、override を加える必要があります。なぜなら elements は今は、iterator へ転送する具象メソッドだからです。
  • マップ中の keys とvalues メソッドは、今は、イテレータの代わりにセットを返します。イテレータを返すメソッドは、今は、keysIterator と valuesIterator と呼ばれます。
  • パッケージ scala.collection 中の マップとセットは、今は、追加と除去操作 +、- も定義していて、既存のコレクションを修正して生まれる新しいコレクションを返します。 これらは抽象であり、collecton.Map あるいは collection.Set を継承するクラス中で実装される必要があります。 以前は、これらの 2 つのクラスは update 操作を定義していませんでした。 これらの操作定義を容易にする、デフォルトの方法で抽象操作を実装するクラス collection.DefaultMap と collection.DefaultSet があります。 もしあなたのコレクションクラスが特定の update 操作に関係しないなら、単に、Map あるいは Set の代わりに collection.DefaultMap あるいは collection.DefaultSet を継承すればよいです。

タグ:

+ タグ編集
  • タグ:

このサイトはreCAPTCHAによって保護されており、Googleの プライバシーポリシー利用規約 が適用されます。

最終更新:2011年04月13日 20:55
ツールボックス

下から選んでください:

新しいページを作成する
ヘルプ / FAQ もご覧ください。