基本概念
ジェネリックの本質は、パラメータ化された型の適用です。つまり、演算対象のデータ型はパラメータとして指定され、詳細は型を使用するときに指定されます。
このパラメータ タイプは、それぞれジェネリック クラス、ジェネリック インターフェイス、ジェネリック メソッドと呼ばれるクラス、インターフェイス、メソッドの作成に使用できます。
1. 開発
JDK 1.5 より前では、型の一般化は、すべての型の親クラスである Object と型キャストの組み合わせによってのみ実現できました。
そのため、コンパイル中に、コンパイラーはこのオブジェクトのキャストが成功したかどうかを確認できず、ClassCastException (キャスト例外) が発生する可能性があります。
ジェネリックの役割を理解するために以下の例を見てみましょう:
ジェネリックを使用しない場合(1.5以前)
ArrayList arrayList = new ArrayList(); arrayList.add(100); arrayList.add("abc");//因为不知道取出来的值的类型,类型转换的时候容易出错 String str = (String) arrayList.get(0);
ジェネリックを使用する場合(1.5以降)
ArrayList<String> arrayList = new ArrayList<String>(); arrayList.add("abc");//因为限定了类型,所以不能添加整形,编译器会提示出错arrayList.add(100);
2. 用語
// 以 ArrayList<E>,ArrayList<Integer> 为例:ArrayList<E>:泛型类型 E:类型变量(或者类型参数) ArrayList<Integer> :参数化的类型 Integer:类型参数的实例(或实际类型参数) ArrayList :原始类型
3. ジェネリッククラスclass Demo<T> { private T value;
Demo(T value) { this.value = value;
} public T getValue() { return value;
} public void setValue(T value) { this.value = value;
}
}public class Test {
public static void main(String[] args) {
Demo<String> demo = new Demo("abc");
demo.setValue("cba");
System.out.println(demo.getValue()); // cba
}
}
ジェネリックインターフェイスinterface Demo<K, V> { void print(K k, V v); } class DemoImpl implements Demo<String, Integer> { @Override public void print(String k, Integer v) { System.out.println(k + "-" + v); } }public class Test { public static void main(String[] args) { Demo<String, Integer> demo = new DemoImpl(); demo.print("abc", 100); } }ジェネリックメソッド
public class Test { public static void main(String[] args) { int num = get("abc", 100); System.out.println(num); } // 关键 --> 多了 <K, V> ,可以理解为声明此方法为泛型方法 public static <K, V> V get(K k, V v) { if (k != null) { return v; } return null; } }型を調べる汎用クラスに限定されており、汎用クラスで使用できます。インターフェースとジェネリックメソッドを使用しますが、次の点に注意してください:
修飾がクラスかインターフェースかに関係なく、キーワード extends が使用されます
- & 記号を使用して複数の修飾を与えることができます
- 資格にインターフェイスとクラスの両方がある場合、クラスは 1 つだけ存在し、それが最初に配置される必要があります。例:
-
public static <T extends Comparable&Serializable> T get(T t1,T t2)
型修飾の役割を分析しましょう... - 1. 型パラメータに境界を設定しないでください
コンパイル前に、コンパイラはジェネリック型 (T) がどのような型であるかを確認できないため
- そのため、プリミティブ型 (Object) としてデフォルトの T が設定されます。
- つまり、呼び出すことができるのは Object メソッドのみであり、compareTo メソッドは呼び出すことができません。
-
public static <T> T get(T t1,T t2) { //编译错误 if(t1.compareTo(t2)>=0); return t1; }
2. 型パラメータに境界を設定する
public static <T extends Comparable> T get(T t1,T t2) { if(t1.compareTo(t2)>=0); return t1; }
型消去
Java のジェネリックスは、基本的にコンパイラ レベルで実装されます。
- 生成された Java バイトコードには、ジェネリックスの型情報が含まれません。
- ジェネリックを使用するときに追加された型パラメーターは、コンパイル中にコンパイラーによって削除されます。このプロセスは型消去と呼ばれます。
- 次の例を見てください:
public class Test { public static void main(String[] args) { ArrayList<String> arrayList1 =new ArrayList<String>(); ArrayList<Integer> arrayList2 = new ArrayList<Integer>(); // true System.out.println(arrayList1.getClass() == arrayList2.getClass()); } }
コードを観察すると、ここでは 2 つの ArrayList 配列が定義されています:
1 つは文字列のみを格納できる ArrayList ジェネリック型で、もう 1 つは ArrayList ジェネリック型です, 整数のみを保存できます。
- それらのクラスオブジェクトを比較すると、結果が true であることがわかります。
- 説明: ジェネリック型 String と Integer はコンパイル プロセス中に消去され、元の型 (つまり Object) だけが残ります。
- 別の例を見てください:
public class Test { public static void main(String[] args) throws Exception{ ArrayList<String> arrayList =new ArrayList<String>(); arrayList.add("abc"); arrayList.getClass().getMethod("add", Object.class).invoke(arrayList, 100); for (int i=0;i<arrayList.size();i++) { System.out.println(arrayList.get(i)); } } }
コードを観察してください。ここでは ArrayList ジェネリック型が Integer オブジェクトとして定義され、インスタンス化されています
add メソッドを直接呼び出した場合、整数データのみを格納できます。
- リフレクションを使用して add メソッドを呼び出しますが、文字列を保存することもできます。
- 説明 整数ジェネリックインスタンスはコンパイル後に消去され、元の型のみが残ります。
1. Raw 型
元の型 (Raw 型) は、汎用情報を消去した後の バイトコード内の型変数の実型です。
- ジェネリック型パラメーターには、対応するプリミティブ変数があります。
型変数が消去 (破損) されると、修飾された型に置き換えられます (修飾されていない変数は Object を使用します)。
// 此时 T 是一个无限定类型,所以原始类型就是 Objectclass Pair<T> { } // 类型变量有限定,原始类型就用第一个边界的类型变量来替换,即Comparableclass Pair<T extends Comparable& Serializable> { } // 此时原始类型为 Serializable,编译器在必要的时要向 Comparable 插入强制类型转换 // 为了提高效率,应该将标签(tagging)接口(即没有方法的接口)放在边界限定列表的末尾class Pair<T extends Serializable&Comparable>
- 2. 型パラメータの型 次の例では、型パラメータは T を指し、T の型はいわゆる [型パラメータ] の型です。
コードを観察すると、次の結論を導き出すことができます:
[型パラメータ T] の型を指定しないでください。パラメータの型が一致しない場合、元の型は同じ親クラスの最小レベルを取得します
。
[型パラメータ T] ] 型を指定します。元の型は、指定された型または型のサブクラスのみですpublic class Test { // 定义泛型方法 public static <T> T add(T x, T y) { return y; } public static void main(String[] args) { // 1.不指定泛型 // 两个参数都是 Integer,所以 T 为 Integer 类型 int i = Test.add(1, 2); // 两个参数分别是 Integer,Float,取同一父类的最小级,T 为 Number 类型 Number f = Test.add(1, 1.2); // T 为 Object Object o = Test.add(1, "asd"); // 2.指定泛型 // 指定了Integer,所以只能为 Integer 类型或者其子类 int a = Test.<Integer> add(1, 2); //编译错误,指定了 Integer,不能为Float int b=Test.<Integer>add(1, 2.2); // 指定为Number,所以可以为 Integer,Float Number c = Test.<Number> add(1, 2.2); } }
- 3. ジェネリックスの型チェックは参考用です。参照されるオブジェクト自体に対するものではありません。 以下の例では、list は参照オブジェクトであるため、型チェックはそれに対して行われます。
// 没有进行类型检查,等价于 ArrayList list = new ArrayLis()ArrayList list = new ArrayList<String>(); list.add(100); list.add("hello");// 进行编译检查,等价于 ArrayList<String> list = new ArrayList<String>();ArrayList<String> list = new ArrayList(); list.add("hello"); list.add(100); // 编译错误
4.类型擦除与多态的冲突
来看下面的例子,这里定义了一个泛型类 Parent,一个实现它的子类 Son,并在子类中重写了父类的方法。
class Parent<T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } } class Son extends Parent<String>{ @Override public void setValue(String value) { super.setValue(value); } @Override public String getValue(){ return super.getValue();} }
在上面提到过泛型的类型参数在编译时会被类型擦除,因此编译后的 Parent 类如下:
class Parent { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } }
此时对比 Parent 与 Son 的 getValue/setValue 方法,发现方法的参数类型已经改变,从 Object -> String,这也意味着不是重写(overrride) 而是重载(overload)。
然而调用 Son 的 setValue 方法, 发现添加 Object 对象时编译错误。说明也不是重载。
public class Test { public static void main(String[] args) { Son son = new Son(); son.setValue("hello"); // 关键 -->编译错误 son.setValue(new Object()); } }
那么问题来了,通过上面的分析?Son 中定义的方法到底是重写还是重载?答案是:重写。这里 JVM 采用了桥方法(Brige)来解决类型擦除和多态引起的冲突。
我们对 Son 进行反编译(”Javap -c 类名.class”),得到如下内容:
Compiled from "Test.java"class Son extends Parent<java.lang.String> { Son(); Code: 0: aload_0 1: invokespecial #8 // Method Parent."<init>":()V 4: return public void setValue(java.lang.String); Code: 0: aload_0 1: aload_1 2: invokespecial #16 // Method Parent.setValue:(Ljava/lang/Object;)V 5: return public java.lang.String getValue(); Code: 0: aload_0 1: invokespecial #23 // Method Parent.getValue:()Ljava/lang/Object; 4: checkcast #26 // class java/lang/String 7: areturn public java.lang.Object getValue(); Code: 0: aload_0 1: invokevirtual #28 // Method getValue:()Ljava/lang/String; 4: areturn public void setValue(java.lang.Object); Code: 0: aload_0 1: aload_1 2: checkcast #26 // class java/lang/String 5: invokevirtual #30 // Method setValue:(Ljava/lang/String;)V 8: return }
发现这里共有 4 个 setValue/getValue 方法,除了 Son 表面上重写的 String 类型,编译器又自己生成了 Object 类型的方法,也称为桥方法。结果就是,编译器通过桥方法真正实现了重写,只是在访问时又去调用表面的定义的方法。
注意事项
不能用基本类型实例化类型参数,可以用对应的包装类来实例化类型参数
// 编译错误ArrayList<int> list = new ArrayList<int>();// 正确写法ArrayList<Integer> list = new ArrayList<Integer>();
参数化类型的数组不合法
Demo<T >{ }public class Test { public static void main(String[] args) { // 编译错误 --> 类型擦除导致数组变成 Object [],因此没有意义 Demo<String>[ ] demo =new Demo[10]; } }
不能实例化类型变量
// 编译错误,需要类型参数需要确定类型Demo<T> demo = new Demo<T>
泛型类的静态上下文中不能使用类型变量
public class Demo<T> { public static T name; public static T getName() { ... } }
不能抛出也不能捕获泛型类的对象
//异常都是在运行时捕获和抛出的,而在编译的时候,泛型信息全都会被擦除掉。会导致这里捕获的类型一致try{ }catch(Problem<Integer> e1){ //do Something... }catch(Problem<Number> e2){ // do Something ...}
以上就是11.Java 基础 - 泛型的内容,更多相关内容请关注PHP中文网(www.php.cn)!

jvmmanagesgarbagecollectionacrossplatformseftivivivivitybyusagenerationalaphadadadaptingtosandhardwaredefferences.itemployscollectorslikeserial、parallel、cms、andg1、各sutitedfordifferentscenarios

Javaは、Javaの「Write and Averywherewhere」という哲学がJava Virtual Machine(JVM)によって実装されているため、変更なしで異なるオペレーティングシステムで実行できます。コンパイルされたJavaバイトコードとオペレーティングシステムの間の仲介者として、JVMはバイトコードを特定のマシン命令に変換し、JVMがインストールされた任意のプラットフォームでプログラムが独立して実行できることを確認します。

Javaプログラムの編集と実行は、BytecodeとJVMを通じてプラットフォームの独立性を達成します。 1)Javaソースコードを書き、それをbytecodeにコンパイルします。 2)JVMを使用して、任意のプラットフォームでByteCodeを実行して、コードがプラットフォーム間で実行されるようにします。

Javaのパフォーマンスはハードウェアアーキテクチャと密接に関連しており、この関係を理解することでプログラミング機能を大幅に改善できます。 1)JVMは、CPUアーキテクチャの影響を受けるJITコンピレーションを介して、Java Bytecodeを機械命令に変換します。 2)メモリ管理とゴミ収集は、RAMとメモリバスの速度の影響を受けます。 3)キャッシュとブランチ予測Javaコードの実行を最適化します。 4)マルチスレッドと並列処理がマルチコアシステムのパフォーマンスを改善します。

ネイティブライブラリを使用すると、これらのライブラリはオペレーティングシステムごとに個別にコンパイルする必要があるため、Javaのプラットフォームの独立性が破壊されます。 1)ネイティブライブラリはJNIを介してJavaと対話し、Javaが直接実装できない機能を提供します。 2)ネイティブライブラリを使用すると、プロジェクトの複雑さが増し、さまざまなプラットフォームのライブラリファイルの管理が必要です。 3)ネイティブライブラリはパフォーマンスを改善できますが、それらは注意して使用し、クロスプラットフォームテストを実施する必要があります。

JVMは、JavanativeInterface(JNI)およびJava Standard Libraryを介してオペレーティングシステムのAPIの違いを処理します。1。JNIでは、Javaコードがローカルコードを呼び出し、オペレーティングシステムAPIと直接対話できます。 2. Java Standard Libraryは統一されたAPIを提供します。これは、異なるオペレーティングシステムAPIに内部的にマッピングされ、コードがプラットフォーム間で実行されるようにします。

modularitydoesnotdirectlyectlyectjava'splatformindepensence.java'splatformendepenceismaindainededainededainededaindainedaindained bythejvm、butmodularityinfluencesApplucationStructure andmanagement、間接的なインパクチャプラット形成依存性.1)

bytecodeinjavaisthe intermediaterepresentationthateNablesplatformindepence.1)javacodeis compiledintobytecodestoredin.classfiles.2)thejvminterpretsorcompilesthisbytecodeintomachinecodeatime、


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

PhpStorm Mac バージョン
最新(2018.2.1)のプロフェッショナル向けPHP統合開発ツール

VSCode Windows 64 ビットのダウンロード
Microsoft によって発売された無料で強力な IDE エディター

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

MantisBT
Mantis は、製品の欠陥追跡を支援するために設計された、導入が簡単な Web ベースの欠陥追跡ツールです。 PHP、MySQL、Web サーバーが必要です。デモおよびホスティング サービスをチェックしてください。

ドリームウィーバー CS6
ビジュアル Web 開発ツール

ホットトピック









