ホームページ  >  記事  >  Java  >  5分でわかるJavaジェネリックの詳しい解説

5分でわかるJavaジェネリックの詳しい解説

高洛峰
高洛峰オリジナル
2016-12-19 15:22:121285ブラウズ

変数を使用する前に変数を定義する必要があることはわかっています。変数を定義するときは、そのデータ型とどのデータ型をどの値に割り当てるかを指定する必要があります。

ここで座標を表すクラスを定義したい場合、必要な座標のデータ型は整数、小数、文字列になります。例:

x = 10, y = 10

x = 12.88, y = 129.65

x = "東京 180 度"、y = "北緯 210 度"


さまざまなデータ型について、メソッドのオーバーロードに加えて、自動ボックス化や上方変換も使用できます。基本的なデータ型は自動的にボックス化され、対応するパッケージング クラスに変換できることがわかっています。オブジェクトはすべてのクラスの祖先クラスであり、任意のクラスのインスタンスはオブジェクト型にアップキャストできます。例:

int --> Integer - ->Object

double -->Double -->Object

String -->Object

このように、すべての種類のデータを受け取るには 1 つのメソッドを定義するだけで済みます。以下のコードを見てください:

public class Demo {
    public static void main(String[] args){
        Point p = new Point();

        p.setX(10);  // int -> Integer -> Object
        p.setY(20);
        int x = (Integer)p.getX();  // 必须向下转型
        int y = (Integer)p.getY();
        System.out.println("This point is:" + x + ", " + y);
       
        p.setX(25.4);  // double -> Integer -> Object
        p.setY("东京180度");
        double m = (Double)p.getX();  // 必须向下转型
        double n = (Double)p.getY();  // 运行期间抛出异常
        System.out.println("This point is:" + m + ", " + n);
    }
}

class Point{
    Object x = 0;
    Object y = 0;

    public Object getX() {
        return x;
    }
    public void setX(Object x) {
        this.x = x;
    }
    public Object getY() {
        return y;
    }
    public void setY(Object y) {
        this.y = y;
    }
}

上記のコードでは、座標を生成する際には問題ありませんが、座標を取り出す際には下向きの変換が必要になります。前述したように、下向きの変換にはリスクがあります。コンパイル中に見つけるのは簡単ではありません。例外は実行時にのみスローされるため、ダウンキャストの使用は避けてください。上記のコードを実行すると、12 行目で java.lang.ClassCastException 例外がスローされます。


それでは、オーバーロード (コードの重複) の使用を回避し、リスクを最小限に抑えることができるより良い方法はあるでしょうか?

はい、あらゆる種類のデータを受け入れることができるジェネリック クラス (Java クラス) を使用できます。いわゆる「ジェネリック」とは、「広範なデータ型」、つまりあらゆるデータ型を指します。

上記のコードを変更してジェネリッククラスを使用します:

public class Demo {
    public static void main(String[] args){
        // 实例化泛型类
        Point<Integer, Integer> p1 = new Point<Integer, Integer>();
        p1.setX(10);
        p1.setY(20);
        int x = p1.getX();
        int y = p1.getY();
        System.out.println("This point is:" + x + ", " + y);
       
        Point<Double, String> p2 = new Point<Double, String>();
        p2.setX(25.4);
        p2.setY("东京180度");
        double m = p2.getX();
        String n = p2.getY();
        System.out.println("This point is:" + m + ", " + n);
    }
}

// 定义泛型类
class Point<T1, T2>{
    T1 x;
    T2 y;
    public T1 getX() {
        return x;
    }
    public void setX(T1 x) {
        this.x = x;
    }
    public T2 getY() {
        return y;
    }
    public void setY(T2 y) {
        this.y = y;
    }
}

実行結果:

この点: 10, 20
この点: 25.4, 東京 180 度

上記のコードは、通常のクラスの定義と比較すると、クラス名の後にある は、データの値ではなく、データの型を渡すために使用されます。ジェネリックスでは、データの値をパラメーターを通じて渡すだけでなく、データの型もパラメーターを通じて渡すことができます。 T1、T2 はデータ型の単なるプレースホルダーであり、実行時に実際のデータ型に置き換えられます。

値パラメーター (通常パラメーターと呼ばれるもの) は (int x, double y) などの括弧で囲まれ、型パラメーター (汎用パラメーター) は山括弧で囲まれ、複数のパラメーターは 36bb01aaa5ad1f4b1d882cc7c50bdd15();
can数値の右側にあるデータ型ですが、次のように警告が生成されます:
className variable3e7ff64bb1c9404d6a7b37b44d70da47 = new className();

使用時にデータ型が指定されているため、ジェネリック クラスでは、他の型に値を割り当てると例外がスローされます。これらは下方変換を必要とせず、潜在的なリスクもありません。この記事の冒頭で紹介した自動ボックス化や上方変換よりも実用的です。

注:

ジェネリックは Java 1.5 の新機能であり、C++ テンプレートに基づいており、本質的にはパラメーター化された型のアプリケーションです。

型パラメーターは参照型を表すためにのみ使用でき、int、double、char などの基本型を表すことはできません。ただし、基本型を渡しても、対応するラッパー クラスに自動的にボックス化されるため、エラーは発生しません。

ジェネリック メソッド

ジェネリック クラスの定義に加えて、ジェネリック メソッドも定義できます。たとえば、座標を印刷するためのジェネリック メソッドを定義できます。

public class Demo {
public static void main(String[] args){
// 实例化泛型类
Point<Integer, Integer> p1 = new Point<Integer, Integer>();
p1.setX(10);
p1.setY(20);
p1.printPoint(p1.getX(), p1.getY());

Point<Double, String> p2 = new Point<Double, String>();
p2.setX(25.4);
p2.setY("东京180度");
p2.printPoint(p2.getX(), p2.getY());
}
}

// 定义泛型类
class Point<T1, T2>{
T1 x;
T2 y;
public T1 getX() {
return x;
}
public void setX(T1 x) {
this.x = x;
}
public T2 getY() {
return y;
}
public void setY(T2 y) {
this.y = y;
}

// 定义泛型方法
public <T1, T2> void printPoint(T1 x, T2 y){
T1 m = x;
T2 n = y;
System.out.println("This point is:" + m + ", " + n);
}
}

実行結果:

このポイントは: 10、20
このポイントは: 25.4、東京 180 度

上記のコードは、通常のパラメーターと型パラメーターの両方を持つジェネリック メソッド printPoint() を定義します。型パラメーターは、修飾子の後、戻り値の型の前に配置する必要があります。型パラメータを定義すると、パラメータ リスト、メソッド本体、および戻り値の型で使用できるようになります。

ジェネリック クラスを使用する場合とは異なり、ジェネリック メソッドを使用する場合は、パラメーターの型を指定する必要はありません。コンパイラーは、渡されたパラメーターに基づいて特定の型を自動的に見つけます。定義の違いを除けば、ジェネリック メソッドは通常のメソッドと同様に呼び出されます。

注: ジェネリック メソッドは必ずしもジェネリック クラスに関連しているわけではありません。ジェネリック メソッドには独自の型パラメータがあり、ジェネリック メソッドは通常のクラスでも定義できます。ジェネリック メソッド printPoint() の型パラメーター T1 と T2 は、ジェネリック クラス Point の T1 と T2 に必ずしも関連しているわけではありません。代わりに他の識別子を使用することもできます。

public static <V1, V2> void printPoint(V1 x, V2 y){
V1 m = x;
V2 n = y;
System.out.println("This point is:" + m + ", " + n);
}

泛型接口

在Java中也可以定义泛型接口,这里不再赘述,仅仅给出示例代码:

public class Demo {
public static void main(String arsg[]) {
Info<String> obj = new InfoImp<String>("www.weixueyuan.net");
System.out.println("Length Of String: " + obj.getVar().length());
}
}

//定义泛型接口
interface Info<T> {
public T getVar();
}

//实现接口
class InfoImp<T> implements Info<T> {
private T var;

// 定义泛型构造方法
public InfoImp(T var) {
this.setVar(var);
}

public void setVar(T var) {
this.var = var;
}

public T getVar() {
return this.var;
}
}

运行结果:
Length Of String: 18

类型擦除

如果在使用泛型时没有指明数据类型,那么就会擦除泛型类型,请看下面的代码:

public class Demo {
public static void main(String[] args){
Point p = new Point();  // 类型擦除
p.setX(10);
p.setY(20.8);
int x = (Integer)p.getX();  // 向下转型
double y = (Double)p.getY();
System.out.println("This point is:" + x + ", " + y);
}
}

class Point<T1, T2>{
T1 x;
T2 y;
public T1 getX() {
return x;
}
public void setX(T1 x) {
this.x = x;
}
public T2 getY() {
return y;
}
public void setY(T2 y) {
this.y = y;
}
}

运行结果:
This point is:10, 20.8

因为在使用泛型时没有指明数据类型,为了不出现错误,编译器会将所有数据向上转型为 Object,所以在取出坐标使用时要向下转型,这与本文一开始不使用泛型没什么两样。

限制泛型的可用类型

在上面的代码中,类型参数可以接受任意的数据类型,只要它是被定义过的。但是,很多时候我们只需要一部分数据类型就够了,用户传递其他数据类型可能会引起错误。例如,编写一个泛型函数用于返回不同类型数组(Integer 数组、Double 数组、Character 数组等)中的最大值:

public <T> T getMax(T array[]){
T max = null;
for(T element : array){
max = element.doubleValue() > max.doubleValue() ? element : max;
}
return max;
}

上面的代码会报错,doubleValue() 是 Number 类的方法,不是所有的类都有该方法,所以我们要限制类型参数 T,让它只能接受 Number 及其子类(Integer、Double、Character 等)。

通过 extends 关键字可以限制泛型的类型,改进上面的代码:

public <T extends Number> T getMax(T array[]){
T max = null;
for(T element : array){
max = element.doubleValue() > max.doubleValue() ? element : max;
}
return max;
}

ae70bf429ee8ba84ce42dadb64335cd7 表示 T 只接受 Number 及其子类,传入其他类型的数据会报错。这里的限定使用关键字 extends,后面可以是类也可以是接口。但这里的 extends 已经不是继承的含义了,应该理解为 T 是继承自 Number 类的类型,或者 T 是实现了 XX 接口的类型。



更多Java泛型详解相关文章请关注PHP中文网!

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。