5.通配符
通配符:?用于在泛型的使用,这个?就是通配符
5.1通配符的作用
class Array<T> { public T[] arr = (T[])(new Object[10]); public void setArr(T val,int pos) { this.arr[pos] = val; } public T getArr(int pos) { return this.arr[pos]; } } public class Test { public static void main(String[] args) { Array<Integer> array = new Array<Integer>(); fun(array); } public static void fun(Array<Integer> arr) { arr.setArr(1,0); System.out.println(arr.getArr(0)); } }
在主函数中实例化了一个 Array 泛型类,传的类型是 Integer。在 fun 方法形参中只能接收 Array 类的类型为 Integer 的对象,调用 fun 方法可以使用类型推导,如果传入的不是 Array 类的类型为Integer 的对象则会报错。Array 是一个泛型类,实例化的时候可以传入不同类型的实参,那么 fun这样就有了一定的局限性
那么此时我们就可以将fun方法形参中的Array<>里面类型设置为通配符,这样就可以接收 Array 类的任何类型的对象了
class Array<T> { public T[] arr = (T[])(new Object[10]); public void setArr(T val,int pos) { this.arr[pos] = val; } public T getArr(int pos) { return this.arr[pos]; } } public class Test { public static void main(String[] args) { Array<Integer> array = new Array<Integer>(); array.setArr(1,0); fun(array); } public static void fun(Array<?> arr) { System.out.println(arr.getArr(0)); } }
使用通配符"?"说明它可以接收任意类型的Array对象,但是由于不确定类型,所以无法修改
5.2子通配符
在通配符的基础上又产生了两个子通配符:
?extends 类:设置泛型上界
?super 类:设置泛型下界
5.2.1通配符上界
①语法
<? extends 上界> <? extends Number>
传入的实参类型必须是 Number 本身类或者是 Number 的子类
②示例
class Fruits { } class Apple extends Fruits { } class Message<T> { private T message; public void setMessage(T message) { this.message = message; } public T getMessage() { return message; } } public class Main { public static void main(String[] args) { Message<Apple> message = new Message<>(); message.setMessage(new Apple()); fun(message); } public static void fun(Message<? extends Fruits> tmp) { //tmp.setMessage(new Apple()); Fruits fruits = tmp.getMessage(); } }
fun 形参类型传过来的实参类型必须是 Fruits 类型 或者是 Fruits 子类类型。在fun里面用通配符接收类型,那么我们也就不清楚是具体的哪种类型,只知道是Fruits 类型 或者是 Fruits 子类类型,那么我们就无法调用 setMessage 去设置 message 的值,因为子类类型无法存储父类对象。我们知道主函数传给fun方法的对象类型要么是 Fruits 类型 要么是 Fruits 子类类型,那么我们就可以调用 getMessage 去获取 message 的值,用Fruits 接收即可。
通配符的上界,不能进行写入数据,只能进行读取数据
5.2.2通配符的下界
①语法
<? super 下界> <? super Integer>
传过来的类型必须是 Integer 本身类 或者是 Integer 的父类
②示例
class Fruits { } class Apple extends Fruits { } class Message<T> { private T message; public void setMessage(T message) { this.message = message; } public T getMessage() { return message; } } public class Main { public static void main(String[] args) { Message<Fruits> message = new Message<>(); message.setMessage(new Fruits()); fun(message); } public static void fun(Message<? super Fruits> tmp) { tmp.setMessage(new Apple()); //Fruits fruits = tmp.getMessage(); } }
fun 形参类型传过来的实参类型必须是 Fruits 类型 或者是 Fruits类型的父类,那我们在fun方法中通过 setMessage 去设置 message 的值时,我们知道主函数传给 fun 方法的对象类型最低也得是Fruits 方法,通过 setMessage 去设置 message 值是我们可以设置为 Fruits 子类对象,因为 Fruits子类肯定也是Fruits父类的子类,所以可以设置 message 的值。但是不能通过 getMessge 去获取messge的值,原因就在于我们只知道主函数传给fun实参类型是 Fruits 类型 或者是 Fruits类型的父类,如果传过来的实参类型是 Fruits 父类那我们去获取message的值时我们就不知道用什么接收了。
通配符的下界,不能进行读取数据,只能写入数据
6.包装类
在 Java 中,基本类型是不会继承 Object 类的,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型
6.1基本类型对应的包装类
基本数据类型 包装类
基本数据类型 | 包装类 |
byte | Byte |
shout | Shout |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
6.2装箱和拆箱
6.2.1装箱
int a = 10; Integer i = Integer.valueOf(a);
Integer.valueof:主要将一个基本数据类型的值存放到 Integer 类的对象里面
上述代码装箱操作,新建一个 Integer 类型对象,将 i 的值放入对象的某个属性中
Integer 类中的 valueOf 方法中,如果你存储的值在 -128(IntegerCache.low:-128)到127(IntegerCache:127)范围中就存储到 i +(-IntegerCache.low)这个位置的数组当中。如果超出了这个范围就 new 一个 Integer 对象,通过构造方法存储在 value 属性中
6.2.2拆箱
int a = 10; Integer i = Integer.valueOf(a);//装箱 int b = i.intValue();//拆箱
拆箱操作,将 Integer 对象中的值取出,放到一个基本数据类型中
6.2.3自动装箱和自动拆箱
从上述手动装箱和拆箱中我们可以看出手动装箱和拆箱给开发者带来了不少代码量,为了减少开发者的负担,Java提供了自动装箱拆箱机制
public class Demo { public static void main(String[] args) { int i = 10; Integer ii = i; // 自动装箱 Integer ij = (Integer)i; // 自动装箱 int j = ii; // 自动拆箱 int k = (int)ii; // 自动拆箱 } }
通过字节码文件可以看出在编译时期会自动的装箱拆箱,还原为装箱和拆箱