Java泛型【了解后读懂源码为目的】

本文详细介绍了Java中的泛型,包括泛型类、泛型方法、通配符及其上下界,强调了泛型在类型检查和转换中的作用。通过实例展示了泛型的使用,如泛型类的边界设定,以及通配符在读写操作中的不同适用场景。此外,还讨论了泛型的限制,如无法创建泛型静态变量、数组等,并给出了泛型使用的一些建议。
摘要由CSDN通过智能技术生成

1. 范型的定义和使用

class MyStack<T>{
	//编译的时候 T 被擦除为 Object
    private T[] e;
    private int usedSize;

    MyStack() {
        this.e = (T[])new Object[5];
    }

    void push(T val){
        this.e[this.usedSize++] = val;
    }

    T pop(){
        return this.e[this.usedSize-1];
    }
}

public class main {
    public static void main(String[] args) {
    	//编译的时候 Integer 被擦除为 Object
        MyStack<Integer> stack = new MyStack<>();
        stack.push(1);
        System.out.println(stack.pop());
    }
}

输出:
1

T 只是一个占位符
泛型的意义: 1⃣️自动进行类型检查, 2⃣️自动进行类型转换

范型是怎么编译的?

泛型是编译期间的一种机制【擦除机制】: 在编译的时候会讲 T 擦除为 Object 类型

stack.push("Hello");// 编译会报错

有同学可能会想, 既然 Integer 会被擦除为 Object 类型, 为何不能赋值一个 String 对象呢?
其实这就是泛型的意义1⃣️的作用, 它会拿着 Integer 来进行对传入的参数进行检测, 提前告知我们程序错误.

也有一种称为 裸类型 的泛型
在定义的时候不穿入任何包装类:MyStack stack = new MyStack();

class MyStack<T>{
    private T[] e;
    private int usedSize;

    MyStack() {
        this.e = (T[])new Object[5];
    }

    void push(T val){
        this.e[this.usedSize++] = val;
    }

    T pop(){
        return this.e[--this.usedSize];
    }
}
public class main {
    public static void main(String[] args) {
        MyStack stack = new MyStack();
        stack.push(1);
        stack.push("Hello");
        System.out.println(stack.pop());
        System.out.println(stack.pop());
    }
}

输出:
Hello
1

由于此类泛型意义不大, 最重要的是缺少了类型检测, 后续可能会引发未知的错误因此最好不要使用. 使用泛型的作用是使得程序在编译期可以检查出与类型相关的错误,但是如果使用了泛型数组,这种能力就会受到破坏【这也是无法直接创建泛型数组的原因】

T[] = new T[];// 代码会报错, 不可以直接创建泛型数组而是通过强转Object数组实现的创建泛型数组

泛型参数可以有多个而不一定是一个, 还可以继承父类
伪代码

class 泛型类名称<类型形参列表> { // 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> {
}

class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数*/
{
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
// 可以只使用部分类型参数
}
常见英文字母含义
EElement
KKey
VValue
NNumber
TType

2. 泛型方法

就是定义在类内部的static修饰的函数【类的静态方法】

实现一个类, 计算最大值

class Algorithm {
    static <T extends Comparable> T findMax(T[] arr) {// static里边定义泛型边界.
        T max = arr[0];
        for (int i = 0; i < arr.length; i++) {
            if (max.compareTo(arr[i]) < 0) {// 引用类型不是简单类型, 所以不能使用运算符来比较大小, 因此必须借用 Comparable 接口
                max = arr[i];
            }
        }
        return max;
    }
}

public class Demo07范型 {
    public static void main(String[] args) {
        Integer[] arr = {19, 91, 28, 82};
        Integer ret = Algorithm.findMax(arr);
        System.out.println(ret);
        String[] arr2 = {"AB", "CD", "EF"};
        String ret2 = Algorithm.findMax(arr2);
        System.out.println(ret2);
    }
}

91
EF

这里在进行类型擦除的时候擦除为 Comparable

3. 范型类的边界

为了了解边界的使用, 先看看这个问题【和泛型方法一样】

class Algorithm<T extends Comparable> {
    T findMax(T[] arr) {
        T max = arr[0];
        for (int i = 0; i < arr.length; i++) {
            if (max.compareTo(arr[i])<0) {
                max = arr[i];
            }
        }
        return max;
    }
}

public class main {
    public static void main(String[] args) {
        Integer[] arr = {19,91,28,82};
        Integer ret = new Algorithm<Integer>().findMax(arr);
        System.out.println(ret);
        String[] arr2 = {"AB", "CD", "EF"};
        String ret2 = new Algorithm<String>().findMax(arr2);
        System.out.println(ret2);
    }
}

输出
91
EF

3.1 通配符

class GenericPrint {
    static <T> void print(ArrayList<T> arrayList) {
        for (T obj : arrayList) {
            System.out.print(obj + " ");
        }
        System.out.println();
    }
}

public class main {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        GenericPrint.print(list);
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("Hello");
        list1.add("World");
        GenericPrint.print(list1);
    }
}

输出
1 2 3 4 
Hello World 

这里的 T 指定了是 Integer, String. 传入 ArrayList<T> arrayList 时候就知道打印的数据类型. 如果不指定类型会怎样呢?

class GenericPrint {
    static <T> void print(ArrayList<?> arrayList) {
        for (Object obj : arrayList) {
            System.out.print(obj + " ");
        }
        System.out.println();
    }
}

public class main {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        GenericPrint.print(list);
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("Hello");
        list1.add("World");
        GenericPrint.print(list1);
    }
}

输出
1 2 3 4 
Hello World 

这里给的是一个 Object 的类型, 所以可以接受任何类型的数据进行打印输出

那对于形参就没有区别吗?
我们看看 add, get 方法看看编译器的形参类型如何提示的
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

我们发现对于 get 方法是没有区别的 , 都是选取的 index 下标进行返回, 只不过对于有确定类型的就会返回确定的数据类型 T
add 方法 多了一个 caputre of ? e 这样的提示, 并没有一个确定的数据类型, 而指定泛型参数后就会有一个确定的形参类型
因此对于制定类型的泛型更适合写入数据也就是add操作【写入数据更明确】, 对于通配符的泛型更适合读取数据也就是get操作【读取数据不受规定泛型检查限制更方便】

因此对于 print 函数而言, 由于使用了通配符, 所以以下传参都是正确的

static <T> void print(ArrayList<?> arrayList)
print(new ArrayList<Integer>());
print(new ArrayList<Number>());
print(new ArrayList<String>());
print(new ArrayList<Object>());
...

3.2 通配符-上界

通配符的上下界规定了参数的"最大值"【不能超过上界的范围】

// 传入的参数必须是 Number 的子类型
static <T> void print(ArrayList<? extends Number> arrayList)
// 以下传参正确
print(new ArrayList<Integer>());
print(new ArrayList<Number>());
//以下传参会被泛型检查异常
print(new ArrayList<String>());
print(new ArrayList<Object>());

3.3 通配符-下界

通配符的上下界规定了参数的"最小值"【不能低于下界的范围】

// 传入的参数必须是 Integer 的父类型
static <T> void print(ArrayList<? super Integer> arrayList)
// 以下传参正确
print(new ArrayList<Integer>());
print(new ArrayList<Number>());
print(new ArrayList<Object>());
//以下传参会被泛型检查异常
print(new ArrayList<String>());
print(new ArrayList<Double>());

4. 泛型的父子类型

 class MyarrayList<T>{

}
public class main {
    public static void main(String[] args) {
        MyarrayList<Number> myarrayList = new MyarrayList<Integer>();// 虽然 Number 和 Integer是父子类关系, 但对于容器 myarrayList 而言它们之间不构成父子类关系
        MyarrayList<Object> myarrayList1 = new MyarrayList<Integer>();
        MyarrayList<? extends Number> myarrayList2 = new MyarrayList<Integer>();// 使用通配符来确定父子类关系
        MyarrayList<? extends Object> myarrayList3 = new MyarrayList<Integer>();
    }
}

在这里插入图片描述

5. 泛型限制

  1. 泛型类型参数不支持基本数据类型
  2. 无法实例化泛型类型的对象
  3. 无法使用泛型类型声明静态的属性
  4. 无法使用 instanceof 判断带类型参数的泛型类型
  5. 无法创建泛型类数组
  6. 无法 create、catch、throw 一个泛型类异常(异常不支持泛型)
  7. 泛型类型不是形参一部分,无法重载

小提示

区别点重载重写
参数列表必须修改一定不能修改
返回值要求可以修改一定不能修改
异常可以修改可以减少或删除,一定不能抛出新的或者更广的异常
访问可以修改一定不能做更严格的限制(可以降低限制)

总结
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

  • 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。外壳与核心都改变
  • 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。外壳不变, 内部核心改变
  • 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
class MyarrayList<T> {
    //无法创建泛型静态变量
    static T t;
    //无法直接创建泛型数组
    T[] arr = new T[5];
    T[] arr1 = (T[]) new Object[5];
    // 当参数个数不同的时候可以重载
    private T func(T args) {
        return args;
    }
    private void func(T args1, T args2) {
    }
    // 当参数个数相同的时候无法重载
    private T func1(T args) {
        return args;
    }
    private void func1(T args1) {
    }

}
public class main {
    public static void main(String[] args) {
        MyarrayList<Integer> arr1 = new MyarrayList<>();// 泛型类型参数必须要是包装类
        try {// 泛型不支持类的异常捕获
            MyarrayList<int> arr;//泛型类型参数不支持基本数据类型
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 无法用 instaceof 判断泛型类型
        main test = new main();
        if (test instanceof main){}
        if (arr1 instanceof Integer){}
    }
}

在这里插入图片描述

这次文章篇幅较少, 旨在了解泛型和使用规范, 主要目的是理解泛型能够查看源码【以优先级队列位列/小堆】
在这里插入图片描述

PriorityQueue 是一个泛型, 继承了抽象队列 AbstractQueue 实现了 序列化 java.io.Serializable 接口
Comparator 形参是 泛型 E 的下边界, 传入的参数范围不能低于 E

Java泛型, 是不是很简单? 其实在实际应用中还有7 条建议【摘自 EffectiveJava中第五章-泛型】

  1. 请不要再新代码中使用原生代码
  2. 消除非受察警告
  3. 列表优先于数组
  4. 优先考虑泛型
  5. 优先考虑泛型方法
  6. 利用限制通配符提升 API 灵活性【对于编程而言, 灵活也可能是贬义词】
  7. 优先考虑类型安全的异构容器
  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值