对泛型的认识

简介: 对泛型的认识

1.什么是泛型

一般的类和方法,只能使用 具体的类型 : 要么是基本类型,要么是自定义的类。 如果要 编写可以应用于多种类型的

代码,这种刻板的限制对代码的束缚就会很大 。 ----- 来源《 Java 编程思想》对泛型的介绍。

泛型是在 JDK1.5 引入的新的语法,通俗讲,泛型: 就是 适用于许多许多类型 。从代码上讲,就是对类型实现了参数

化。

2.引出泛型

实现一个 类 ,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个

下标的值?

思路:

1. 我们以前学过的数组,只能存放指定类型的元素,例如: int[] array = new int[10]; String[] strs = new

String[10];

2. 所有类的父类,默认为 Object 类。数组是否可以创建为 Object?

问题:以上代码实现后 发现

1. 任何类型数据都可以存放

2. 1 号下标本身就是字符串,但是确 编译报错。必须进行强制类型转换

虽然在这种情况下,当前数组任何数据都可以存放,但是,更多情况下,我们还是希望他只能够持有一种数据类

型。而不是同时持有这么多类型。 所以,泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译

器去做检查。 此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型(即指定特定类型的参数)

2.1 语法

class 泛型类名称 < 类型形参列表 > {

// 这里可以使用类型参数

}

class ClassName < T1 , T2 , ..., Tn > {

}

class 泛型类名称 < 类型形参列表 > extends 继承类 /* 这里可以使用类型参数 */ {

// 这里可以使用类型参数

}

class ClassName < T1 , T2 , ..., Tn > extends ParentClass < T1 > {

// 可以只使用部分类型参数

}

代码实例

class MyArray < T > {

public T [] array = ( T []) new Object [ 10 ]; //1

public T getPos ( int pos ) {

return this . array [ pos ];

}

public void setVal ( int pos , T val ) {

this . array [ pos ] = val ;

}

}

public class TestDemo {

public static void main ( String [] args ) {

MyArray < Integer > myArray = new MyArray <> (); //2

myArray . setVal ( 0 , 10 );

myArray . setVal ( 1 , 12 );

int ret = myArray . getPos ( 1 ); //3

System . out . println ( ret );

myArray . setVal ( 2 , "bit" ); //4

}

}

代码解释:

1. 类名后的 <T> 代表占位符,表示当前类是一个泛型类

了解: 【规范】类型形参一般使用一个大写字母表示,常用的名称有:

E 表示 Element

K 表示 Key

V 表示 Value

N 表示 Number

T 表示 Type

S, U, V 等等 - 第二、第三、第四个类型

2. 注释 1 处,不能 new 泛型类型的数组

意味着:

课件当中的代码: T[] array = (T[])new Object[10]; 是否就足够好,答案是未必的。这块问题一会儿介

绍。(与泛型的擦除机制有关)

3. 注释 2 处,类型后加入 <Integer> 指定当前类型

4. 注释 3 处,不需要进行强制类型转换

5. 注释 4 处,代码编译报错,此时因为在注释 2 处指定类当前的类型,此时在注释 4 处, 编译器会在存放元素的时

候帮助我们进行类型检查。(即泛型存在的意义)

3.泛型类的使用

3.1 语法

泛型类 < 类型实参 > 变量名 ; // 定义一个泛型类引用

new 泛型类 < 类型实参 > ( 构造方法实参 ); // 实例化一个泛型类对象

3.2 示例

MyArray < Integer > list = new MyArray < Integer > ();

注意:泛型只能接受类, 所有的基本数据类型必须使用包装类 !

3.3 类型推导 (Type Inference)

MyArray < Integer > list = new MyArray <> (); // 可以推导出实例化需要的类型实参为 String

当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写

4. 裸类型 (Raw Type) (了解)

4.1 说明

裸类型是一个泛型类但没有带着类型实参,例如 MyArrayList 就是一个裸类型

T [] ts = new T [ 5 ]; // 是不对的

泛型类 < 类型实参 > 变量名 ; // 定义一个泛型类引用

new 泛型类 < 类型实参 > ( 构造方法实参 ); // 实例化一个泛型类对象

MyArray < Integer > list = new MyArray < Integer > ();

MyArray < Integer > list = new MyArray <> (); // 可以推导出实例化需要的类型实参为 String

4.裸类型

4.1 说明

裸类型是一个泛型类但没有带着类型实参,例如 MyArrayList 就是一个裸类型

MyArray list = new MyArray (); (定义时缺少了泛型)

注意: 我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制

下面的类型擦除部分,我们也会讲到编译器是如何使用裸类型的。

小结:

1. 泛型是将数据类型参数化,进行传递

2. 使用 <T> 表示当前类是一个泛型类。

3. 泛型目前为止的优点: 数据类型参数化,编译时自动进行类型检查和转换

本质 :其 本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

5.泛型如何编译的

5.1 擦除机制

那么,泛型到底是怎么编译的?这个问题,也是曾经的一个面试问题。泛型本质是一个非常难的语法,要理解好他

还是需要一定的时间打磨。

通过命令: javap -c 查看字节码文件,所有的 T 都是 Object 。

在编译的过程当中,将所有的 T 替换为 Object 这种机制 ,我们称为: 擦除机制 。

Java 的泛型机制是在编译级别实现的。编译器生成的字节码在 运行期间并不包含泛型的类型信息

有关泛型擦除机制的文章截介绍: Java泛型擦除机制之答疑解惑 - 知乎

提出问题:

1 、那为什么, T[] ts = new T[5]; 是不对的,编译的时候,替换为 Object ,不是相当于: Object[] ts = new

Object[5] 吗?-->是这样,但是 编译器语法不允许将参数类型T直接进行实例化(如下图)

f4bdb18254db479487c95f43ad55091d.png

2 、类型擦除,一定是把 T 变成 Object 吗?(是的)

5.2 为什么不能实例化泛型类型数组

class MyArray < T > {

public T [] array = ( T []) new Object [ 10 ];// 留意这行代码所体现的问题

public T getPos ( int pos ) {

return this . array [ pos ];

}

public void setVal ( int pos , T val ) {

this . array [ pos ] = val ;

}

public T [] getArray () {

return array ;

}

}

public static void main ( String [] args ) {

MyArray < Integer > myArray1 = new MyArray <> ();

Integer [] strings = myArray1 . getArray () ;//我们在编译时默认将T转化为Object,所以getArray方法返回的数组为Object类型,用integer进行接收,明显是不安全的

}

/*

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;

at TestDemo.main(TestDemo.java:31)

* 原因: 替换后的方法为:将 Object[] 分配给 Integer[] 引用,程序报错。

public Object [] getArray () {

return array ;

} 数组和泛型之间的一个重要区别是它们如何强制执行类型检查。 具体来说,数组在运行时存储和检查类型信息。然

而,泛型在编译时检查类型错误。

通俗讲就是: 返回的 Object 数组里面,可能存放的是任何的数据类型,可能是 String ,可能是 Person ,运行的时

候,直接转给 Intefer 类型的数组,编译器认为是不安全的。

正确的方式: 【了解即可】

class MyArray < T > {

public T [] array ;

public MyArray () {

}

/**

* 通过反射创建,指定类型的数组

* @param clazz

* @param capacity

*/

public MyArray ( Class < T > clazz , int capacity ) {

array = ( T []) Array . newInstance ( clazz , capacity );

}

public T getPos ( int pos ) {

return this . array [ pos ];

}

public void setVal ( int pos , T val ) {

this . array [ pos ] = val ;

}

public T [] getArray () {

return array ;

}

}

public static void main ( String [] args ) {

MyArray < Integer > myArray1 = new MyArray <> ( Integer . class , 10 );

Integer [] integers = myArray1 . getArray ();

}

6.泛型的上界

在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束

6.1 语法

6.2 示例

只接受 Number 的子类型作为 E 的类型实参

public MyArray () {

}

/**

* 通过反射创建,指定类型的数组

* @param clazz

* @param capacity

*/

public MyArray ( Class < T > clazz , int capacity ) {

array = ( T []) Array . newInstance ( clazz , capacity );

}

public T getPos ( int pos ) {

return this . array [ pos ];

}

public void setVal ( int pos , T val ) {

this . array [ pos ] = val ;

}

public T [] getArray () {

return array ;

}

}

public static void main ( String [] args ) {

MyArray < Integer > myArray1 = new MyArray <> ( Integer . class , 10 );

Integer [] integers = myArray1 . getArray ();

}

class 泛型类名称 < 类型形参 extends 类型边界 > {

...

}

public class MyArray < E extends Number > {

...

}

MyArray < Integer > l1 ; // 正常,因为 Integer 是 Number 的子类型

MyArray < String > l2 ; // 编译错误,因为 String 不是 Number 的子类型

error: type argument String is not within bounds of type-variable E

MyArrayList<String> l2;

^

where E is a type-variable:

E extends Number declared in class MyArrayList

了解: 没有指定类型边界 E ,可以视为 E extends Object

6.3 复杂示例

public class MyArray < E extends Comparable < E >> {

...

}

E 必须是实现了 Comparable 接口的

7.泛型方法

7.3 使用示例 - 可以类型推导

Integer [] a = { ... };

swap ( a , 0 , 9 );

String [] b = { ... };

swap ( b , 0 , 9 );

7.4 使用示例 - 不使用类型推导

Integer [] a = { ... };

Util . < Integer > swap ( a , 0 , 9 );

String [] b = { ... };

Util . < String > swap ( b , 0 , 9 );

8.通配符

? 用于在泛型的使用,即为通配符

8.1 通配符解决什么问题

通配符是用来解决泛型无法协变的问题的 , 协变指的就是如果 Student 是 Person 的子类,那么 List<Student> 也应 该是 List<Person> 的子类。但是泛型是不支持这样的父子类关系的。

泛型 T 是确定的类型,一旦你传了我就定下来了,而通配符则更为灵活或者说是不确定,更多的是用于扩充

参数的范围

示例:

package www . bit . java . test ;

class Message < T > {

private T message ;

public T getMessage () {

return message ;

}

public void setMessage ( T message ) {

this . message = message ;

}

}

public class TestDemo {

public static void main ( String [] args ) {

Message < String > message = new Message () ;

message . setMessage ( " 比特就业课欢迎您 " );

fun ( message );

}

public static void fun ( Message < String > temp ){

System . out . println ( temp . getMessage ());

}

}

以上程序会带来新的问题,如果现在泛型的类型设置的不是 String ,而是 Integer

public class TestDemo {

public static void main ( String [] args ) {

Message < Integer > message = new Message () ;

message . setMessage ( 99 );

fun ( message ); // 出现错误,只能接收 String

}

public static void fun ( Message < String > temp ){

System . out . println ( temp . getMessage ());

}

}

我们需要的解决方案: 可以接收所有的泛型类型,但是又不能够让用户随意修改。这种情况就需要使用通配符 "?" 来

处理

范例:使用通配符

public class TestDemo {

public static void main ( String [] args ) {

Message < Integer > message = new Message () ;

message . setMessage ( 55 );

fun ( message );

}

// 此时使用通配符 "?" 描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改

public static void fun ( Message <?> temp ){

//temp.setMessage(100); 无法修改!

System . out . println ( temp . getMessage ());

}

}

在 "?" 的基础上又产生了两个子通配符:

? extends 类:设置泛型上限

? super 类:设置泛型下限

8.2 通配符上界

语法:

<? extends 上界 >

<? extends Number > // 可以传入的实参类型是 Number 或者 Number 的子


4a8e230a5dbb412eb61ec969794f1c79.png

9.包装类

Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了

一个包装类型。

9.1基本数据类型和对应的包装类

d7533ebe386b4f3d8826b8f262175e62.png


相关文章
|
1月前
|
Java
|
2天前
|
Java 编译器 语音技术
泛型的理解
泛型的理解
7 0
|
26天前
|
存储 算法 容器
什么是泛型?
什么是泛型?
10 0
|
26天前
什么是泛型,泛型的具体使用?
什么是泛型,泛型的具体使用?
|
5月前
|
存储 算法 编译器
泛型的讲解
泛型的讲解
40 0
|
9月前
|
编译器 C#
C# 泛型
C# 泛型
39 0
|
安全 Java 编译器
第10章 泛型
泛型是什么,以及泛型怎么用。
96 0
|
存储 开发框架 安全
一文搞定泛型知识
一文搞定泛型知识
76 0
|
存储 Java 编译器
一文带你玩转“泛型“
一文带你玩转“泛型“
130 0
一文带你玩转“泛型“
|
安全 Java 编译器
java基础巩固-详解泛型
java泛型(generics)为jdk5引入的新特性,泛型提供了编译时类型安全检测机制,可以在编译时检测到非法的类型。 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
1357 0