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直接进行实例化(如下图)
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 的子类
9.包装类
在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了
一个包装类型。
9.1基本数据类型和对应的包装类