泛型与包装类
前言
本篇博客将对Java中的泛型和包装类做以简单介绍,以及其注意事项有哪些,只做本人小结,后期随学习深入再做补充修改。
泛型
概念及背景
在Java中用众多的元素类型,int的包装类Integer类型、String类型一级众多的自定义类型,而这些类型想要统一使用时就会出现类型不兼容的情况,在1.5之前,Java允许可以创建一个元素为Object类型,因为其是所有类的父类,所以可以通过对类型Object的引用,来实现参数的任意化。但其带来的缺点也十分明显,因为每次引用都需要强制转型,这就需要开发人员对实际参数类型可以在预知的情况下进行,如果使用错误,这种异常在运行时才会报出,被人诟病。因此在1.5以后,引入了泛型的概念来用于这种场景。
Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法
对于泛型中参数类型的理解就可以把它理解成一个占位符,在程序运行时再用具体指定的类型去替换
使用语法
class 名称<类型参数列表>{
}
实例
class Test<E>{
private E[] arr;
private size;
private Test(){
arr = (E[])new Object[10];
}
public static boolean add(E e){
arr[size++] = e;
}
public static void printArray(){
for(E e,arr){
System.out.println(e);
}
}
原理
泛型本质是将数据类型参数化,它通过擦除的方式来实现。声明了泛型的 .java 源代码,在编译生成 .class 文件之后,其中泛型相关的信息就被擦除替换成具体的类型,这就是类型擦除
Java编译器在类型擦除阶段所做的事
- 将类型变量用擦除后的类型替换,通常为Object类
- 在必要位置加入强制转换语句保证程序的安全性
- 在继承了泛型类或泛型接口的类中添加 bridge method 保留多态的特性
泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型
class T <E> {
public void print() {
System.out.println("可执行");
}
}
public class Test5 {
public static void main(String[] args) {
T<Integer> t1 = new T<>();
T<String> t2 = new T<>();
System.out.println(t1.getClass().equals(t2.getClass()));//返回true
}
}
如上例子可看出,在对同一个泛型类传入不同的参数类型时并没有真正生成多个不同的类型,其只有一个类型即 T 类型,Java泛型只在编译期间有效,在编译过程中,对传入参数检验无误后,会将泛型的信息全部擦除,因此泛型信息不会进入到运行时阶段。
类型边界
当在定义泛型,需要对传入的参数类型做一定约束时,可以通过定义它的类型边界来实现
class 名称<类型参数 extends 类型边界>{
...
}
例如:
上次定义的T泛型类,由于对类型边界做了修改,让其传入的 E 类型只能是 CharSequence 的子类,所以传入String可执行,传入Integer不可执行
通配符
以上所述都是泛型类的范畴,而如果对于一个方法,想让其传入的参数可以是多种类型又该怎么做呢?此处就涉及到泛型的通配符——?
//语法
public static void function(T<?> t){
...
}
类型通配符一般是使用 ? 代替具体的类型实参,而T<?> 中的 ? 逻辑上是其下Integer、String…所有定义的类型实参的父类
通配符的上界:
//语法
T<? extends 上界>
在定义一个方法,要是其传入的参数只能为某个类的子类,此时就可以用到通配符的上界
public static void function(T<? extends Number> t){
...
}
public static void main(String[] args){
function(T<Integer> t);
function(T<Double> t);
function(T<Long> t);
}
通配符的下界:
//语法
T<? super 下界>
与上界正好相反,通配符的下界使得传入的参数只能是该类本身或父类
public static void function(T<? super Integer> t){
...
}
public static void main(String[] args){
function(T<Integer> t);
function(T<Number> t);
function(T<Object> t);
}
泛型方法
语法
方法限定符 <类型形参列表> 返回值类型 方法名(形参列表){
...
}
//例
public <E> E fun(T<E> t,int i){
...
}
- 定义泛型方法,必须在限定符与返回值之间加 <类型形参列表> ,以此声明该方法是泛型方法
- 只有用 <类型形参列表> 声明的方法才是泛型方法,而一个方法的形参中使用了泛型而为声明,该方法并不是泛型方法
- 静态方法无法直接访问类上定义的泛型,如果一个类中的静态方法要使用泛型,必须将该静态方法定义成泛型方法,即要通过 <类型形参列表> 声明
局限
- 泛型类型参数只能为引用类型不能为基本类型
- 无法实例化泛型类型的对象
- 无法使用泛型类型什么静态属性
- 无法使用 instanceof 判断带类型参数的泛型类型
- 无法创建泛型类型数组
- 泛型类不可以继承Exception类,即泛型类不可以作为异常被抛出
- 泛型类型不是形参的一部分,无法重载
包装类
背景及概念
Java作为一门面向对象的语言,诸多操作都是面向对象而言的,而此时其基本类型使用起来就显得不太方便,例如上面所说的泛型场景,其传入参数类型只能为引用类型。正因此,Java的包装类应运而生
实际上的包装类就是将原有的八大基本数据类型封装成各个对象,以更加方便的使用
八大基本数据类型对应的其包装类
包装类的使用
以 Integer 为例,
- 当要把 int 转换为 Integer 时,调用 Integer 的构造方法 new Integer(5);
- 当要把 Integer 转换 为 int时,调用 Integer 的 intValue() 方法;
- Integer 还支持将字符串 str 转换成 Integer,调用构造方法 new Integer(str);
自动解包
在Java中,如果对一个 int 类型的变量 a 和 一个 Integer 类型的变量 b,用 == 判断是否相等,会触发Integer 的自动解包,即 变量b会自动转换成 int 类型的变量在与之比较
int a = 10;
Integer b = new Integer(10);
System.out.println(a == b);//返回true
基本数据类型所对应包装类的基础关系
以上便是对Java中泛型与包装类的知识点小结,随着后续学习的深入还会同步的对内容进行补充和修改,如能帮助到各位博友将不胜荣幸,敬请斧正