1.包装类
在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类.可以把包装类理解为基本数据类型所对应的引用数据类型.
1.1基本数据类型与对应的包装类
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
除了char和int,其他的都是把首字母大写即可.
1.2 装箱和拆箱
1.2.1 装箱与拆箱的操作
装箱,就是把基本类型转换为包装类型.
拆箱,就是把包装类型转化为基本类型.
public class Package {
int i = 10;
//手动装箱操作
Integer ij = Integer.valueOf(i);
// Integer iij = new Integer(i); jdk17已弃用
//手动拆箱操作
int a = ij.intValue();
}
上面的valueOf方法为静态方法,所以通过类名来调用.而intValue方法为非静态方法,通过对象名来调用.
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
public int intValue() {
return value;
}
既然拆装箱可以手动,当然也可以自动,下面实现自动装箱与自动拆箱
class Auto_Package{
int a = 10;
//自动装箱操作
Integer b = a;
Integer c = (Integer) a;
//自动拆箱操作
int d = b;
int e = (int) c;
}
1.2.2 包装类的范围问题
下面代码输出什么,为什么?
[阿里巴巴面试题]
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a == b);
System.out.println(c == d);
}
第一个输出true,第二个输出false.原因是,包装类的数据有一定地限制范围.下面我们看源码:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);//超出范围之后就会new一个新的对象出来
}
private static class IntegerCache {
static final int low = -128;//范围的最小值为-128
static final int high;
static final Integer[] cache;
static Integer[] archivedCache;
static {
// high value may be configured by property
int h = 127;//定义h=127
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
h = Math.max(parseInt(integerCacheHighPropValue), 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;//范围的最大值为127
由于128超出了if语句的条件限制,所以就会new一个新的对象出来,它们指向的是不同的引用,而 == 比较的就是两个对象的地址是否想相同,所以第二个返回false.
2.泛型
2.1 什么是泛型
一般的类和方法,只能使用具体的类型,要不是基础类型,要不是引用类型,要不是自定义类型,如果需要编写多种类型都可以应用的代码,这种编程方式就会特别刻板.
于是在jdk1.5中就引入了新的语法,就是泛型:通俗讲,就是适用于多种类型.从代码上讲,就是把类型作为一种参数来传递,实现的是类型的参数化.
2.2 引出泛型
实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值?我们现在通过已有的知识来实现.
class MyArray{
public Object[] array = new Object[10];//通过object类实现存储任何类型元素
public void setPos(int pos,Object val){
this.array[pos] = val;//设置元素
}
public Object getPos(int pos){
return array[pos];//获取元素
}
}
public class Main {
public static void main(String[] args) {
MyArray myArray = new MyArray();
myArray.setPos(1,2);
myArray.setPos(2,"String");
String str = (String) myArray.getPos(2);//Object类向下转型
System.out.println(str);
}
}
上述的数组可以存放任何类型的数据,但是更多情况下,我们还是希望能够只有一种数据类型,所以,泛型的目的就是指定当前容器要持有什么类型的对象,让编译器做检查.此时就需要把类型作为参数传递.
2.3 语法
class className<T1,T2,T3....>{
//这里可以使用指定的参数类型
}
我们对上述代码进行改写:
class MyArray2<T>{
public T[] array = (T[]) new Object[10];//这种创建数组的方法其实不太好,我们后面说为什么,1
public void setPos(int pos,T val){
this.array[pos] = val;
}
public T getPos(int pos){
return array[pos];
}
}
public class Main {
public static void main(String[] args) {
MyArray2<Integer> myArray2 = new MyArray2<>();//2
myArray2.setPos(1,2);
myArray2.setPos(2,3);
myArray2.setPos(3,4);
int i = myArray2.getPos(3);//3
System.out.println(i);
//myArray2.setPos(4,"String");//4
}
}
代码解释:
- 类名后的 < T >代表占位符,表示当前类型是一个泛型.
类型的指定一般用一个大写字母来表示,常见的有:
E 表示 Element
K 表示 Key
V 表示 Value
N 表示 Number
T 表示 Type
S, U, V 等等 - 第二、第三、第四个类型 - 注释1处不可以new泛型的数组,
T[] t = new T[5]
不正确.后面new对象的时候尖括号中可以不写内容,是因为编译器可以根据上下文推导出类型实参. - 注释2处
<Integer>
指定了当前对象的类型. - 注释3处,无需强转,自动拆包.
- 注释4处报错,传入的字符串类型和Integer类型呢不匹配.
2.4 泛型编译原理
泛型是如何编译的呢,它遵循的是擦除机制,就是把所有的T擦除为Object.
Java的泛型机机制是编译时实现的,生成的字节码文件之后并不存在泛型的这种概念.
2.5为什么不能实例化泛型类型的数组
class MyArray3<T>{
public T[] ts = (T[]) new Object[5];
public T getPos(int pos){
return ts[pos];
}
public T[] getTs(){
return ts;
}
}
public class Main {
public static void main(String[] args) {
MyArray3<Integer> myArray3 = new MyArray3<>();
Integer[] integers = myArray3.getTs();
}
}
这里我们看到,出现了类型转换异常,是因为在返回数组的操作中,T被擦除为了Object,而Object数组中可以存放任何类型的元素,而要把Object类型的数组传给一个Integer数组,编译器认为是不安全的.所以会出现异常.
接下来,我们解释为什么强转为(T[])
类型的数组是不妥当的,其实和上面的问题是一样的道理,都是在类型中出现了问题.
上述我们可以看到编译器给出了警告,意思就是你Object类型数组里的元素要往T去转(向下转型),不一定都可以转成功,比如在new这个类型的对象的时候,T给的是一个Integer类型,而Object类型中的元素若是有String类型,这时就强转不了.我们前面提到向下转型本来就不安全,所以向下转型一定要谨慎.
正确的做法如下:
class MyArray4<T>{
public Object[] array = new Object[10];
public T getPos(int pos){
return (T)array[pos];//这里虽然会报警告,但是在创建这个类的对象的时候,就已经指定了
//类型,所以在添加的时候就是添加的这个类型,所以这里的强转是安全的
}
public void setPos(int pos,T val){
array[pos] = val;//这里给数组放入的数据类型和创建对象时指定的T的类型是一致的
}
}
2.6 泛型的上界
在定义泛型类的时候,有时需要对传入的类型进行一定的约束
2.6.1 语法
public class MyArray<类型形参 extends 类型边界> {
........
}
实例1:
public class MyArray<T extends Number> {//传入的类型必须继承自Number
........
}
实例2:
public class MyArray<E extends Comparable<E>>{//传入的类型必须实现comparable接口,即可比较对象
...
}
2.7 泛型方法
2.7.1 语法
方法限定符 <类型形参> 返回类型 方法名称(形参列表){...}
下面结合泛型上界和泛型方法来举个例子
class Alg{
public <E extends Comparable<E>> E findMaxVal(E[] array){
E max = array[0];
for (int i = 1; i < array.length; i++) {
if (max.compareTo(array[i])>0){
max = array[i];
}
}
return max;
}
}
public class Main {
public static void main(String[] args) {
Integer[] array = {1,2,3,4,5};
Alg alg = new Alg();
System.out.println(alg.findMaxVal(array));
}
}