Collection与数据结构 数据结构预备知识(二):包装类与泛型

1.包装类

在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类.可以把包装类理解为基本数据类型所对应的引用数据类型.

1.1基本数据类型与对应的包装类

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

除了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
    }
}

代码解释:

  1. 类名后的 < T >代表占位符,表示当前类型是一个泛型.
    类型的指定一般用一个大写字母来表示,常见的有:
    E 表示 Element
    K 表示 Key
    V 表示 Value
    N 表示 Number
    T 表示 Type
    S, U, V 等等 - 第二、第三、第四个类型
  2. 注释1处不可以new泛型的数组,T[] t = new T[5]不正确.后面new对象的时候尖括号中可以不写内容,是因为编译器可以根据上下文推导出类型实参.
  3. 注释2处<Integer>指定了当前对象的类型.
  4. 注释3处,无需强转,自动拆包.
  5. 注释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));
    }
}