IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java泛型 -> 正文阅读

[Java知识库]Java泛型

一、概述

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

一个例子(没有使用泛型):
优点:可以存储各种数据类型,因为默认情况下集合接受的是Object对象。
缺点:取出集合元素时需要人为的强制类型转化到具体的目标类型,很容易现“java.lang. ClassCast Exception”异常,而且无法使用具体类型实现的方法。

private static void genericTest() {
        List arrayList = new ArrayList();
        arrayList.add("总有刁民想害朕");
        arrayList.add("朕的大清亡了");
        arrayList.add(7);

        for (int i = 0; i < arrayList.size(); i++)
            System.out.println("泛型测试 item = " + arrayList.get(i));

//        for (int i = 0; i < arrayList.size(); i++)  //此时会报错,代码无法执行,
//        说明不使用泛型的情况下十五分使用具体引用类型的方法的
//            System.out.println("泛型测试 item的长度为:" + arrayList.get(i).length());
    }
    
结果:
泛型测试 item = 总有刁民想害朕
泛型测试 item = 朕的大清亡了
泛型测试 item = 7

使用泛型后:
优点:可以使用具体类的方法,代码更安全,编译器会自动检查集合所含的元素是否为一种类型;
缺点:集合中只能使用一种数据类型。

private static void genericTest2() {
        List<String> arrayList = new ArrayList<>();
        arrayList.add("总有刁民想害朕");
        arrayList.add("朕的大清亡了");
//        arrayList.add(7);//使用泛型后首先这里会报错,代码安全性提高。但同时一个集合里的元素只能是一种数据类型

        for (int i = 0; i < arrayList.size(); i++)
            System.out.println("泛型测试 item = " + arrayList.get(i));

        for (int i = 0; i < arrayList.size(); i++)//此时可以使用具体引用类型的方法了
            System.out.println("泛型测试 item的长度为:" + arrayList.get(i).length());
    }
    
结果:
泛型测试 item = 总有刁民想害朕
泛型测试 item = 朕的大清亡了
泛型测试 item的长度为:7
泛型测试 item的长度为:6

二、泛型的特性

????????Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
????????如在代码中定义List和List等类型,在编译后都会变成List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法在运行时刻出现的类型转换异常的情况,类型擦除也是Java的泛型与C++模板机制实现方式之间的重要区别。

所以什么是类型擦除?

????????类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。 类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。 类型擦除的主要过程如下: 1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。 2.移除所有的类型参数。

看看例子:

public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<String>();
        list1.add("abc");

        ArrayList<Integer> list2 = new ArrayList<Integer>();
        list2.add(123);
        
        System.out.println(list1.getClass() == list2.getClass());
}
    
结果:
true

说明:在这个例子中,我们定义了两个ArrayList数组,不过一个是ArrayList泛型类型的,只能存储字符串;一个是ArrayList泛型类型的,只能存储整数,最后,我们通过list1对象和list2对象的getClass()方法获取他们的类的信息,最后发现结果为true。说明泛型类型String和Integer都被擦除掉了,只剩下原始类型List。


第二个例子:通过反射添加其它类型元素

public class Test {
 
    public static void main(String[] args) throws Exception {
 
        ArrayList<Integer> list = new ArrayList<Integer>();
 
        list.add(1);  //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer
 
        list.getClass().getMethod("add", Object.class).invoke(list, "asd");
 
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
 
}

在程序中定义了一个ArrayList泛型类型实例化为Integer对象,如果直接调用add()方法,那么只能存储整数数据,不过当我们利用反射调用add()方法的时候,却可以存储字符串,这说明了Integer泛型实例在编译之后被擦除掉了,只保留了原始类型。

原始类型 :就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。


第三个例子:原始类型Object

class Pair<T> {  
    private T value;  
    public T getValue() {  
        return value;  
    }  
    public void setValue(T  value) {  
        this.value = value;  
    }  
}

Pair的原始类型为:

class Pair {  
    private Object value;  
    public Object getValue() {  
        return value;  
    }  
    public void setValue(Object  value) {  
        this.value = value;  
    }  
}

因为在Pair中,T 是一个无限定的类型变量,所以用Object替换,其结果就是一个普通的类,如同泛型加入Java语言之前的已经实现的样子。在程序中可以包含不同类型的Pair,如Pair或Pair,但是擦除类型后他们的就成为原始的Pair类型了,原始类型都是Object。

从上面的例2中,我们也可以明白ArrayList被擦除类型后,原始类型也变为Object,所以通过反射我们就可以存储字符串了。

如果类型变量有限定,那么原始类型就用第一个边界的类型变量类替换。

比如: Pair这样声明的话:

public class Pair<T extends Comparable> {}

那么原始类型就是Comparable

要区分原始类型和泛型变量的类型。

在调用泛型方法时,可以指定泛型,也可以不指定泛型。

  • 在不指定泛型的情况下,泛型变量的类型为该方法中的几种类型的同一父类的最小级,直到Object
  • 在指定泛型的情况下,该方法的几种类型必须是该泛型的实例的类型或者其子类
public class Test {  
    public static void main(String[] args) {  
 
        /**不指定泛型的时候*/  
        int i = Test.add(1, 2); //这两个参数都是Integer,所以T为Integer类型  
        Number f = Test.add(1, 1.2); //这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Number  
        Object o = Test.add(1, "asd"); //这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Object  
 
        /**指定泛型的时候*/  
        int a = Test.<Integer>add(1, 2); //指定了Integer,所以只能为Integer类型或者其子类  
        int b = Test.<Integer>add(1, 2.2); //编译错误,指定了Integer,不能为Float  
        Number c = Test.<Number>add(1, 2.2); //指定为Number,所以可以为Integer和Float  
    }  
 
    //这是一个简单的泛型方法  
    public static <T> T add(T x,T y){  
        return y;  
    }  
}

其实在泛型类中,不指定泛型的时候,也差不多,只不过这个时候的泛型为Object,就比如ArrayList中,如果不指定泛型,那么这个ArrayList可以存储任意的对象。

三、泛型的使用

1、泛型类

public class GenericClass<T> {
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static void main(String[] args) {
        GenericClass<String> genericClass=new GenericClass<>();
        genericClass.setData("Generic Class");
        System.out.println(genericClass.getData());
        
        GenericClass<Integer> genericClass2=new GenericClass<>();
        genericClass2.setData(985211520);
        System.out.println(genericClass2.getData());
    }
}

结果:
Generic Class
985211520

2、泛型接口

定义一个泛型接口:

public interface GenericInterface <T>{
    T getData();
}

实现泛型接口方式一:泛型类实现方式

public class ImplGenericInterface1<T> implements GenericInterface<T>{
    private T data;

    private void setData(T data) {
        this.data = data;
    }

    @Override
    public T getData() {
        return data;
    }

    public static void main(String[] args) {
        ImplGenericInterface1<String> implGenericInterface1 = new ImplGenericInterface1<>();
        implGenericInterface1.setData("Generic Interface1");
        System.out.println(implGenericInterface1.getData());
    }
}

结果:
Generic Interface1

实现泛型接口方式二:指定具体类型实现方式

public class ImplGenericInterface2 implements GenericInterface<String>{
    @Override
    public String getData() {
        return "Generic Interface2";
    }

    public static void main(String[] args) {
        ImplGenericInterface2 implGenericInterface2 = new ImplGenericInterface2();
        System.out.println(implGenericInterface2.getData());
    }
}

结果:
Generic Interface2

3、泛型方法

例一:

public class GenericMethod1 {
    private static int add(int a, int b) {
        System.out.println(a + "+" + b + "=" + (a + b));
        return a + b;
    }

    private static <T> T genericAdd(T a, T b) {
        System.out.println(a + "+" + b + "="+a+b);
        return a;
    }

    public static void main(String[] args) {
        GenericMethod1.add(1, 2);
        GenericMethod1.<String>genericAdd("a", "b");
        GenericMethod1.<Integer>genericAdd(1, 2);
    }
}

结果:
1+2=3
a+b=ab
1+2=12

例二:

public class GenericMethod2 {
    static class Animal {
        @Override
        public String toString() {
            return "Animal";
        }
    }

    static class Dog extends Animal {
        @Override
        public String toString() {
            return "Dog";
        }
    }

    static class Fruit {
        @Override
        public String toString() {
            return "Fruit";
        }
    }

    static class GenericClass<T> {

        public void show01(T t) {
            System.out.println(t.toString());
        }

        public <T> void show02(T t) {
            System.out.println(t.toString());
        }
    }

    public static void main(String[] args) {
        Animal animal = new Animal();
        Dog dog = new Dog();
        Fruit fruit = new Fruit();
        GenericClass<Animal> genericClass = new GenericClass<>();

        //泛型类在初始化时限制了参数类型
        genericClass.show01(dog);
//        genericClass.show01(fruit); //将会报错

        System.out.println("====================");
        //泛型方法的参数类型在使用时指定
        genericClass.show02(dog);
        genericClass.show02(fruit);

        System.out.println("====================");
        genericClass.<Animal>show02(animal);
        genericClass.<Animal>show02(dog);
        genericClass.<Fruit>show02(fruit);
//        genericClass.<Dog>show02(animal); //将会报错
    }
}

结果:
Dog
====================
Dog
Fruit
====================
Animal
Dog
Fruit

4、泛型的继承

泛型继承

保留父类泛型 ----> 泛型子类
不保留父类泛型 ----> 子类按需实现

子类重写父类的方法,泛型类型随父类而定 子类使用父类的属性,该属性类型随父类定义的泛型

public abstract class Father<T1, T2> {
    public T1 age;
    public abstract void test(T2 name);
}

// 保留父类泛型 ----》泛型子类
// 1)全部保留
class C1<T1, T2> extends Father<T1, T2> {
    @Override
    public void test(T2 name) {
        System.out.println(name);
    }
}

// 2) 部分保留
class C2<T1> extends Father<T1, Integer> {

    @Override
    public void test(Integer name) {
        System.out.println(name);
    }
}

// 不保留父类泛型 -----》子类按需实现
// 1)具体类型
class C3 extends Father<String, Integer> {

    @Override
    public void test(Integer name) {
        System.out.println(name);
    }
}

// 2)没有具体类型
// 泛型擦除:实现或继承父类的子类,没有指定类型,类似于Object
class C4 extends Father {

    @Override
    public void test(Object name) {
        System.out.println(name);
    }
    
}

四、Java中的泛型通配符

? 常用的 T,E,K,V,?
? 本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,? 是这样约定的:

  • ? 表示不确定的 java 类型
  • T (type) 表示具体的一个java类型
  • K V (key value) 分别代表java键值中的Key Value
  • E (element) 代表Element

1、? 无界通配符

现有一个父类 Animal 和几个子类,如狗、猫等,需要一个动物的列表,一般想法是这样的:

List<Animal> listAnimals

使用无界通配符是这样的:

List<? extends Animal> listAnimals

看个例子:

import java.util.ArrayList;
import java.util.List;

 /* 泛型的通配符 类型不确定,用于声明变量或者形参上面
  *
  * 不能使用在类上 或者  new 创建对象上
*/
public class Test{
    // 用在形参上
    public static void test(List<?> list) {

        List<?> list2; // 用在声明变量上
        list2 = new ArrayList<String>();
        list2 = new ArrayList<Integer>();
        list2 = new ArrayList<Object>();

    }

    public static void main(String[] args) {
        test(new ArrayList<String>());
        test(new ArrayList<Integer>());
    }
}

2、泛型限制的上下限

上限:extends —> 指定的类必须是继承某个类,或者实现了某个接口(不是implements),即<= —> 格式:? extends List

下限:super —> 即父类或本身 —> 格式:? super List

import java.util.ArrayList;
import java.util.List;

/**
 * @Author HM.ming
 * @Date 2022/1/1 15:42
 * @Version 1.0
 *
 * extends:泛型的上限 <= 一般用于限制操作 不能使用在添加数据上,一般都是用于数据的读取
 *
 *supper:泛型的上限 >= 即父类或自身。一般用于下限操作
 *
 */
public class Test<T extends Fruit> {
    private static void test02(List<? extends Fruit> list) {

    }

    private static void test03(List<? super Apple> list) {

    }

    public static void main(String[] args) {

        // 调用test02(),测试 extends  <=
        test02(new ArrayList<Fruit>());
        test02(new ArrayList<Apple>());
        test02(new ArrayList<ReadApple>());
        // test02(new ArrayList<Object>()); Object 不是 Fruit 的子类 ,编译不通过

        // 调用test03() ,测试super >=
        test03(new ArrayList<Apple>());
        test03(new ArrayList<Fruit>());
        //test03(new ArrayList<ReadApple>());  //ReadApple < apple,所以不能放入
    }

}

class Fruit {

}

class Apple extends Fruit {

}

class Pear extends Fruit {

}

class ReadApple extends Apple {

}

下面来理解一下 <T extends Comparable<? super T>>:
T表示任意字符名,extends对泛型上限进行了限制即T必须是Comparable<? super T>的子类,然后<? super T>表示Comparable<>中的类型下限为T!这样来看一段代码辅助理解:

import java.util.GregorianCalendar;


class Demo<T extends Comparable<? super T>>{}


public class Test1{
    public static void main(String[] args) {
    	Demo<GregorianCalendar> p = null; // 编译正确
    }

}

这个可以理解为<GregorianCalendar extends Comparable>是可以运行成功的!因为Calendar为GregorianCalendar 的父类并且GregorianCalendar 实现了Comparable。

如果是如下代码则运行不成功:

import java.util.GregorianCalendar;
class Demo<T extends Comparable<T>>{}
//这里把? super去掉了
public class Test
{
    public static void main(String[] args) {
        Demo<GregorianCalendar> p = null;
    }
}

编译会报错!因为<T extends Comparable>相当于<GregorianCalendar extends Comparable>但是GregorianCalendar并没有实现Comparable而是实现的Comparable,这里不在限制范围之内所以会报错!

五、泛型嵌套

从外向里取
School.java

public class School <T>{
    private T stu;

    public T getStu() {
        return stu;
    }

    public void setStu(T stu) {
        this.stu = stu;
    }
}

Student2.java

public class Student2 <T>{
    T score;

    public T getScore() {
        return score;
    }

    public void setScore(T score) {
        this.score = score;
    }
}

Demo05.java

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * @Author HM.ming
 * @Date 2022/1/2 9:56
 * @Version 1.0
 */
public class Demo05 {
    public static void main(String[] args) {
        Student2<String> student = new Student2<String>();
        student.setScore("优秀");
        System.out.println(student.getScore());

        //泛型嵌套
        School<Student2<String>> school = new School<Student2<String>>();
        school.setStu(student);

        String s = school.getStu().getScore(); //从外向里取
        System.out.println(s);

        // hashmap 使用了泛型的嵌套
        Map<String, String> map =  new HashMap<String,String>();
        map.put("a", "张三");
        map.put("b", "李四");
        Set<Entry<String, String>> set = map.entrySet();
        for (Entry<String, String> entry : set) {
            System.out.println(entry.getKey()+":"+entry.getValue());
        }

    }
}

结果:
优秀
优秀
a:张三
b:李四
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-01-30 18:48:07  更:2022-01-30 18:49:51 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:47:30-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码