Jadual Kandungan
基本概念
1.发展
2.术语
3.探究
类型限定
1.不对类型参数设置界限
2.对类型参数设置界限
类型擦除
1.原始类型
2.类型参数的类型
3.类型检查
4.类型擦除与多态的冲突
注意事项
Rumah Java javaTutorial 11.Java 基础 - 泛型

11.Java 基础 - 泛型

Feb 27, 2017 am 10:43 AM

基本概念

泛型的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型。

这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法


1.发展

在 JDK 1.5 之前,只能通过 Object 是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化。

因此在编译期间,编译器无法检查这个 Object 的强制转型是否成功,这样容导致发生 ClassCastException (强制转换异常) 。

下面我们来看一个实例,就能知道泛型的作用:

  • 不使用泛型的情况(1.5 之前)

ArrayList arrayList = new ArrayList();
arrayList.add(100);
arrayList.add("abc");//因为不知道取出来的值的类型,类型转换的时候容易出错  String str = (String) arrayList.get(0);
Salin selepas log masuk
  • 使用泛型的情况(1.5 之后)

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("abc");//因为限定了类型,所以不能添加整形,编译器会提示出错arrayList.add(100);
Salin selepas log masuk

2.术语

// 以 ArrayList<E>,ArrayList<Integer> 为例:ArrayList<E>:泛型类型

E:类型变量(或者类型参数)

ArrayList<Integer> :参数化的类型

Integer:类型参数的实例(或实际类型参数)

ArrayList :原始类型
Salin selepas log masuk

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
    }
}
Salin selepas log masuk

泛型接口

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);
    }
}
Salin selepas log masuk

泛型方法

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;
    }
}
Salin selepas log masuk

类型限定

类型限定在泛型类、泛型接口和泛型方法中都可以使用,不过要注意下面几点:

  • 不管该限定是类还是接口,统一都使用关键字 extends

  • 可以使用 & 符号给出多个限定

  • 如果限定既有接口也有类,那么类必须只有一个,并且放在首位置。例如:

public static <T extends Comparable&Serializable> T get(T t1,T t2)
Salin selepas log masuk

下面再来分析下类型限定的作用…


1.不对类型参数设置界限

观察下面的代码,在没有对类型参数进行类型限定时会出现编译错误。原因如下:

  • 因为在编译之前,编译器并不能确认泛型类型(T)是什么类型

  • 因此它默认 T 为原始类型(Object)。

  • 所以只能调用 Object 的方法,而不能调用 compareTo 方法。

public static <T> T get(T t1,T t2) {    //编译错误
    if(t1.compareTo(t2)>=0);    return t1;
}
Salin selepas log masuk

2.对类型参数设置界限

当对类型参数 T 设置界限(bound)后,编译错误不再发生。因为此时编译器默认 T 的原始类型为 Comparable。

public static <T extends Comparable> T get(T t1,T t2) {    if(t1.compareTo(t2)>=0);    return t1;
}
Salin selepas log masuk

类型擦除

  • 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());
    }
}
Salin selepas log masuk

观察代码,这里定义了两个ArrayList数组:

  • 一个是ArrayList泛型类型,只能存储字符串,一个是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));  
            }  
    }
}
Salin selepas log masuk

观察代码,这里定义了一个ArrayList 泛型类型实例化为 Integer 的对象

  • 如果直接调用 add 方法,那么只能存储整形的数据。

  • 利用反射调用 add 方法,却可以存储字符串。

  • 说明 Integer 泛型实例在编译之后被擦除了,只保留了原始类型。


1.原始类型

  • 原始类型(raw type)就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型

  • 任意一个泛型的类型参数,都存在对应的原始变量。

  • 一旦类型变量被擦除(crased),就会使用其限定类型(无限定的变量用 Object)替换。

// 此时 T 是一个无限定类型,所以原始类型就是 Objectclass Pair<T> { } 

// 类型变量有限定,原始类型就用第一个边界的类型变量来替换,即Comparableclass Pair<T extends Comparable& Serializable> { }  

// 此时原始类型为 Serializable,编译器在必要的时要向 Comparable 插入强制类型转换
// 为了提高效率,应该将标签(tagging)接口(即没有方法的接口)放在边界限定列表的末尾class Pair<T extends Serializable&Comparable>
Salin selepas log masuk

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);
    }

}
Salin selepas log masuk

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);  // 编译错误
Salin selepas log masuk

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();}
    }
Salin selepas log masuk

在上面提到过泛型的类型参数在编译时会被类型擦除,因此编译后的 Parent 类如下:

class Parent {    private Object value;    public Object getValue() {        return value;
    }    public void setValue(Object value) {        this.value = value;
    }
}
Salin selepas log masuk

此时对比 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());
    }
}
Salin selepas log masuk

那么问题来了,通过上面的分析?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        }
Salin selepas log masuk

发现这里共有 4 个 setValue/getValue 方法,除了 Son 表面上重写的 String 类型,编译器又自己生成了 Object 类型的方法,也称为桥方法。结果就是,编译器通过桥方法真正实现了重写,只是在访问时又去调用表面的定义的方法。


注意事项

  • 不能用基本类型实例化类型参数,可以用对应的包装类来实例化类型参数

// 编译错误ArrayList<int> list = new ArrayList<int>();// 正确写法ArrayList<Integer> list = new ArrayList<Integer>();
Salin selepas log masuk
  • 参数化类型的数组不合法

Demo<T >{
}public class Test {
    public static void main(String[] args) {        // 编译错误 --> 类型擦除导致数组变成 Object [],因此没有意义
        Demo<String>[ ]  demo =new Demo[10];
    }
}
Salin selepas log masuk
  • 不能实例化类型变量

// 编译错误,需要类型参数需要确定类型Demo<T> demo = new Demo<T>
Salin selepas log masuk
  • 泛型类的静态上下文中不能使用类型变量

public class Demo<T> {
    public static T name;    public static T getName() {
        ...
    }
}
Salin selepas log masuk
  • 不能抛出也不能捕获泛型类的对象

//异常都是在运行时捕获和抛出的,而在编译的时候,泛型信息全都会被擦除掉。会导致这里捕获的类型一致try{  
}catch(Problem<Integer> e1){  
    //do Something... }catch(Problem<Number> e2){  
    // do Something ...}
Salin selepas log masuk

 以上就是11.Java 基础 - 泛型的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

AI Hentai Generator

AI Hentai Generator

Menjana ai hentai secara percuma.

Artikel Panas

Repo: Cara menghidupkan semula rakan sepasukan
1 bulan yang lalu By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Kristal tenaga dijelaskan dan apa yang mereka lakukan (kristal kuning)
2 minggu yang lalu By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island Adventure: Cara mendapatkan biji gergasi
1 bulan yang lalu By 尊渡假赌尊渡假赌尊渡假赌

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Akar Kuasa Dua di Jawa Akar Kuasa Dua di Jawa Aug 30, 2024 pm 04:26 PM

Panduan untuk Square Root di Java. Di sini kita membincangkan cara Square Root berfungsi di Java dengan contoh dan pelaksanaan kodnya masing-masing.

Nombor Sempurna di Jawa Nombor Sempurna di Jawa Aug 30, 2024 pm 04:28 PM

Panduan Nombor Sempurna di Jawa. Di sini kita membincangkan Definisi, Bagaimana untuk menyemak nombor Perfect dalam Java?, contoh dengan pelaksanaan kod.

Penjana Nombor Rawak di Jawa Penjana Nombor Rawak di Jawa Aug 30, 2024 pm 04:27 PM

Panduan untuk Penjana Nombor Rawak di Jawa. Di sini kita membincangkan Fungsi dalam Java dengan contoh dan dua Penjana berbeza dengan contoh lain.

Nombor Armstrong di Jawa Nombor Armstrong di Jawa Aug 30, 2024 pm 04:26 PM

Panduan untuk Nombor Armstrong di Jawa. Di sini kita membincangkan pengenalan kepada nombor Armstrong di java bersama-sama dengan beberapa kod.

Weka di Jawa Weka di Jawa Aug 30, 2024 pm 04:28 PM

Panduan untuk Weka di Jawa. Di sini kita membincangkan Pengenalan, cara menggunakan weka java, jenis platform, dan kelebihan dengan contoh.

Nombor Smith di Jawa Nombor Smith di Jawa Aug 30, 2024 pm 04:28 PM

Panduan untuk Nombor Smith di Jawa. Di sini kita membincangkan Definisi, Bagaimana untuk menyemak nombor smith di Jawa? contoh dengan pelaksanaan kod.

Soalan Temuduga Java Spring Soalan Temuduga Java Spring Aug 30, 2024 pm 04:29 PM

Dalam artikel ini, kami telah menyimpan Soalan Temuduga Spring Java yang paling banyak ditanya dengan jawapan terperinci mereka. Supaya anda boleh memecahkan temuduga.

Cuti atau kembali dari Java 8 Stream Foreach? Cuti atau kembali dari Java 8 Stream Foreach? Feb 07, 2025 pm 12:09 PM

Java 8 memperkenalkan API Stream, menyediakan cara yang kuat dan ekspresif untuk memproses koleksi data. Walau bagaimanapun, soalan biasa apabila menggunakan aliran adalah: bagaimana untuk memecahkan atau kembali dari operasi foreach? Gelung tradisional membolehkan gangguan awal atau pulangan, tetapi kaedah Foreach Stream tidak menyokong secara langsung kaedah ini. Artikel ini akan menerangkan sebab -sebab dan meneroka kaedah alternatif untuk melaksanakan penamatan pramatang dalam sistem pemprosesan aliran. Bacaan Lanjut: Penambahbaikan API Java Stream Memahami aliran aliran Kaedah Foreach adalah operasi terminal yang melakukan satu operasi pada setiap elemen dalam aliran. Niat reka bentuknya adalah

See all articles