【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呢?下面我们查看一下对应的字节码文件:

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

相关文章
|
21天前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
43 2
|
17天前
|
安全 Java Go
Java&Go泛型对比
总的来说,Java和Go在泛型的实现和使用上各有特点,Java的泛型更注重于类型安全和兼容性,而Go的泛型在保持类型安全的同时,提供了更灵活的类型参数和类型集的概念,同时避免了运行时的性能开销。开发者在使用时可以根据自己的需求和语言特性来选择使用哪种语言的泛型特性。
33 7
|
1月前
|
存储 算法 Java
14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
36 2
14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
|
16天前
|
存储 安全 Java
如何理解java的泛型这个概念
理解java的泛型这个概念
|
20天前
|
存储 缓存 Java
|
21天前
|
安全 Java
【Java 第六篇章】泛型
Java泛型是自J2 SE 1.5起的新特性,允许类型参数化,提高代码复用性与安全性。通过定义泛型类、接口或方法,可在编译时检查类型安全,避免运行时类型转换异常。泛型使用尖括号`&lt;&gt;`定义,如`class MyClass&lt;T&gt;`。泛型方法的格式为`public &lt;T&gt; void methodName()`。通配符如`?`用于不确定的具体类型。示例代码展示了泛型类、接口及方法的基本用法。
10 0
|
22天前
|
Java
【Java基础面试四十五】、 介绍一下泛型擦除
这篇文章解释了Java泛型的概念,它解决了集合类型安全问题,允许在创建集合时指定元素类型,避免了类型转换的复杂性和潜在的异常。
|
22天前
|
Java
【Java基础面试四十四】、 说一说你对泛型的理解
这篇文章解释了Java泛型的概念,它解决了集合类型安全问题,允许在创建集合时指定元素类型,避免了类型转换的复杂性和潜在的异常。
|
28天前
|
Java
【Java】内部类、枚举、泛型
【Java】内部类、枚举、泛型
|
2月前
|
安全 Java
Java进阶之泛型
【7月更文挑战第10天】Java泛型,自Java 5引入,旨在提升类型安全和代码重用。通过泛型,如List&lt;String&gt;,可在编译时捕获类型错误,防止ClassCastException。泛型包括泛型类、接口和方法,允许定义参数化类型,如`class className&lt;T&gt;`,并用通配符&lt;?&gt;、extends或super限定边界。类型擦除确保运行时兼容性,但泛型仅做编译时检查。使用泛型能增强类型安全性,减少强制转换,提高性能。
28 1