【Java SE】认识泛型(上)

简介: 通过前面JavaSE的语法知识储备,如果现在让你们创建如标题一样的数组,你会怎么创建呢?

1、如何创建可以存放各种类型的数组?

通过前面JavaSE的语法知识储备,如果现在让你们创建如标题一样的数组,你会怎么创建呢?

答案是:使用 Object 类来定义数组,因为 Object 是所有类的父类, 可以接收任意子类对象,也即实现了向上转型,于是我们就写出了这样的代码:

private Object[] array = new Object[3];

那么这种方法可取吗?显然是可取的,但只是使用起来会很不方便,具体不方便在哪,我们接着往后看,在这里我们要写一个类,里面提供了获取array指定下标的数据,和设置array指定下标的数据,于是写出了这样的代码:

public class DrawForth {
    private Object[] array = new Object[3];
    public void setPosArray(int pos, Object o) {
        this.array[pos] = o;
    }
    public Object getPosValue(int pos) {
        return this.array[pos];
    }
}

代码到这里仍然是正确的,那我们就要去使用这个类,也就是在main方法中用这个类实例对象,去操作里面的数组,所以main方法的代码就是这个样子:

public static void main(String[] args) {
        DrawForth draw = new DrawForth();
        draw.setPosArray(0, 123);
        draw.setPosArray(1, "hello");
        draw.setPosArray(2, 12.5);
        int a = (int)draw.getPosValue(0);
        String str = (String)draw.getPosValue(1);
        double d = (double)draw.getPosValue(1);
    }

看到这里,你是不是就发现这样做很不方便呢?当我们往数组里面设置数据的时候开心了,想设置成什么类型就是什么类型,但是!当我们要获取对应位置的元素就麻烦了,我们必须知道他是什么类型,然后进行强制类型转换才能接收,(返回是Object类型所以需要强转),难道往后每次取数据的时候我还得看一看是什么类型吗?

2、泛型的概念

2.1 浅聊泛型

泛型是在JDK1.5引入的新的语法,通过上面的例子,由此我们就引出了泛型,泛型简单来说就是把类型当成参数传递,指定当前容器,你想持有什么类型的对象,你就传什么类型过去,让编译器去做类型检查!从而实现类型参数化(不能是基本数据类型,后面讲)

2.2 泛型的简单语法

class Test1<类型形参列表> {
}
class Test2<类型形参1, 类型形参2, ...> {
}

2.3 类型形参列表的命名规范

类名后面的 <类型形参列表> 这是一个占位符,表示当前类是一个泛型类,形参列表里面如何写?通常用一个大写字母表示,当然,你也可以怎么开心怎么来,但是小心办公室谈话警告哈(dog),这里有几个常用的名称:

image.png

2.4 使用泛型知识创建数组

这里就来修改一下刚开始的代码,使用到泛型的知识,那么我们就可以这样修改:

public class DrawForth<T> {
    //private T[] array = new T[3]; error
    private T[] array = (T[])new Object[3];
    public void setPosArray(int pos, T o) {
        this.array[pos] = o;
    }
    public T getPosValue(int pos) {
        return this.array[pos];
    }
    public static void main(String[] args) {
        DrawForth<Integer> draw = new DrawForth<>();
        draw.setPosArray(0, 123);
        //draw.setPosArray(1, "hello"); error
        //draw.setPosArray(2, 12.5); error
        draw.setPosArray(1, 1234);
        draw.setPosArray(2, 12345);
        int a = draw.getPosValue(0);
        int b = draw.getPosValue(1);
        int c = draw.getPosValue(2);
    }
}

如上修改之后的代码,我们可以得到以下知识点:

  • <T> 是一个占位符,仅表示这个类是泛型类
  • 不能 new 泛型数组(原因后面讲),此代码的写法也不是最好的方法!
  • 实例化泛型类的语法是:类名<类型实参>变量名 = new 泛型类<类型实参>(构造方法实参);
  • 注意:new 泛型类<>尖括号中可以省略类型实参,编译器可以根据上下文推导!
  • 编译时自动进行类型检查和转换。

2.5 什么是裸类型?

裸类型就是指在实例化泛型类对象的时候,没有传类型实参,比如下面的代码就是一个裸类型:

DrawForth draw = new DrawForth();

我现在可以告诉你,这样做编译完全正常,但我们不要去使用裸类型,因为这是为了兼容老版本的 API 保留的机制,毕竟泛型是 Java1.5 新增的语法。

3、泛型是如何编译的?

3.1 泛型的擦除机制

如果我们要看泛型是如何编译的,可以通过命令 javap -c 字节码文件 来进行查看:

如上代码是 2.4 段落中的代码,奇怪,明明传的实参是 Integer 类型,最后所有的 T 却变成了 Object 类型,这就是擦除机制,所以在Java中,泛型机制是在编译级别实现的,运行期间不会包含任何泛型信息。

提示:类型擦除,不一定是把 T 变成 Object(泛型的上界会提到)

3.2 再谈为什么不能实例化泛型数组?

知道了擦除机制后,那么 T[] array = new T[3]; 是不对的,编译的时候,替换为Object,不是相当于:Object[] array = new Object[3]吗?

在Java中,数组是一个很特殊的类型,数组是在运行时存储和检查类型信息, 泛型则是在编译时检查类型错误。而且Java设定擦除机制就只针对变量的类型和返回值的类型,所以在编译时候压根不会擦除 new T[3]; 这个 T ,所以自然编译就会报错!

我们前面通过强制类型转换的方式创建了泛型数组,说过那样写并不好,正确的方式是通过反射创建指定类型的数组,由于现在没学习到反射,这里先放着就行。

3.3 什么是泛型的上界?

有了擦除机制的学习,泛型在运行时都会被擦除成 Object 但是并不是所有的都是这样,泛型的上界就是对泛型类传入的类型变量做一定的约束,可以通过类型边界来进行约束。

语法:

class 泛型类名称<类型形参 extends类型边界> {
   //...code
}

这里我们来举两个例子:

例1:

这里简单分析一下,Student 继承了 Person 类,而 Teacher 没有继承 Person 类,接着 Test 类给定了泛型的上界, 那么 Test 类中 <> 里面是什么意思呢?表示只接收 Person 或 Person 的子类作为 T 的类型实参。

通过 main 方法中的例子也可也看出,类型传参只能传 Person 或 Person 的子类。

例2:

还是简单分析一下,Student 类实现了 Comparable 接口,而 Teacher 类并没有实现, 接着 Test 类给定了泛型的上界, 那么 Test 类中 <> 里面是什么意思呢?表示 T 接收的类型必须是实现 Comparable 这个接口的!

通过 main 方法中的例子也可也看出,类型传参只能传实现了 Comparable 接口的类 。

注意:如果泛型类没有指定边界,则可以默认视为 T extends Object。

3.4 再谈擦除机制

如果给泛型设置了上界,则会擦除到边界处,也就不会擦除成 Object!

class Person {}
class Student extends Person {}
public class Main<T extends Person> {
    T array[] = (T[])new Object[10];
    public static void main(String[] args) {
        Main<Student> main = new Main<>();
    }
}

这里 Main 方法中设定了泛型的上界,传的类型实参必须是Person的子类,所以编译时会不会被擦除成 Person呢?下面我们查看一下对应的字节码文件:

显而易见,确实被擦除成了泛型的上界!

相关文章
|
3月前
|
安全 Java
Java之泛型使用教程
Java之泛型使用教程
286 10
|
3月前
|
安全 Java API
Java SE 与 Java EE 区别解析及应用场景对比
在Java编程世界中,Java SE(Java Standard Edition)和Java EE(Java Enterprise Edition)是两个重要的平台版本,它们各自有着独特的定位和应用场景。理解它们之间的差异,对于开发者选择合适的技术栈进行项目开发至关重要。
466 1
|
5月前
|
安全 Java API
在Java中识别泛型信息
以上步骤和示例代码展示了怎样在Java中获取泛型类、泛型方法和泛型字段的类型参数信息。这些方法利用Java的反射API来绕过类型擦除的限制并访问运行时的类型信息。这对于在运行时进行类型安全的操作是很有帮助的,比如在创建类型安全的集合或者其他复杂数据结构时处理泛型。注意,过度使用反射可能会导致代码难以理解和维护,因此应该在确有必要时才使用反射来获取泛型信息。
231 11
|
6月前
|
设计模式 算法 Java
Java SE 与 Java EE 组件封装使用方法及实践指南
本指南详细介绍了Java SE与Java EE的核心技术使用方法及组件封装策略。涵盖集合框架、文件操作、Servlet、JPA、EJB和RESTful API的使用示例,提供通用工具类与基础组件封装建议,如集合工具类、文件工具类、基础Servlet、实体基类和服务基类等。同时,通过分层架构集成示例展示Servlet、EJB和JPA的协同工作,并总结组件封装的最佳实践,包括单一职责原则、接口抽象、依赖注入、事务管理和异常处理等。适合希望提升代码可维护性和扩展性的开发者参考。
212 0
|
存储 安全 Java
🌱Java零基础 - 泛型详解
【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
160 1
|
安全 Java 编译器
Java基础-泛型机制
Java基础-泛型机制
114 0
|
存储 Java 编译器
Java集合定义其泛型
Java集合定义其泛型
113 1
|
Java 语音技术 容器
java数据结构泛型
java数据结构泛型
128 5
|
存储 Java 编译器
【用Java学习数据结构系列】初识泛型
【用Java学习数据结构系列】初识泛型
115 2
|
Java
【Java】什么是泛型?什么是包装类
【Java】什么是泛型?什么是包装类
152 0