泛型的定义、使用及其擦除机制

1. 什么是泛型

1.1 泛型的来源

在 Java 编程思想中, 有这样一句对泛型介绍的话: 一般的类和方法, 只能使用具体的类型: 要么是基本类型, 要么是自定义的类. 如果要编写可以应用于多种类型的代码, 这种刻板的限制对代码束缚就会很大.

于是为了解决这样的一个问题, 在 JDK1.5 中就引入了一种新的语法, 那就是泛型. 泛型在代码层面上就是对类型实现了参数化.

1.2 引入泛型

问题引入1:

public class Test {
    public static void func(int val) {
        System.out.println(val);
    }

    public static void main(String[] args) {
        func(1);
    }
}

在如上的代码中,有一个 func 方法, 但是这个方法只能传递 int 类型的参数, 但是如果我有需求让 func 方法可以对多种参数都能起到一个处理的作用, 那么我则需要这个 func 方法能够接收多种参数.

在还没接触到泛型的时候, 其实是可以使用方法的重载来让一个方法接收多种不同类型的参数, 如下所示:

public class Test {
    // 接收 int 类型的参数
    public static void func(int val) {
        System.out.println(val);
    }
    
    // 接收 String 类型的参数
    public static void func(String val) {
        System.out.println(val);
    }

    // 接收 double 类型的参数
    public static void func(double val) {
        System.out.println(val);
    }
    
    public static void main(String[] args) {
        // 传递 int 类型的值
        func(1);
        
        // 传递 String 类型的值
        func("Alex");
        
        // 传递 double 类型的值
        func(1.2);
    }
}

这样子就能够使用方法的重载来让 func 可以接收多种类型的值了. 但是使用这种方法, 需要写出很多重复性代码, 甚至还有可能每一个重载方法都需要类型的强转, 在代码量上出现许多冗余的代码, 并且代码的可维护性低, 成本高(如果需要让 func 方法在后期加上另外的功能, 需要在每一个重载方法上都进行改变).

问题引入2:

实现一个类, 类中包含一个数组成员, 使得该数组能够存放任意类型的数据, 也可以根据类中的方法来返回数组中指定下标的值.

先来拆解一下问题, 怎么指定一个数组, 来让这个数组可以存放不同类型的值?
这里可以想到使用 Object 数组. 因为 Object 类是所有类的父类, 所有类都可以向上转型为 Object 类, 使得 Object 数组可以存放任意的数据

	Object[] arr = {1, "Alex", 1.2};

上面这一行代码是不会报错的, 这样我们就解决了这个问题中其中一小个问题.

接下来, 整个问题的实现思路:

class MyArray {
    Object[] arr = new Object[100];

    public void setVal(int pos, Object val) {
    	arr[pos] = val;
    }

    public Object getVal(int pos) {
        return arr[pos];
    }
}

这样, 就可以使用这个类来存放数据了.

    MyArray array = new MyArray();

    array.setVal(0, 1);
    array.setVal(1, "Alex");
    array.setVal(2, 1.2);

但是如果使用一个变量来接收这个下标的值就需要进行强转了

    int val1 = (int) array.getVal(1);
    String val2 = (String) array.getVal(2);
    double val3 = (double) array.getVal(3);

所以上述代码可以看出, 将元素存放进去的时候很方便, 但是要将它取出来的时候就变得麻烦. 这不仅降低了代码的开发效率, 也影响了代码本身的可维护性.

使用 Object 数组的缺点:

  1. 存放元素的时候, 任何的元素都可以存放进去.
  2. 取出元素的时候, 需要进行类型的强转.

这就引出了上面描述泛型的定义: 在代码层面上, 将类型参数化. 那么什么是类型的参数化呢? 从上述两个例子中可以看出来, 想要实现一个方法能够接收多种不同类型的参数进行处理, 那么就可以在传参的过程中将传递的参数的类型也传递过去, 可以形象化的表示为: 传递的参数的类型也可以是一个参数一起传递过去. 这就说明了上面所说的 <将类型参数化> 了.

2. 泛型的使用

泛型类的写法

根据上面的 MyArray 类, 改成一个具有泛型效果的类

static class MyArray<T> {
    T[] arr = (T[]) new Object[100];

    public void setVal(int pos, T val) {
    	arr[pos] = val;
    }

    public T getVal(int pos) {
    	return arr[pos];
    }
}

这样, 这个类就是一个泛型类

其中, 是一个占位符, 代表着这个类是一个泛型类.

泛型类的使用

public static void main(String[] args) {
    MyArray<Integer> myArray1 = new MyArray<>();

    myArray1.setVal(0, 0);
    myArray1.setVal(1, 1);
    myArray1.setVal(2, 2);
    int val1 = myArray1.getVal(0);

    MyArray<String> myArray2 = new MyArray<>();
        
    myArray2.setVal(0, "Alex");
    myArray2.setVal(1, "Alice");
    myArray2.setVal(2, "Mana");
    String val2 = myArray2.getVal(0);
}

泛型存在的两个最大的意义:

  1. 存放元素的时候, 会进行类型的检查.
  2. 取出元素的时候, 会自动进行类型的转换. 不需要进行强转.
    这都是在编译期间的一种机制 -> 擦除机制

3. 泛型的擦除机制

上面说到, 泛型是在编译期间所使用的一种语法, 在运行期间是没有泛型这个概念的, 可以来看一下上面的 MyArray 的字节码文件.
在这里插入图片描述
从上述图片可以看出, 编译之后的字节码文件中并没有 的影子, 全部都被擦除成了 Object. 这就是泛型的擦除机制. 编译生成的字节码在运行过程中并不包含泛型的信息.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值