ホームページ >Java >&#&チュートリアル >Java プログラミング思考の学習クラス (3) 第 15 章 - ジェネリックス
ジェネリック (ジェネリック) の概念は、Java SE5 の大きな変更点の 1 つです。ジェネリックはパラメータ化された型 (パラメータ化された型) の概念を実装し、コードを複数の型に適用できるようにします。 「ジェネリック」という用語は、「非常に多くの種類に適用できる」ことを意味します。
ジェネリックメソッドが配置されているクラスがジェネリックかどうか、つまりジェネリックメソッドが配置されているクラスがジェネリッククラスであるかどうかとは関係がありません。
ジェネリック メソッドを使用すると、クラスとは独立してメソッドを変更できます。
基本的な指針: できる限り、一般的な方法を使用するように努めるべきです。つまり、クラス全体のジェネリック化をジェネリック メソッドの使用で置き換えることができる場合は、物事がより明確になるため、ジェネリック メソッドのみを使用する必要があります。
static
メソッドの場合、ジェネリック クラスの型パラメーターにアクセスできないため、static
メソッドでジェネリック機能を使用する必要がある場合は、ジェネリックである必要があります。方法。 static
方法而言,无法访问泛型类的类型参数,所以,如果static
方法需要使用泛型能力,就必须使其成为泛型方法。
要定义泛型方法,只需将泛型参数列表置于返回值之前。
使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型。这称为类型参数推断(type argument inference)。
类型推断只对赋值操作有效。
如果将一个泛型方法调用的结果作为参数,传递给另一个方法,这时编译器并不会执行类型推断。
在点操作符与方法名之间插入尖括号,然后把类型置于尖括号内,即显式的类型说明。
根据JDK文档的描述,
Class.getTypeParameters()
将“返回一个TypeVariable
对象数组,表示有泛型声明的类型参数…..”,这好像是在暗示你可能发现参数类型的信息,但是,正如你从输出中看到,你能够发现的只是用作参数占位符的标识符,这并非有用的信息。因此,残酷的现实是:在泛型代码内部,无法获得任何有关泛型参数类型的信息。
因此,你可以知道诸如泛型参数标识符和泛型类型边界这类信息——你却无法知道创建某个特定实例的实际的类型参数。……,在使用Java泛型工作时它是必须处理的最基本的问题。
Java泛型是使用擦除来实现的,这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此
Listf7e83be87db5cd2d9a8a0b8117b38cd4
和Listc0f559cc8d56b43654fcbe4aa9df7b4a
在运行时事实上是相同的类型。这两种形式都被擦除成它们的“原生类型,即List
。
ジェネリックメソッドを使用する場合、コンパイラが特定の型を見つけてくれるため、通常はパラメータの型を指定する必要はありません。これを型引数の推論と呼びます。它怎么知道
f()
方法是为类型参数T而存在的呢?当你实例化这个模板时,C++编译器将进行检查,因此在Manipulatorda98a08537ace02ebe5efb4a0b237f4a
被实例化的这一刻,它看到HasF
拥有一个方法f()
ジェネリックメソッドを定義するには、戻り値の前にジェネリックパラメータリストを置くだけです。 1.1 型パラメータの推論
型推論は代入演算に対してのみ有効です。
ジェネリックメソッド呼び出しの結果がパラメータとして別のメソッドに渡される場合、コンパイラは型推論を実行しません。
2 消去の謎🎜🎜🎜 JDK ドキュメントによると、🎜1.1.1 明示的な型の指定
ドット演算子とメソッド名の間に山括弧を挿入し、山括弧内に型を配置します。つまり、明示的な型の説明 。Class.getTypeParameters()
🎜 は「オブジェクトの 🎜TypeVariable
🎜 配列を返します。これは、これは、パラメーターの型に関する情報が見つかる可能性があることを暗示しているようですが、出力からわかるように、見つけられるものはすべて🎜🎜パラメーターのプレースホルダーとして使用されています🎜🎜識別子 🎜🎜 は有用な情報ではありません。 🎜🎜したがって、厳しい現実は次のとおりです: 🎜🎜ジェネリック コード内では、 ジェネリック パラメータの型に関する情報を取得する方法はありません🎜🎜。 🎜🎜つまり、🎜ジェネリック パラメーター識別子🎜や🎜ジェネリック型境界🎜などはわかりますが、🎜固有のインスタンスを作成する実際の型パラメーター🎜を知ることはできません。 …、これは Java ジェネリックスを扱うときに 🎜対処しなければならない🎜最も基本的な 🎜問題 🎜 です。 🎜🎜Java ジェネリックスは 🎜🎜erasure🎜🎜 を使用して実装されます。つまり、ジェネリックスを使用すると、特定の型情報はすべて消去されます。🎜知っていることは、オブジェクトを使用していることだけです 🎜。したがって、 🎜Listf7e83be87db5cd2d9a8a0b8117b38cd4
🎜 と 🎜Listc0f559cc8d56b43654fcbe4aa9df7b4a
🎜 は、実行時には実際には 🎜🎜同じ型🎜🎜 になります。どちらのフォームも、🎜🎜ネイティブ タイプ🎜🎜、つまり 🎜List
🎜 に消去されます。 🎜🎜🎜2.1 C++ の方法 🎜🎜 2.1.1 次の C++ テンプレートの例: 🎜🎜 🎜その方法f()
メソッドが型パラメータ T に存在することを知っていますか? このテンプレートを 🎜インスタンス化すると、C++ コンパイラがそれをチェックします。そのため、Manipulatord99feec941ecc385f5dca0de7b8e0d0f
,set()
方法不能工作于Apple
和Fruit
,因为 set() 的参数也是? extends Furit
,这意味着它可以是任何事物,而编译器无法验证“任何事物”的类型安全性。但是,
equals()
方法工作良好,因为它将接受Object类型而并非T类型的参数。因此,编译器只关注传递进来和要返回的对象类型,它并不会分析代码,以查看是否执行了任何实际的写入和读取操作。5.2 逆变(Contravariance)
使用超类型通配符。声明通配符是由某个特定类的任何基类界定的,方法是指定
bbee182344df36d3890f89a83f7ca198
,甚至或者使用类型参数:117c5a0bdb71ea9a9d0c2b99b03abe3e
。这使得你可以安全地传递一个类型对象到泛型类型中。参数apples是Apple的某种基类型的List,这样你就知道向其中添加Apple或Apple的子类型是安全的。
package net.mrliuli.generics.wildcards;import java.util.*;public class SuperTypeWildcards { /** * 超类型通配符使得可以向泛型容器写入。超类型边界放松了在可以向方法传递的参数上所作的限制。 * @param apples 参数apples是Apple的某种基类型的List,这样你就知道向其中添加Apple或Apple的子类型是安全的。 */ static void writeTo(List<? super Apple> apples){ apples.add(new Apple()); apples.add(new Jonathan()); //apples.add(new Fruit()); // Error } }
GenericWriting.java 中
writeExact(fruitList, new Apple());
在JDK1.7中没有报错,说明进入泛型方法writeExact()
时T
被识别为Fruit
,书中说报错,可能JDK1.5将T
识别为Apple
。package net.mrliuli.generics.wildcards;import java.util.*;/** * Created by li.liu on 2017/12/8. */public class GenericWriting { static <T> void writeExact(List<T> list, T item){ list.add(item); } static List<Apple> appleList = new ArrayList<Apple>(); static List<Fruit> fruitList = new ArrayList<Fruit>(); static void f1(){ writeExact(appleList, new Apple()); writeExact(fruitList, new Apple()); } static <T> void writeWithWildcard(List<? super T> list, T item){ list.add(item); } static void f2(){ writeWithWildcard(appleList, new Apple()); writeWithWildcard(fruitList, new Apple()); } public static void main(String[] args){ f1(); f2(); } }5.3 无界通配符(Unbounded wildcards)
原生泛型
Holder
与Holder6b3d0130bba23ae47fe2b8e8cddf0195
原生
Holder
将持有任何类型的组合,而Holder6b3d0130bba23ae47fe2b8e8cddf0195
将持有具有某种具体类型的同构集合,因此不能只是向其中传递Object。5.4 捕获转换
以下示例,被称为捕获转换,因为未指定的通配符类型被捕获,并被转换为确切类型。参数类型在调用
f2()
的过程中被捕获,因此它可以在对f1()
的调用中被使用。package net.mrliuli.generics.wildcards;/** * Created by leon on 2017/12/9. */public class CaptureConversion { static <T> void f1(Holder<T> holder){ T t = holder.get(); System.out.println(t.getClass().getSimpleName()); } static void f2(Holder<?> holder){ f1(holder); // Call with captured type } public static void main(String[] args){ Holder raw = new Holder<Integer>(1); f1(raw); f2(raw); Holder rawBasic = new Holder(); rawBasic.set(new Object()); f2(rawBasic); Holder<?> wildcarded = new Holder<Double>(1.0); f2(wildcarded); } }6 问题
基本类型不能作为类型参数
由于探险,一个类不能实现同一个泛型接口的两种变体
由于擦除,通过泛型来重载方法将产生相同的签名,编译出错,不能实现重载
基类劫持了接口
7 总结
我相信被称为泛型的通用语言特性(并非必须是其在Java中的特定实现)的目的在于可表达性,而不仅仅是为了创建类型安全的容器。类型安全的容器是能够创建更通用代码这一能力所带来的副作用。
泛型正如其名称所暗示的:它是一种方法,通过它可以编写出更“泛化”的代码,这些代码对于它们能够作用的类型有更少的限制,因此单个的代码段能够应用到更多的类型上。
相关文章:
以上がJava プログラミング思考の学習クラス (3) 第 15 章 - ジェネリックスの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。