私はこのソフトボールを公園の外で打ちたいと思っている人に提供しようと思いました。ジェネリックとは何ですか、ジェネリックの利点は何ですか、なぜ、どこで、どのように使用する必要がありますか? かなり基本的なものにしてください。ありがとう。
29 に答える
- コードを記述したり、タイプ セーフなライブラリ メソッドを使用したりできます。つまり、List<string> は文字列のリストであることが保証されます。
- ジェネリックが使用された結果、コンパイラは型安全性のためにコードのコンパイル時チェックを実行できます。つまり、文字列のリストに int を入れようとしていますか? ArrayList を使用すると、実行時エラーの透過性が低下します。
- ボックス化/ボックス化解除 (.net は値型を参照型に、またはその逆に変換する必要があります) またはオブジェクトから必要な参照型へのキャストを回避するため、オブジェクトを使用するよりも高速です。
- 同じ基本動作を持つ多くの型に適用可能なコードを記述できます。つまり、Dictionary<string, int> は Dictionary<DateTime, double> と同じ基本コードを使用します。ジェネリックを使用することで、フレームワーク チームは 1 つのコードを記述するだけで、前述の利点と両方の結果を得ることができました。
私は自分自身を繰り返すのが本当に嫌いです。必要以上に同じことをタイプするのは嫌いです。わずかな違いで物事を何度も言い直すのは好きではありません。
作成する代わりに:
class MyObjectList {
MyObject get(int index) {...}
}
class MyOtherObjectList {
MyOtherObject get(int index) {...}
}
class AnotherObjectList {
AnotherObject get(int index) {...}
}
再利用可能なクラスを 1 つ作成できます... (何らかの理由で生のコレクションを使用したくない場合)
class MyList<T> {
T get(int index) { ... }
}
効率が 3 倍になり、維持する必要があるコピーは 1 つだけです。維持するコードを減らしたくないのはなぜですか?
Callable<T>
これは、 aや aなどReference<T>
、他のクラスと対話する必要がある非コレクション クラスにも当てはまります。タイプ セーフなバージョンを作成するために、 および および関連する他のすべてのクラスをCallable<T>
本当に 拡張しますか?Future<T>
私はしません。
型キャストする必要がないことは、コンパイル時に型チェックを実行するため、Java ジェネリックの最大の利点の 1 つです。これにより、実行時にスローされる s の可能性が減少し、ClassCastException
より堅牢なコードにつながる可能性があります。
しかし、あなたはそれを十分に認識していると思います。
ジェネリックを見るたびに頭が痛くなります。Java の最良の部分は、その単純さと最小限の構文であり、ジェネリックは単純ではなく、かなりの量の新しい構文が追加されていることです。
最初は、ジェネリックの利点もわかりませんでした。私は 1.4 構文から Java を学び始めましたが (当時は Java 5 がリリースされていました)、ジェネリックに出会ったとき、書くべきコードが増えると感じ、その利点を本当に理解していませんでした。
最新の IDE では、ジェネリックを使用してコードを簡単に記述できます。
最新の適切な IDE のほとんどは、ジェネリックを使用したコードの記述、特にコード補完を支援するのに十分なほどスマートです。
Map<String, Integer>
を使用してを作成する例を次に示しHashMap
ます。入力する必要があるコードは次のとおりです。
Map<String, Integer> m = new HashMap<String, Integer>();
実際、新しいHashMap
. しかし、実際には、必要なものを Eclipse が認識する前に、これだけ入力する必要がありました。
Map<String, Integer> m = new Ha
Ctrl+Space
HashMap
確かに、候補のリストから選択する必要がありましたが、基本的に IDE は、ジェネリック型を含め、何を追加すればよいかを認識していました。適切なツールがあれば、ジェネリックを使用することはそれほど悪くありません。
さらに、タイプが既知であるため、ジェネリック コレクションから要素を取得するときに、IDE はそのオブジェクトがすでに宣言されたタイプのオブジェクトであるかのように動作します。オブジェクトのタイプを知るために IDE をキャストする必要はありません。は。
ジェネリックの主な利点は、Java 5 の新しい機能とうまく連携できることです。Set
整数を a に入れ、その合計を計算する例を次に示します。
Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(42);
int total = 0;
for (int i : set) {
total += i;
}
そのコードには、3 つの新しい Java 5 機能が含まれています。
まず、プリミティブのジェネリックとオートボクシングにより、次の行が可能になります。
set.add(10);
set.add(42);
整数10
はInteger
、 の値を持つ にオートボックス化されます10
。( についても同じです42
)。次に、それは sを保持することが知られている にInteger
投げ込まれます。をスローしようとすると、コンパイル エラーが発生します。Set
Integer
String
次に、for-each ループはこれら 3 つすべてを使用します。
for (int i : set) {
total += i;
}
まず、Set
含まInteger
れている が for-each ループで使用されます。各要素は であると宣言され、はプリミティブ にボックス化解除int
されるため、許可されます。そして、このボックス化解除が発生するという事実は、.Integer
int
Integer
Set
ジェネリックは、Java 5 で導入された新機能をまとめるための接着剤となることができ、コーディングをより簡単かつ安全にします。そしてほとんどの場合、IDE は適切な提案を行うのに十分なほどスマートであるため、一般的に、それほど多くのタイピングが必要になることはありません。
そして率直に言って、例からわかるようにSet
、Java 5 の機能を利用すると、コードがより簡潔で堅牢になると感じています。
編集 - ジェネリックなしの例
以下は、Set
ジェネリックを使用しない上記の例の図です。それは可能ですが、必ずしも快適ではありません:
Set set = new HashSet();
set.add(10);
set.add(42);
int total = 0;
for (Object o : set) {
total += (Integer)o;
}
(注: 上記のコードは、コンパイル時に未チェックの変換警告を生成します。)
非ジェネリック コレクションを使用する場合、コレクションに入力される型は type のオブジェクトですObject
。したがって、この例では、 aObject
がadd
セットに含まれています。
set.add(10);
set.add(42);
上記の行では、オートボクシングが行われています。プリミティブint
値10
と42
は、Integer
オブジェクトにオートボックス化され、 に追加されますSet
。ただし、Integer
オブジェクトは s として処理されることに注意してください。これは、コンパイラが期待するObject
型を知るのに役立つ型情報がないためです。Set
for (Object o : set) {
これは重要な部分です。for-each ループが機能する理由は、存在する場合は with 型情報を返すインターフェイスをSet
実装するためです。( 、つまり。)Iterable
Iterator
Iterator<T>
ただし、型情報がないため、はasの値を返す を返しSet
ます。そのため、for-each ループで取得される要素は型でなければなりません。Iterator
Set
Object
Object
Object
が から取得されたので、追加を実行するために手動で にSet
キャストする必要があります。Integer
total += (Integer)o;
ここでは、型キャストが an から an に実行されObject
ますInteger
。この場合、これが常に機能することはわかっていますが、手動の型キャストは、他の場所でマイナーな変更が行われた場合に破損する可能性のある脆弱なコードであると常に感じさせます. (私はすべてのタイプキャストがClassCastException
起こるのを待っていると感じていますが、私は脱線します...)
はInteger
にボックス化解除され、変数int
への追加を実行できるようになりました。int
total
Java 5 の新機能を非ジェネリック コードで使用できることを説明できればと思いますが、それはジェネリックを使用してコードを記述するほどクリーンで単純ではありません。そして、私の意見では、Java 5 の新機能を最大限に活用するには、ジェネリックを検討する必要があります。少なくとも、無効な型キャストが実行時に例外をスローするのを防ぐコンパイル時のチェックが可能である場合。
1.5 がリリースされる直前に Java バグ データベースを検索するとNullPointerException
、ClassCastException
. そのため、バグ、または少なくともスモーク テストを行った後も存続するバグを見つけることは、優れた機能ではないように思われます。
私にとってジェネリックの大きな利点は、重要な型情報をコードで文書化できることです。その型情報をコードに記述したくない場合は、動的に型付けされた言語、または少なくともより暗黙的な型推論を備えた言語を使用します。
オブジェクトのコレクションをそれ自体に保持することは悪いスタイルではありません (しかし、一般的なスタイルはカプセル化を効果的に無視することです)。それはむしろあなたが何をしているかに依存します。コレクションを「アルゴリズム」に渡すと、ジェネリックを使用して (コンパイル時またはコンパイル前に) チェックするのが少し簡単になります。
Java のジェネリックは、パラメトリック ポリモーフィズムを促進します。型パラメーターを使用して、引数を型に渡すことができます。メソッド likeString foo(String s)
が特定の string だけでなく任意の string に対して何らかの動作をモデル化するs
ように、 type likeList<T>
は特定の type だけでなく任意の type に対して何らかの動作をモデル化します。List<T>
は、任意の type について、要素がsT
である type がList
T
あることを示しています。List
実際には型コンストラクターもそうです。型を引数として取り、結果として別の型を構築します。
以下に、私が毎日使用するジェネリック型の例をいくつか示します。まず、非常に便利な汎用インターフェース:
public interface F<A, B> {
public B f(A a);
}
このインターフェイスは、いくつかの 2 つの型A
とについて、 を受け取り、を返すB
関数 ( と呼ばれるf
) があることを示しています。A
B
このインターフェイスを実装するA
と、前者を取り後者を返すB
関数を提供する限り、任意の型にすることができます。f
インターフェイスの実装例を次に示します。
F<Integer, String> intToString = new F<Integer, String>() {
public String f(int i) {
return String.valueOf(i);
}
}
ジェネリックの前は、キーワードを使用してサブクラス化することでポリモーフィズムが実現されていました。extends
ジェネリックを使用すると、実際にはサブクラス化を廃止し、代わりにパラメトリック ポリモーフィズムを使用できます。たとえば、任意の型のハッシュ コードを計算するために使用されるパラメーター化された (ジェネリック) クラスを考えてみましょう。Object.hashCode() をオーバーライドする代わりに、次のようなジェネリック クラスを使用します。
public final class Hash<A> {
private final F<A, Integer> hashFunction;
public Hash(final F<A, Integer> f) {
this.hashFunction = f;
}
public int hash(A a) {
return hashFunction.f(a);
}
}
これは、脆弱な階層をロックダウンすることなく、構成とパラメトリック ポリモーフィズムを使用するというテーマにとどまることができるため、継承を使用するよりもはるかに柔軟です。
ただし、Java のジェネリックは完全ではありません。たとえば、型を抽象化することはできますが、型コンストラクターを抽象化することはできません。つまり、「任意の型 T に対して」とは言えますが、「型パラメーター A を取る任意の型 T に対して」とは言えません。
Java ジェネリックのこれらの制限に関する記事をここに書きました。
ジェネリックの大きな利点の 1 つは、サブクラス化を回避できることです。サブクラス化は、拡張するのが厄介な脆弱なクラス階層や、階層全体を見ないと個別に理解するのが難しいクラスになる傾向があります。
ジェネリックの前には、、 、Widget
によって拡張されたようなクラスがありましたが、ジェネリックを使用すると、コンストラクターで、またはを取得して、、を提供する単一のジェネリック クラスを持つことができます。FooWidget
BarWidget
BazWidget
Widget<A>
Foo
Bar
Baz
Widget<Foo>
Widget<Bar>
Widget<Baz>
ジェネリックは、ボックス化とボックス化解除のパフォーマンス ヒットを回避します。基本的に、ArrayList と List<T> を見てください。どちらも同じコア機能を実行しますが、List<T> は、オブジェクトとの間でボックス化する必要がないため、はるかに高速になります。
Genericsの最大の利点は、コードの再利用です。多くのビジネスオブジェクトがあり、同じアクションを実行するために各エンティティに対して非常に類似したコードを作成するとします。(IE LinqからSQLへの操作)。
ジェネリックスを使用すると、特定の基本クラスから継承する任意の型を指定して操作できるクラスを作成したり、次のように特定のインターフェイスを実装したりできます。
public interface IEntity
{
}
public class Employee : IEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int EmployeeID { get; set; }
}
public class Company : IEntity
{
public string Name { get; set; }
public string TaxID { get; set }
}
public class DataService<ENTITY, DATACONTEXT>
where ENTITY : class, IEntity, new()
where DATACONTEXT : DataContext, new()
{
public void Create(List<ENTITY> entities)
{
using (DATACONTEXT db = new DATACONTEXT())
{
Table<ENTITY> table = db.GetTable<ENTITY>();
foreach (ENTITY entity in entities)
table.InsertOnSubmit (entity);
db.SubmitChanges();
}
}
}
public class MyTest
{
public void DoSomething()
{
var dataService = new DataService<Employee, MyDataContext>();
dataService.Create(new Employee { FirstName = "Bob", LastName = "Smith", EmployeeID = 5 });
var otherDataService = new DataService<Company, MyDataContext>();
otherDataService.Create(new Company { Name = "ACME", TaxID = "123-111-2233" });
}
}
上記のDoSomethingメソッドでタイプが異なる場合、同じサービスが再利用されることに注意してください。本当にエレガント!
あなたの仕事にジェネリックを使用する他の多くの大きな理由があります、これは私のお気に入りです。
カスタムタイプを定義する簡単な方法を提供するので、私はそれらが好きです(とにかく私はそれらを使用しています).
したがって、たとえば、文字列と整数で構成される構造を定義し、それらの構造の配列にアクセスする方法についてオブジェクトとメソッドのセット全体を実装する必要があるなどの代わりに、Dictionary を作成できます。
Dictionary<int, string> dictionary = new Dictionary<int, string>();
そして、コンパイラ/IDE が残りの面倒な作業を行います。特にディクショナリでは、最初のタイプをキーとして使用できます (値の繰り返しなし)。
型付きコレクション - それらを使用したくない場合でも、他のライブラリや他のソースからそれらを処理する必要がある可能性があります。
クラス作成におけるジェネリック型付け:
public class Foo < T> { public T get()...
キャストの回避 - 私はいつも次のようなことが嫌いでした
new Comparator { public int compareTo(Object o){ if (o instanceof classIcareAbout)...
インターフェイスがオブジェクトの観点から表現されているためにのみ存在する必要がある条件を本質的にチェックしている場合。
ジェネリックに対する私の最初の反応は、あなたの反応と似ていました。私の経験では、それらを少し使用した後、それらに慣れ、それらのないコードは明確に指定されていないように感じ、不快になります。それとは別に、Java の世界の残りの部分はそれらを使用しているので、最終的にはプログラムを使用する必要がありますよね?
良い例を挙げると。Foo というクラスがあるとします。
public class Foo
{
public string Bar() { return "Bar"; }
}
例 1 Foo オブジェクトのコレクションが必要です。LIst または ArrayList の 2 つのオプションがあり、どちらも同様の方法で機能します。
Arraylist al = new ArrayList();
List<Foo> fl = new List<Foo>();
//code to add Foos
al.Add(new Foo());
f1.Add(new Foo());
上記のコードで、Foo の代わりに FireTruck のクラスを追加しようとすると、ArrayList によって追加されますが、Foo のジェネリック リストによって例外がスローされます。
例 2。
2 つの配列リストができたので、それぞれに対して Bar() 関数を呼び出します。ArrayList はオブジェクトで満たされているため、bar を呼び出す前にオブジェクトをキャストする必要があります。しかし、Foo のジェネリック リストには Foo しか含まれないため、それらに対して直接 Bar() を呼び出すことができます。
foreach(object o in al)
{
Foo f = (Foo)o;
f.Bar();
}
foreach(Foo f in fl)
{
f.Bar();
}
メソッド/クラスの重要な概念がパラメーター/インスタンス変数の特定のデータ型に厳密にバインドされていないメソッド (またはクラス) を作成したことはありませんか (リンクされたリスト、最大/最小関数、バイナリ検索を考えてください)。など)。
カット アンド ペーストの再利用に頼ったり、強い型付けを損なうことなく、アルゴリズム/コードを再利用したいと思ったことはありList
ませんか?List
そのため、ジェネリック (またはそれ以上のもの) を使用する必要があります。
ジェネリックはクラスだけでなく、メソッドでも使用できることを忘れないでください。たとえば、次のスニペットを見てください。
private <T extends Throwable> T logAndReturn(T t) {
logThrowable(t); // some logging method that takes a Throwable
return t;
}
シンプルですが、とても上品に使えます。良い点は、メソッドが与えられたものを何でも返すことです。これは、呼び出し元に再スローする必要がある例外を処理するときに役立ちます。
...
} catch (MyException e) {
throw logAndReturn(e);
}
ポイントは、メソッドを介して渡すことによって型を失わないことです。の代わりに正しいタイプの例外をスローThrowable
できます。ジェネリックなしでできることはこれだけです。
これは、ジェネリック メソッドの単純な使用例です。ジェネリック メソッドを使ってできることは他にもたくさんあります。私の意見では、最もクールなのは、ジェネリックを使用した型推論です。次の例を見てください (Josh Bloch の「Effective Java 2nd Edition」から引用)。
...
Map<String, Integer> myMap = createHashMap();
...
public <K, V> Map<K, V> createHashMap() {
return new HashMap<K, V>();
}
これは多くのことを行うわけではありませんが、ジェネリック型が長い (または入れ子になっている、つまりMap<String, List<String>>
) 場合に混乱を減らします。
jvmはとにかくキャストします...ジェネリック型を「オブジェクト」として扱うコードを暗黙的に作成し、目的のインスタンス化へのキャストを作成します。Javaジェネリックは単なるシンタックスシュガーです。
Mitchel が指摘するように、主な利点は、複数のクラスを定義する必要がない強い型付けです。
このようにして、次のようなことができます。
List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();
ジェネリックがない場合、blah[0] を正しい型にキャストしてその関数にアクセスする必要があります。
Sun Java ドキュメントから、「なぜジェネリックを使用する必要があるのですか?」に対する応答として:
「ジェネリックは、コレクションのタイプをコンパイラに伝達してチェックできるようにする方法を提供します。コンパイラがコレクションの要素タイプを認識すると、コンパイラはコレクションを一貫して使用したことをチェックし、挿入できるようになります。コレクションから取り出される値の正しいキャスト...ジェネリックを使用するコードはより明確で安全です....コンパイラは、実行時に型制約に違反していないことをコンパイル時に検証できます[鉱山を強調]。プログラムが警告なしでコンパイルされる場合、実行時に ClassCastException をスローしないことを確実に述べることができます. ジェネリックを使用することの最終的な効果は、特に大規模なプログラムで、読みやすさと堅牢性が向上することです. [私のものを強調]"
私はこれが C# の質問であることを知っていますが、ジェネリックは他の言語でも使用されており、その使用/目的は非常に似ています。
Java コレクションは、Java 1.5 以降ジェネリックを使用します。そのため、独自のコレクションのようなオブジェクトを作成するときに使用するのが適しています。
ほとんどどこでも目にする例は、2 つのオブジェクトを保持する Pair クラスですが、これらのオブジェクトを汎用的な方法で処理する必要があります。
class Pair<F, S> {
public final F first;
public final S second;
public Pair(F f, S s)
{
first = f;
second = s;
}
}
この Pair クラスを使用するときはいつでも、処理するオブジェクトの種類を指定でき、型キャストの問題は実行時ではなくコンパイル時に表示されます。
ジェネリックは、キーワード「super」および「extends」で定義された境界を持つこともできます。たとえば、ジェネリック型を扱いたいが、それが Foo というクラス (setTitle メソッドを持つ) を確実に拡張するようにしたい場合:
public class FooManager <F extends Foo>{
public void setTitle(F foo, String title) {
foo.setTitle(title);
}
}
それ自体はあまり興味深いものではありませんが、FooManager を扱うときはいつでも、それが MyClass 型を処理し、MyClass が Foo を拡張することを知っていると便利です。
ジェネリックス(特にコレクション/リスト)を使用するもう1つの利点は、コンパイル時の型チェックを利用できることです。これは、オブジェクトのリストの代わりに汎用リストを使用する場合に非常に便利です。
ジェネリックを使用すると、厳密に型指定されたオブジェクトを作成できますが、特定の型を定義する必要はありません。最も役立つ例は List と同様のクラスだと思います。
ジェネリック リストを使用すると、好きなようにリスト リスト リストを作成でき、いつでも強力な型付けを参照できます。配列や標準リストの場合のように、変換する必要はありません。
ジェネリックを使用すると、任意のオブジェクトを保持できるオブジェクトとデータ構造に強い型付けを使用できます。また、一般的な構造 (ボックス化/ボックス化解除) からオブジェクトを取得するときに、面倒でコストのかかる型キャストを排除します。
両方を使用する 1 つの例は、リンクされたリストです。オブジェクト Foo しか使用できない場合、リンクされたリスト クラスは何の役に立つでしょうか? あらゆる種類のオブジェクトを処理できるリンク リストを実装するには、リストに 1 種類のオブジェクトのみを含める場合、仮想ノードの内部クラスのリンク リストとノードがジェネリックである必要があります。
最も多い理由の 1 つは、型の安全性を提供することです。
List<Customer> custCollection = new List<Customer>;
とは対照的に、
object[] custCollection = new object[] { cust1, cust2 };
簡単な例として。
要約すると、ジェネリックを使用すると、意図することをより正確に指定できます (より強力な型付け)。
これにはいくつかの利点があります。
コンパイラはユーザーが何をしたいのかをよりよく知っているため、多くの型キャストを省略することができます。これは、型に互換性があることを既に認識しているためです。
これにより、プログラムの正確さについてのフィードバックも早期に得られます。以前は実行時に失敗していたもの (オブジェクトを目的の型にキャストできなかったなど) がコンパイル時に失敗するようになり、テスト部門が不可解なバグ レポートを提出する前に間違いを修正できます。
コンパイラは、ボクシングを回避するなど、より多くの最適化を行うことができます。
コレクションに値の型が含まれている場合、コレクションに挿入するときにオブジェクトをボックス化/ボックス化解除する必要がないため、パフォーマンスが大幅に向上します。resharper のようなクールなアドオンは、foreach ループなど、より多くのコードを生成できます。
追加/拡張することがいくつかあります (.NET の観点から言えば):
ジェネリック型を使用すると、ロールベースのクラスとインターフェイスを作成できます。これはすでにより基本的な用語で述べられていますが、タイプにとらわれない方法で実装されたクラスを使用してコードを設計し始めていることがわかりました。これにより、再利用性の高いコードが得られます。
メソッドの一般的な引数は同じことを行うことができますが、「Tell Don't Ask」の原則をキャストに適用するのにも役立ちます。
たとえば、次のように SpringORM と Hibernate で実装された GenericDao でそれらを使用します
public abstract class GenericDaoHibernateImpl<T>
extends HibernateDaoSupport {
private Class<T> type;
public GenericDaoHibernateImpl(Class<T> clazz) {
type = clazz;
}
public void update(T object) {
getHibernateTemplate().update(object);
}
@SuppressWarnings("unchecked")
public Integer count() {
return ((Integer) getHibernateTemplate().execute(
new HibernateCallback() {
public Object doInHibernate(Session session) {
// Code in Hibernate for getting the count
}
}));
}
.
.
.
}
ジェネリックを使用することで、この DAO の私の実装では、GenericDao をサブクラス化するだけで、開発者が設計されたエンティティだけを渡すように強制されます。
public class UserDaoHibernateImpl extends GenericDaoHibernateImpl<User> {
public UserDaoHibernateImpl() {
super(User.class); // This is for giving Hibernate a .class
// work with, as generics disappear at runtime
}
// Entity specific methods here
}
私の小さなフレームワークはより堅牢です (フィルタリング、遅延読み込み、検索などがあります)。例を示すためにここで単純化しました
私は、スティーブやあなたと同じように、最初は「面倒で複雑すぎる」と言っていましたが、今ではその利点がわかります
ジェネリックを使用すると、型固有のサポートを提供しながら、より再利用可能なオブジェクト/メソッドを作成することもできます。場合によっては、多くのパフォーマンスも得られます。Java Generics の完全な仕様はわかりませんが、.NET では、Implements a Interface、Constructor、Derivation などの Type パラメーターに制約を指定できます。
私はかつてこのトピックについて講演しました。私のスライド、コード、および音声録音はhttp://www.adventuresinsoftware.com/generics/にあります。
コレクションにジェネリクスを使用するのは単純でクリーンです。他の場所でパントしたとしても、コレクションからの利益は私にとって勝利です.
List<Stuff> stuffList = getStuff();
for(Stuff stuff : stuffList) {
stuff.do();
}
対
List stuffList = getStuff();
Iterator i = stuffList.iterator();
while(i.hasNext()) {
Stuff stuff = (Stuff)i.next();
stuff.do();
}
また
List stuffList = getStuff();
for(int i = 0; i < stuffList.size(); i++) {
Stuff stuff = (Stuff)stuffList.get(i);
stuff.do();
}
それだけでも、ジェネリックの限界「コスト」に値するものであり、これを使用して価値を得るためにジェネリックの達人である必要はありません。