泛型和包装类
一、泛型
早期 Java 是使用 Object 来代替任意类型的。这是因为基类的引用可以指向子类的对象。而 Object 是所有类型的祖先类,所以可以定义元素是 Object 类型,然后使用 Object 类型的引用指向任意类型的对象。
但是这样是存在问题的:在集合里由于没有了类型的限制,可以插入任意类型的对象,而且是不会产生语法错误的。但是集合是不知道这是什么类型的,只知道这是 Object 类型,因此返回时就是 Object 。在外部接受的时候就需要强转类型,甚至自定义类型之间如果不能强转,编译期间也不会报错,而是在运行时抛出了 ClassCastException (类型转换异常),就像下面被举了无数次的示例所示,这就大大降低了程序的安全性。
示例:
public class GenericTest {
static class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
Integer i = new Integer(1);
String str = "Hello";
// 添加任意类型的元素
arrayList.add(i);
arrayList.add(str);
arrayList.add(new Person("Tom", 27));
// 接收返回值
String result = (String) arrayList.get(0);
System.out.println(result);
}
}
结果:
Exception in thread "main" java.lang.ClassCastException : java.lang.Integer cannot be cast to java.lang.String
at generic.GenericTest.main(GenericTest.java:18)
1. 泛型介绍
1.1 设计原则
泛型把明确类型这项工作推迟到创建对象或者调用方法时才确定。而只要在编译期间没有警告,运行时就不会抛出 ClassCastException 异常。这是因为泛型可以自动进行类型的检查;自动进行类型的转换。
1.2 泛型特性
1. 泛型是定义在编译期间的一种机制,运行期间没有泛型的概念;
运行期间有没有泛型呢?
public static void main(String[] args) {
ArrayList<Integer> intArray = new ArrayList<>();
ArrayList<String> strArray = new ArrayList<>();
Class intClass = intArray.getClass();
Class strClass = strArray.getClass();
if (strClass.equals(intClass)) {
System.out.println("这说明运行时类型相同");
}
}
这是结果:
这说明运行时类型相同
Process finished with exit code 0
很明显运行时的两个类型相同,这就说明泛型只在编译期间有效。在泛型做完编译期间的工作(检查类型和强制转换)后,就擦除了,不会进入到运行阶段。而对于JVM来说,String和Integer也是不可见的。这就是泛型的擦除机制。
2. 泛型可以自动进行类型检查;自动进行类型转换;
泛型为什么被设计出来?就是为了解决这个问题!将运行期间有可能抛出异常的问题在编译期间就解决掉。让它不会出现ClassNotSupportException异常。
3. 泛型的参数不能是简单类型,应该是包装类 / 类;
泛型主要是对集合类的设计,集合类只能插入引用类型的元素,所以不能是简单类型。
4. 泛型类型的参数不参与类型组成.
就像第一个特性,由于泛型不出现在运行期间,怎么参与类型的组成。
2. 泛型使用
2.1 泛型分类
泛型可以分为泛型类、泛型接口和泛型方法,三种类型的定义如下所示:
泛型类的定义:
/**
* 泛型类
* 1.泛型的标志就是 <>, <T> 只是一个占位符, 表示当前类是泛型类
* 2.T 是类型参数变量, 一般要大写. 可以是 T E V K等等;
* 3.T 代表的是 MyArrayList 最终传入的类型, 目前还不知道, 使用时才能确定类型
* 4.注意:这里定义了 T,那里面返回值和形参的类型就是 T,其他的会报错 "cannot reslove symbol X"
*/
public class MyArrayList<T> {
public T[] elem;
public int size;
......
/**
* 这不是一个泛型方法!!!
* 因为没有泛型的标志 <>
* 这只是一个普通方法,返回值是在声明泛型类已经声明过的泛型
*/
public T get(){
......
}
}
泛型接口的定义:
/**
* 泛型接口
*/
public interface Generic<T> {
public T do();
}
泛型方法的定义:
/**
* 泛型方法
* 在返回值和修饰限定符之间有 <T> 表明这是泛型方法
* 泛型数量可以是多个
*/
public <T> void add(T t) {
......
}
错误定义
public class MyArrayList<T> {
/**
* 这不是一个泛型方法!!!
* 因为没有泛型的标志 <>
* 这只是一个普通方法,返回值是在声明泛型类已经声明过的泛型
*/
public T get(){
......
}
/**
* 这不是一个泛型方法!!!
* 只是使用这个泛型作为形参而已
*/
public void put(List<Integer> num) {
...
}
/**
* 这不是一个泛型方法!!!
* 根本没有声明过 E 这个类型
*/
public <T> void show(Generic<E> num) {
...
}
}
2.2 泛型的使用
代码如下:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
// 添加元素
list.add("hello!");
// 添加其他类型的元素
list.add(1);// 会产生编译错误
// 接受返回值时不需要类型转换
String str = list.get(0);
// 类型不匹配时会产生编译错误
Integer i = list.get(0);// 编译错误
}
3. 泛型总结
- 泛型是定义在编译期间的一种机制,运行期间没有泛型的概念;
- 泛型可以自动进行类型检查;自动进行类型转换;
- 泛型是为了解决某些容器、算法等代码的通用性而引入的,可以在编译期间进行类型检查;
- 泛型利用 Object 是所有类的祖先类,父类的引用可以指向子类对象的特定而工作;
- 泛型的参数不能是简单类型,应该是包装类 / 类;
- 泛型类型的参数不参与类型组成;
- 不能 new 一个泛型类型的数组;
- 泛型是怎么编译的?
擦除机制:在编译期间将 T 擦除(而不是替换)为Object;
二、包装类
Object 引用可以指向任意类型的对象,但是 8 种基本数据类型不是对象,为了适配泛型的使用,Java中将这 8 种基本数据类型分别包装到一个对象中,引入了包装类。
1. 基本数据类型的包装类
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
2. 包装类的使用
2.1 装箱(装包)- boxing
装箱是简单类型包装到包装类型的过程,有自动装箱和显式装箱两种方法
// 装箱: 简单类型 -> 包装类型
Integer i1 = 10;// 自动装箱
Integer i2 = new Integer(10);// 显式装箱
Integer i3 = Integer.valueOf(10);// 显式装箱
通过 javap -c 类名 反编译,可以看到自动装箱底层还是Integer.valueOf();
Compiled from "GenericTest.java"
public class generic.GenericTest {
public generic.GenericTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 10
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: new #3 // class java/lang/Integer
9: dup
10: bipush 10
12: invokespecial #4 // Method java/lang/Integer."<init>":(I)V
15: astore_2
16: bipush 10
18: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
21: astore_3
22: return
}
2.1 拆箱(拆包)- unboxing
拆箱是包装类型到简单类型的过程,同样有自动拆箱和显式拆箱两种方法
// 拆箱: 包装类型 -> 简单类型
int j1 = i1; // 自动拆箱
int j2 = i2.intValue();// 显式拆箱