検索
ホームページJava&#&チュートリアルJava 汎用プログラミングの最も完全な概要

1 はじめに

Java 汎用プログラミングは、JDK1.5 バージョンの後に導入されました。ジェネリックを使用すると、プログラマは、多くの場合、コレクション内で型の抽象化を使用できます。以下はジェネリックスを使用しない例です:

List myIntList=new LinkedList(); //1  
myIntList.add(newInteger(0)); //2  
Integer x=(Integer)myIntList.iterator().next(); //3

コードの 3 行目に注目してください。ただし、これは非常に不快な点です。プログラマは、List に格納されているオブジェクトの型が Integer であることを知っている必要がありますが、 list 内の要素、または型をキャストする必要があります。これはなぜですか?その理由は、コンパイラがイテレータの next() メソッドが Object 型のオブジェクトを返すことのみを保証できるためです。 Integer 変数の型安全性を確保するには、強制的に変換する必要があります。

この種の変換は混乱を招くだけでなく、型変換例外 ClassCastException が発生する可能性もあり、多くの場合検出が困難です。リスト内の要素が特定のデータ型であることを確認して、型変換をキャンセルできるようにし、エラーの可能性を減らします。これはジェネリック設計の本来の目的でもあります。以下はジェネリックスの使用例です:

List<integer> myIntList=newLinkedList<integer>(); //1’  
myIntList.add(newInteger(0)); //2’  
Integerx=myIntList.iterator().next(); //3’</integer></integer>

コードの最初の行で、List に格納されているオブジェクトの型が Integer であることを指定します。これにより、リスト内のオブジェクトを取得するときに型をキャストする必要がなくなります。

2 単純なジェネリックスを定義する

以下は、ジェネリック技術を使用する java.util パッケージのインターフェイス List および Iterator から引用した定義です。

public interface List<e> {  
<span>    </span>void add(E x);  
<span>    </span>Iterator<e> iterator();  
}  
public interface Iterator<e> {  
<span>    </span>E next();  
<span>    </span>boolean hasNext();  
}</e></e></e>

これは、インターフェイスの後に山括弧が追加され、山括弧内に型パラメータがあることを除いて、ネイティブ型と変わりません (定義時には書式設定された型パラメータであり、特定の型に置き換えられます) type を呼び出すとき)。

List は、List 内の型パラメータ E が Integer に置き換えられることを意味すると思われるかもしれません。

public interface IntegerList {  
<span style="white-space: pre;">    </span>void add(Integer x)  
<span style="white-space: pre;">    </span>Iterator<Integer> iterator();  
}

型の消去とは、型パラメーターのマージを通じてジェネリック型のインスタンスを同じバイトコードに関連付けることを指します。コンパイラはジェネリック型に対して 1 つのバイトコードのみを生成し、そのインスタンスをこのバイトコードに関連付けます。そのため、ジェネリック型の静的変数はすべてのインスタンスで共有されます。さらに、クラスがインスタンス化されていないため、静的メソッドはジェネリック クラスの型パラメーターにアクセスできないため、静的メソッドでジェネリック機能を使用する必要がある場合は、ジェネリック メソッドにする必要があることに注意してください。型消去の鍵は、ジェネリック型から型パラメーターに関する情報を消去し、必要に応じて型チェックおよび型変換メソッドを追加することです。ジェネリックスを使用すると、具象型は消去され、オブジェクトを使用していることだけがわかります。例: List と List は実際には同じ型です。これらはすべて、ネイティブ タイプである List に消去されます。コンパイル中に型の消去が行われるため、同じジェネリック クラスのインスタンスによってメソッドを区別することはできません。たとえば、次の例では、型の消去後は両方のメソッドが List 型のパラメーターになるため、コンパイル中にエラーが発生します。ジェネリック クラスの型に基づいてメソッドを区別することはできません。

 public class Erasure{  
            public void test(List<String> ls){  
                System.out.println("Sting");  
            }  
            public void test(List<Integer> li){  
                System.out.println("Integer");  
            }  
  }

そこで問題が発生します。コンパイル中にメソッドやクラス内の実際の型情報が消去されるため、オブジェクトを返すときに特定の型をどのように知ることができるのでしょうか。たとえば、List の String 情報はコンパイル後に消去されます。では、List 内のオブジェクトが実行時にイテレータを通じて返されるとき、List に格納されているオブジェクトが String 型オブジェクトであることをどのように確認すればよいでしょうか。中 メソッド本体の型情報を削除するため、実行時の問題は境界です。つまり、オブジェクトがメソッドに出入りする場所であり、コンパイラが型チェックを実行して遷移コードを挿入する場所とまったく同じです。ジェネリックスのすべてのアクションは境界で発生します。追加のコンパイル時チェックが渡された値に対して実行され、渡された値に対してキャストが挿入されます。

3. ジェネリックとサブタイプ

ジェネリックを完全に理解するために、ここに例を示します: (Apple は Fruit のサブクラスです

)
List<Apple> apples = new ArrayList<Apple>(); //1  
List<Fruit> fruits = apples; //2

第1行代码显然是对的,但是第2行是否对呢?我们知道Fruit fruit = new Apple(),这样肯定是对的,即苹果肯定是水果,但是第2行在编译的时候会出错。这会让人比较纳闷的是一个苹果是水果,为什么一箱苹果就不是一箱水果了呢?可以这样考虑,我们假定第2行代码没有问题,那么我们可以使用语句fruits.add(new Strawberry())(Strawberry为Fruit的子类)在fruits中加入草莓了,但是这样的话,一个List中装入了各种不同类型的子类水果,这显然是不可以的,因为我们在取出List中的水果对象时,就分不清楚到底该转型为苹果还是草莓了。

通常来说,如果Foo是Bar的子类型,G是一种带泛型的类型,则G不是G的子类型。这也许是泛型学习里面最让人容易混淆的一点。

 

4.通配符

4.1通配符?

先看一个打印集合中所有元素的代码。

void printCollection(Collection c) {                  
<span style="white-space: pre;">    </span>Iterator i=c.iterator();  
<span style="white-space: pre;">    </span>for (k=0;k < c.size();k++) {  
<span style="white-space: pre;">        </span>System.out.println(i.next());  
<span style="white-space: pre;">    </span>}  
}
void printCollection(Collection<Object> c) {  
for (Object e:c) {  
System.out.println(e);  
}  
}

   很容易发现,使用泛型的版本只能接受元素类型为Object类型的集合如ArrayList();如果是ArrayList,则会编译时出错。因为我们前面说过,Collection并不是所有集合的超类。而老版本可以打印任何类型的集合,那么如何改造新版本以便它能接受所有类型的集合呢?这个问题可以通过使用通配符来解决。修改后的代码如下所示:

//使用通配符?,表示可以接收任何元素类型的集合作为参数  
void printCollection(Collection<?> c) {  
<span style="white-space: pre;">    </span>for (Object e:c) {  
<span style="white-space: pre;">        </span>System.out.println(e);  
<span style="white-space: pre;">    </span>}  
}

这里使用了通配符?指定可以使用任何类型的集合作为参数。读取的元素使用了Object类型来表示,这是安全的,因为所有的类都是Object的子类。这里就又出现了另外一个问题,如下代码所示,如果试图往使用通配符?的集合中加入对象,就会在编译时出现错误。需要注意的是,这里不管加入什么类型的对象都会出错。这是因为通配符?表示该集合存储的元素类型未知,可以是任何类型。往集合中加入元素需要是一个未知元素类型的子类型,正因为该集合存储的元素类型未知,所以我们没法向该集合中添加任何元素。唯一的例外是null,因为null是所有类型的子类型,所以尽管元素类型不知道,但是null一定是它的子类型。

Collection<?> c=new ArrayList<String>();  
c.add(newObject()); //compile time error,不管加入什么对象都出错,除了null外。  
c.add(null); //OK

另一方面,我们可以从List> lists中获取对象,虽然不知道List中存储的是什么类型,但是可以肯定的是存储的类型一定是Object的子类型,所以可以用Object类型来获取值。如for(Object obj: lists),这是合法的。

4.2边界通配符

   1)?extends通配符

假定有一个画图的应用,可以画各种形状的图形,如矩形和圆形等。为了在程序里面表示,定义如下的类层次:

public abstract class Shape {  
<span style="white-space: pre;">    </span>public abstract void draw(Canvas c);  
}  
  
public class Circle extends Shape {  
<span style="white-space: pre;">    </span>private int x,y,radius;  
<span style="white-space: pre;">    </span>public void draw(Canvas c) { ... }  
}  
  
public class Rectangle extends Shape  
<span style="white-space: pre;">    </span>private int x,y,width,height;  
<span style="white-space: pre;">    </span>public void draw(Canvasc) { ... }  
}

为了画出集合中所有的形状,我们可以定义一个函数,该函数接受带有泛型的集合类对象作为参数。但是不幸的是,我们只能接收元素类型为Shape的List对象,而不能接收类型为List的对象,这在前面已经说过。为了解决这个问题,所以有了边界通配符的概念。这里可以采用public void drawAll(List extends Shape>shapes)来满足条件,这样就可以接收元素类型为Shape子类型的列表作为参数了。

//原始版本  
public void drawAll(List<Shape> shapes) {  
<span style="white-space: pre;">    </span>for (Shapes:shapes) {  
<span style="white-space: pre;">        </span>s.draw(this);  
<span style="white-space: pre;">    </span>}  
}
//使用边界通配符的版本  
public void drawAll(List<?exends Shape> shapes) {  
<span style="white-space: pre;">    </span>for (Shapes:shapes) {  
<span style="white-space: pre;">        </span>s.draw(this);  
<span style="white-space: pre;">    </span>}  
}

这里就又有个问题要注意了,如果我们希望在List shapes中加入一个矩形对象,如下所示:

shapes.add(0, new Rectangle()); //compile-time error

那么这时会出现一个编译时错误,原因在于:我们只知道shapes中的元素时Shape类型的子类型,具体是什么子类型我们并不清楚,所以我们不能往shapes中加入任何类型的对象。不过我们在取出其中对象时,可以使用Shape类型来取值,因为虽然我们不知道列表中的元素类型具体是什么类型,但是我们肯定的是它一定是Shape类的子类型。

 

2)?super通配符

       这里还有一种边界通配符为?super。比如下面的代码:

List<Shape> shapes = new ArrayList<Shape>();  
List<? super Cicle> cicleSupers = shapes;  
cicleSupers.add(new Cicle()); //OK, subclass of Cicle also OK  
cicleSupers.add(new Shape()); //ERROR

       这表示cicleSupers列表存储的元素为Cicle的超类,因此我们可以往其中加入Cicle对象或者Cicle的子类对象,但是不能加入Shape对象。这里的原因在于列表cicleSupers存储的元素类型为Cicle的超类,但是具体是Cicle的什么超类并不清楚。但是我们可以确定的是只要是Cicle或者Circle的子类,则一定是与该元素类别兼容。

 

3)边界通配符总结

l         如果你想从一个数据类型里获取数据,使用 ? extends 通配符

l         如果你想把对象写入一个数据结构里,使用 ? super 通配符

l         如果你既想存,又想取,那就别用通配符。

 

5.泛型方法

考虑实现一个方法,该方法拷贝一个数组中的所有对象到集合中。下面是初始的版本:

static void fromArrayToCollection(Object[]a, Collection<?> c) {  
<span style="white-space: pre;">    </span>for (Object o:a) {  
<span style="white-space: pre;">        </span>c.add(o); //compile time error  
<span style="white-space: pre;">    </span>}  
}

可以看到显然会出现编译错误,原因在之前有讲过,因为集合c中的类型未知,所以不能往其中加入任何的对象(当然,null除外)。解决该问题的一种比较好的办法是使用泛型方法,如下所示:

static <T> void fromArrayToCollection(T[] a, Collection<T>c){  
<span style="white-space: pre;">    </span>for(T o : a) {  
<span style="white-space: pre;">        </span>c.add(o);// correct  
<span style="white-space: pre;">    </span>}  
}

注意泛型方法的格式,类型参数需要放在函数返回值之前。然后在参数和返回值中就可以使用泛型参数了。具体一些调用方法的实例如下:

Object[] oa = new Object[100];  
Collection<Object>co = new ArrayList<Object>();  
fromArrayToCollection(oa, co);// T inferred to be Object  
String[] sa = new String[100];  
Collection<String>cs = new ArrayList<String>();  
fromArrayToCollection(sa, cs);// T inferred to be String  
fromArrayToCollection(sa, co);// T inferred to be Object  
Integer[] ia = new Integer[100];  
Float[] fa = new Float[100];  
Number[] na = new Number[100];  
Collection<Number>cn = new ArrayList<Number>();  
fromArrayToCollection(ia, cn);// T inferred to be Number  
fromArrayToCollection(fa, cn);// T inferred to be Number  
fromArrayToCollection(na, cn);// T inferred to be Number  
fromArrayToCollection(na, co);// T inferred to be Object  
fromArrayToCollection(na, cs);// compile-time error

注意到我们调用方法时并不需要传递类型参数,系统会自动判断类型参数并调用合适的方法。当然在某些情况下需要指定传递类型参数,比如当存在与泛型方法相同的方法的时候(方法参数类型不一致),如下面的一个例子:

public  <T> void go(T t) {  
    System.out.println("generic function");  
}  
public void go(String str) {  
    System.out.println("normal function");  
}  
public static void main(String[] args) {  
        FuncGenric fg = new FuncGenric();  
        fg.go("haha");//打印normal function  
        fg.<String>go("haha");//打印generic function  
        fg.go(new Object());//打印generic function  
        fg.<Object>go(new Object());//打印generic function  
}

如例子中所示,当不指定类型参数时,调用的是普通的方法,如果指定了类型参数,则调用泛型方法。可以这样理解,因为泛型方法编译后类型擦除,如果不指定类型参数,则泛型方法此时相当于是public void go(Object t)。而普通的方法接收参数为String类型,因此以String类型的实参调用函数,肯定会调用形参为String的普通方法了。如果是以Object类型的实参调用函数,则会调用泛型方法。

6.其他需要注意的小点

1)方法重载

在JAVA里面方法重载是不能通过返回值类型来区分的,比如代码一中一个类中定义两个如下的方法是不容许的。但是当参数为泛型类型时,却是可以的。如下面代码二中所示,虽然形参经过类型擦除后都为List类型,但是返回类型不同,这是可以的。

/*代码一:编译时错误*/   
public class Erasure{  
            public void test(int i){  
                System.out.println("Sting");  
            }  
            public int test(int i){  
                System.out.println("Integer");  
            }  
  }
/*代码二:正确 */  
 public class Erasure{  
            public void test(List<String> ls){  
                System.out.println("Sting");  
            }  
            public int test(List<Integer> li){  
                System.out.println("Integer");  
            }  
  }

2)泛型类型是被所有调用共享的

       所有泛型类的实例都共享同一个运行时类,类型参数信息会在编译时被擦除。因此考虑如下代码,虽然ArrayList和ArrayList类型参数不同,但是他们都共享ArrayList类,所以结果会是true。

List<String>l1 = new ArrayList<String>();  
List<Integer>l2 = new ArrayList<Integer>();  
System.out.println(l1.getClass() == l2.getClass()); //True

3)instanceof

不能对确切的泛型类型使用instanceOf操作。如下面的操作是非法的,编译时会出错。

Collection cs = new ArrayList<String>();  
if (cs instanceof Collection<String>){…}// compile error.如果改成instanceof Collection<?>则不会出错。

4)泛型数组问题

不能创建一个确切泛型类型的数组。如下面代码会出错。

List[] lsa = new ArrayList[10]; //compile error.

       因为如果可以这样,那么考虑如下代码,会导致运行时错误。

List<String>[] lsa = new ArrayList<String>[10]; // 实际上并不允许这样创建数组  
Object o = lsa;  
Object[] oa = (Object[]) o;  
List<Integer>li = new ArrayList<Integer>();  
li.add(new Integer(3));  
oa[1] = li;// unsound, but passes run time store check  
String s = lsa[1].get(0); //run-time error - ClassCastException

       因此只能创建带通配符的泛型数组,如下面例子所示,这回可以通过编译,但是在倒数第二行代码中必须显式的转型才行,即便如此,最后还是会抛出类型转换异常,因为存储在lsa中的是List类型的对象,而不是List类型。最后一行代码是正确的,类型匹配,不会抛出异常。

ist<?>[] lsa = new List<?>[10]; // ok, array of unbounded wildcard type  
Object o = lsa;  
Object[] oa = (Object[]) o;  
List<Integer>li = new ArrayList<Integer>();  
li.add(new Integer(3));  
oa[1] = li; //correct  
String s = (String) lsa[1].get(0);// run time error, but cast is explicit  
Integer it = (Integer)lsa[1].get(0); // OK



更多Java泛型编程最全总结相关文章请关注PHP中文网!

声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
JVMは、Javaの「Write and、Run Anywhere」(Wora)機能にどのように貢献しますか?JVMは、Javaの「Write and、Run Anywhere」(Wora)機能にどのように貢献しますか?May 02, 2025 am 12:25 AM

JVMは、バイトコード解釈、プラットフォームに依存しないAPI、動的クラスの負荷を介してJavaのWORA機能を実装します。 2。標準API抽象オペレーティングシステムの違い。 3.クラスは、実行時に動的にロードされ、一貫性を確保します。

Javaの新しいバージョンは、プラットフォーム固有の問題にどのように対処しますか?Javaの新しいバージョンは、プラットフォーム固有の問題にどのように対処しますか?May 02, 2025 am 12:18 AM

Javaの最新バージョンは、JVMの最適化、標準的なライブラリの改善、サードパーティライブラリサポートを通じて、プラットフォーム固有の問題を効果的に解決します。 1)Java11のZGCなどのJVM最適化により、ガベージコレクションのパフォーマンスが向上します。 2)Java9のモジュールシステムなどの標準的なライブラリの改善は、プラットフォーム関連の問題を削減します。 3)サードパーティライブラリは、OpenCVなどのプラットフォーム最適化バージョンを提供します。

JVMによって実行されたバイトコード検証のプロセスを説明します。JVMによって実行されたバイトコード検証のプロセスを説明します。May 02, 2025 am 12:18 AM

JVMのバイトコード検証プロセスには、4つの重要な手順が含まれます。1)クラスファイル形式が仕様に準拠しているかどうかを確認し、2)バイトコード命令の有効性と正確性を確認し、3)データフロー分析を実行してタイプの安全性を確保し、検証の完全性とパフォーマンスのバランスをとる。これらの手順を通じて、JVMは、安全で正しいバイトコードのみが実行されることを保証し、それによりプログラムの完全性とセキュリティを保護します。

プラットフォームの独立性は、Javaアプリケーションの展開をどのように簡素化しますか?プラットフォームの独立性は、Javaアプリケーションの展開をどのように簡素化しますか?May 02, 2025 am 12:15 AM

java'splatformendencealLowsApplicationStorunOperatingSystemwithajvm.1)singlecodebase:writeandcompileonceforallplatforms.2)easyUpdates:updatebytecodeforsimultaneousdeployment.3)テストの実験効果:scalbortffortfforduniverbehaviol.4)

Javaのプラットフォームの独立性は、時間とともにどのように進化しましたか?Javaのプラットフォームの独立性は、時間とともにどのように進化しましたか?May 02, 2025 am 12:12 AM

Javaのプラットフォームの独立性は、JVM、JITコンピレーション、標準化、ジェネリック、ラムダ式、Projectpanamaなどのテクノロジーを通じて継続的に強化されています。 1990年代以来、Javaは基本的なJVMから高性能モダンJVMに進化し、さまざまなプラットフォームでのコードの一貫性と効率を確保しています。

Javaアプリケーションでプラットフォーム固有の問題を緩和するためのいくつかの戦略は何ですか?Javaアプリケーションでプラットフォーム固有の問題を緩和するためのいくつかの戦略は何ですか?May 01, 2025 am 12:20 AM

Javaはプラットフォーム固有の問題をどのように軽減しますか? Javaは、JVMおよび標準ライブラリを通じてプラットフォームに依存します。 1)bytecodeとjvmを使用して、オペレーティングシステムの違いを抽象化します。 2)標準のライブラリは、パスクラス処理ファイルパス、CHARSETクラス処理文字エンコードなど、クロスプラットフォームAPIを提供します。 3)最適化とデバッグのために、実際のプロジェクトで構成ファイルとマルチプラットフォームテストを使用します。

Javaのプラットフォームの独立性とマイクロサービスアーキテクチャの関係は何ですか?Javaのプラットフォームの独立性とマイクロサービスアーキテクチャの関係は何ですか?May 01, 2025 am 12:16 AM

java'splatformentencentenhancesmicroservicesecturectureby byofferingdeploymentflexability、一貫性、スケーラビリティ、およびポート可能性。1)展開の展開の展開は、AllosmicRoserviThajvm.2)deploymentflexibility lowsmicroserviceSjvm.2)一貫性のあるAcrossServicessimplisimpligiessdevelisementand

GraalvmはJavaのプラットフォーム独立目標とどのように関係していますか?GraalvmはJavaのプラットフォーム独立目標とどのように関係していますか?May 01, 2025 am 12:14 AM

Graalvmは、Javaのプラットフォームの独立性を3つの方法で強化します。1。言語間の相互運用性、Javaが他の言語とシームレスに相互運用できるようにします。 2。独立したランタイム環境、graalvmnativeimageを介してJavaプログラムをローカル実行可能ファイルにコンパイルします。 3.パフォーマンスの最適化、Graalコンパイラは、Javaプログラムのパフォーマンスと一貫性を改善するための効率的なマシンコードを生成します。

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

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

ホットツール

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

このプロジェクトは osdn.net/projects/mingw に移行中です。引き続きそこでフォローしていただけます。 MinGW: GNU Compiler Collection (GCC) のネイティブ Windows ポートであり、ネイティブ Windows アプリケーションを構築するための自由に配布可能なインポート ライブラリとヘッダー ファイルであり、C99 機能をサポートする MSVC ランタイムの拡張機能が含まれています。すべての MinGW ソフトウェアは 64 ビット Windows プラットフォームで実行できます。

EditPlus 中国語クラック版

EditPlus 中国語クラック版

サイズが小さく、構文の強調表示、コード プロンプト機能はサポートされていません

AtomエディタMac版ダウンロード

AtomエディタMac版ダウンロード

最も人気のあるオープンソースエディター

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 英語版

SublimeText3 英語版

推奨: Win バージョン、コードプロンプトをサポート!