1。一般的なものとは正確には何ですか?
タイプの推論について議論する前に、一般的なものを確認する必要があります。ジェネリックは、Java SE 1.5の新機能です。ジェネリックの本質はパラメーター化されたタイプです。つまり、動作するデータ型はパラメーターとして指定されています。素人の用語では、「タイプ変数」になります。このタイプの変数は、クラス、インターフェイス、およびメソッドの作成に使用できます。 Java Genericsを理解する最も簡単な方法は、Javaタイプのcastingの操作を節約できる便利な構文と見なすことです。
List <purph> box = new arraylist <pople>(); box.add(new Apple()); Apple Apple = box.get(0);
上記のコード自体が明確に表現されています。BoxはAppleオブジェクトを含むListです。 getメソッドはAppleオブジェクトインスタンスを返しますが、このプロセスではタイプ変換は必要ありません。ジェネリックはありません。上記のコードは次のように記述する必要があります。
Apple Apple =(Apple)Box.get(0);
もちろん、ジェネリックはここで説明したほど単純ではありませんが、これは私たちの時代の主人公ではありません。ジェネリックを非常によく理解していない学生は、レッスンを補う必要があります〜もちろん、最高の参考資料は依然として公式の文書です。
2。ジェネリックによって引き起こされる問題(Java 7以前)
ジェネリックの最大の利点は、プログラムのタイプの安全性を提供し、後方互換性があることです。しかし、開発者を不幸にするものもあります。ジェネリックのタイプは、定義するたびに書かなければなりません。このディスプレイ仕様は少し冗長に感じるだけでなく、最も重要なことは、多くのプログラマーがジェネリックに精通していないため、多くの場合、正しい型パラメーターを提供することはできません。これで、コンパイラは自動的にパラメータータイプのジェネリックを推進します。
3。Java 7のジェネリックの型導入の改善
Java 7の以前のバージョンで一般的なタイプを使用するには、値を宣言および割り当てるときに、両側にジェネリックタイプを追加する必要があります。例えば:
map <string、integer> map = new hashmap <string、integer>();
多くの人々は最初は私と同じであったに違いありません、そして、彼らはこれに困惑していました:私は変数宣言でパラメータータイプを宣言しませんでしたか?オブジェクトが初期化されているときに、なぜそれを書き出す必要があるのですか?これはまた、ジェネリックが最初に登場したときに不平を言うものでもあります。しかし、Javaが改善している間、デザイナーはJavaコンパイラを常に改善して、よりインテリジェントで人間化していることも満足しています。今日の主人公は次のとおりです。タイププッシュダウン...この男が登場すると、上記のコードを書くと、オブジェクトのインスタンス化がインスタンス化されたときにパラメータータイプを喜んで省略でき、次のようになります。
map <string、integer> map = new Hashmap <>();
このステートメントでは、コンパイラは、可変宣言時のジェネリックタイプに基づいてHashMapをインスタンス化するときにジェネリックタイプを自動的に推測します。繰り返しますが、 new HashMap背後にある「<>」に注意を払ってください。これを追加することによってのみ、それが自動タイプの推論であることを意味します。そうでなければ、それは非世のHashMapであり、コンパイラを使用してソースコードをコンパイルするときに警告プロンプトが与えられます。この角度ブラケットのペア「<>」は、公式文書で「ダイヤモンド」と呼ばれます。
ただし、Java SE 7で一般的なインスタンスを作成する際のタイプ推論は限られているため、この時点でのタイプの導出は完全ではありません(半洗浄製品でさえ)。たとえば、次の例をJava 7で正しくコンパイルすることはできません(ただし、ジェネリックタイプはメソッドパラメーターに基づいて自動的に推測されるため、Java 8でコンパイルできます):
List <String> list = new ArrayList <>(); list.add( "a"); // addallは型コレクションのパラメーターを取得すると予想しています<?文字列>を拡張すると、次のステートメントはlist.addall(new ArrayList <>())を渡すことができません。
4。Java8の再革新
最新の公式Javaドキュメントでは、型導入の定義を確認できます。
タイプ推論は、各メソッドの呼び出しと対応する宣言を調べるJavaコンパイラの能力であり、呼び出しを適用するタイプの引数(または引数)を決定します。推論アルゴリズムは、引数のタイプと、利用可能な場合、結果が割り当てられている、または返されるタイプを決定します。最後に、推論アルゴリズムは、すべての引数で機能する最も具体的なタイプを見つけようとします。
要するに、タイプ派生とは、コンパイラが呼び出すメソッドと対応する宣言に基づいて必要なパラメータータイプを決定する能力を指します。また、説明する公式ドキュメントにも例が示されています。
static <t> t pick(t a1、t a2){return a2; } serializable s = pick( "d"、new ArrayList <String>());ここで、コンパイラは、 pickメソッドで渡された2番目のパラメーターのタイプがSerializableであると推測できます。
以前のJavaバージョンでは、上記の例をコンパイルできる場合、これを書く必要があります。
serializable s = this。<serializable> pick( "d"、new arrayList <string>());
これを書く詳細な理由は、ブルース・エッケルのJavaプログラミング思考(第4版)の一般的な章で見ることができます。もちろん、この本はJava 6に基づいており、このバージョンにはタイプの派生の概念はありません。これを見ると、多くの人は最新バージョンでタイプの派生の力をはっきりと見ることができます。一般的なクラスの宣言とインスタンス化プロセスに限定されなくなりましたが、ジェネリックパラメーターを使用した方法に拡張されます。
4.1推論と一般的な方法を入力します
新しいバージョンのタイプの派生方法と一般的なメソッドに関して、ドキュメントは少し複雑な例を示しています。ここに投稿しました。原則は上記のSerializable例と同じなので、詳細は説明しません。統合したい場合は、ご覧ください。
public class boxdemo {public static <u> void addbox(u、java.util.list <box <u >> boxes){box <u> box = new box <>(); box.set(u); boxes.add(box); } public static <u> void outputboxes(java.util.list <box <u >> boxes){int counter = 0; for(box <u> box:boxes){u boxcontents = box.get(); System.out.println( "Box#" + counter + "には[" + boxcontents.toString() + "]"が含まれます。カウンター++; }} public static void main(string [] args){java.util.arraylist <box <integer >> listofintegerboxes = new java.util.arraylist <>(); boxdemo。<integer> addbox(integer.valueof(10)、listofintegerboxes); boxdemo.addbox(integer.valueof(20)、listofintegerboxes); boxdemo.addbox(integer.valueof(30)、listofintegerboxes); boxdemo.outputboxes(listofintegerboxes); }}上記のコード出力は次のとおりです。
ボックス#0が含まれます[10]ボックス#1が含まれます[20]ボックス#2が含まれます[30]
一般的なメソッドaddBoxの焦点は、新しいJavaバージョンのメソッド呼び出しに表示する必要のないタイプ説明であることを言及してください。
boxdemo。<integer> addbox(integer.valueof(10)、listofintegerboxes);
コンパイラは、パラメータータイプがaddBoxに渡されたパラメーターからのIntegerあることを自動的に推測できます。
4.2一般的なクラスと非ジェネリッククラスのタイプ推論と汎用コンストラクター
まあ...これは英語のより良い文かもしれません:タイプ推論と一般的なクラスの一般的なコンストラクターと
実際、一般的なコンストラクターは、一般的なクラスの特許ではありません。非遺伝子クラスには、独自の一般的なコンストラクターも持つことができます。この例を見てください:
クラスmyclass <x> {<t> myclass(t t){// ...}}MyClassクラスに次のインスタンス化が行われた場合:
新しいmyclass <integer>( "")
OK、ここでは、MyClassのパラメータータイプxがIntegerであり、コンストラクターの場合、コンパイラは正式なパラメーターtが着信Stringオブジェクト( "")に基づいてStringあることを推測します。これはJava7バージョンに実装されています。 Java8でどのような改善が行われましたか? Java8の後、このような一般的なコンストラクターを使用して、一般的なクラスのこのインスタンス化を書くことができます。
myclass <integer> myobject = new MyClass <>( "");
はい、それはまだアングルブラケットのペア(<>)であり、ダイヤモンドと呼ばれるため、コンパイラは正式なパラメーターxがIntegerであり、tがStringであると自動的に推測できます。これは、実際にはMap<String,String>の最初の例と非常に似ています。ただし、コンストラクターの汎用化があることを除いて。
タイプの導出は、コールのパラメータータイプ、ターゲットタイプ(これはまもなく説明されます)、および戻りタイプ(返品がある場合)に基づいてのみ導出され、プログラム後のいくつかの要件に基づいて導出できないことに注意してください。
4.3ターゲットタイプ
前述のように、コンパイラはターゲットタイプに基づいてタイプの導入を実行できます。式のターゲットタイプは、式がどこに表示されるかに基づいてコンパイラが必要とする正しいデータ型を指します。たとえば、この例:
static <t> list <t> emmthylist(); list <string> listone = collections.emptylist();
ここで、リスト<string>はターゲットタイプです。ここで必要なのはList<String> 、およびCollections.emptyList()を返すため、 List<T> String返します。これはJava 7と8では問題ありません。ただし、Java 7では、次の状況では正常にコンパイルすることはできません。
void ProcessStringList(List <String> StringList){// Process StringList} processStringList(collections.emptylist());現時点では、Java7はこのエラーメッセージを送信します。
//リスト<オブジェクト>は<string>リストに変換できません
理由: Collections.emptyList()はList<T>を返し、tには特定のタイプが必要ですが、メソッド宣言から必要なのはObject Stringであると推測できないため、コンパイラはTを与えます。明らかに、 List<Object> List<String>.したがって、Java7バージョンでは、このようにこの方法を呼び出す必要があります。
ProcessStringList(Collection。<String> empthylist());
ただし、Java 8では、ターゲットタイプの概念が導入されているため、コンパイラが必要とするものがList<String> (つまり、ここのターゲットタイプ)であることは明らかであるため、コンパイラは返されたList<T>のtがStringである必要があるため、 processStringList(Collections.emptyList());大丈夫です。
ターゲットタイプの使用は、ラムダ式で最も明白です。
要約します
OK、上記はJavaのタイプ派生に関する個人的な洞察です。要約すると、ますます完璧なタイプの派生は、自然なものと思われるいくつかのタイプの変換作業を完了することですが、これらの作業はすべて、開発者がそれを表示できるのではなく、自動派生のためにコンパイラに任されています。この記事の内容が、Javaを学習するすべての人に役立つことを願っています。ご質問がある場合は、メッセージを残してコミュニケーションをとることができます。