Java初始泛型-LMLPHP

目录

一、包装类 

1、基本数据类型和对应的包装类

2、装箱和拆箱

3、自动装箱和自动拆箱

二、什么是泛型

三、引出泛型

1、泛型的语法

四、泛型类的使用

1、语法

2、示例

3、类型推导(Type Inference)

六、泛型如何编译的 

1、擦除机制

2、为什么不能实例化泛型类型数组

七、泛型的上界

1、语法

2、示例

八、泛型方法

1、定义语法

2、示例


一、包装类 

Java 中,由于基本类型不是继承自 Object ,为了在泛型代码中可以支持基本类型, Java 给每个基本类型都对应了一个包装类型。

1、基本数据类型和对应的包装类

Java初始泛型-LMLPHP

 除了 Integer Character, 其余基本类型的包装类都是首字母大写。


2、装箱和拆箱

简单的说,装箱就是:自动将基本数据类型转换为包装器类型;拆箱就是:自动将包装器类型转换为基本数据类型。

int i = 10;
// 装箱操作,新建一个 Integer 类型对象,将 i 的值放入对象的某个属性中
Integer ii = Integer.valueOf(i);
Integer ij = new Integer(i);
// 拆箱操作,将 Integer 对象中的值取出,放到一个基本数据类型中
int j = ii.intValue();

3、自动装箱和自动拆箱

可以看到在使用过程中,装箱和拆箱带来不少的代码量,所以为了减少开发者的负担, java 提供了自动机制。
int i = 10;
Integer ii = i; // 自动装箱
Integer ij = (Integer)i; // 自动装箱
int j = ii; // 自动拆箱
int k = (int)ii; // 自动拆箱

Java初始泛型-LMLPHP

从字节码内容可以看出,在装箱的时候自动调用的是Integer的valueOf(int)方法,而在拆箱的时候自动调用的是Integer的intValue方法。

其他的比如Double、Character等也是类似的,大家可以自己动手尝试一下。


因此可以用一句话总结装箱和拆箱的实现过程:

装箱过程是通过调用包装器的valueOf( )方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)。

现在,我们来看一道题,试着思考下列代码的输出结果是什么:

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

这是因为,在Integer这个包装类中,它所能存储的数值的范围是在 -128~127 之间的,而 a、b 均在这个范围内,因此比较a和b的时候比较的是数值的大小,此时a == b,因此输出true。

但是 c 和 d 均超出了Integer所能的范围,因此此时为了存储 c 和 d ,会自动new对象来分别存储它们,此时也就相当于我们比较的是引用类型, 因此在c  == d 这个比较中比较的是它们的地址,从而导致输出结果为false


二、什么是泛型

一般的类和方法,只能使用具体的类型 : 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。
                                                                                      ----- 来源《Java编程思想》对泛型的介绍。
泛型是在 JDK1.5 引入的新的语法,通俗讲, 泛型:就是适用于许多许多类型 。从代码上讲,就是对类型实现了参数化。

三、引出泛型

现在,我们有这么一个任务:
实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值?
那么此时我们该如何实现这段代码呢?思路如下:
思路:

那么此时,便会有这么一段代码:

class MyArray {
    public Object[] array = new Object[10];
    public Object getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos,Object val) {
        this.array[pos] = val;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        MyArray myArray = new MyArray();
        myArray.setVal(0,10);
        myArray.setVal(1,"hello");//字符串也可以存放
        String ret = myArray.getPos(1);//编译报错
        System.out.println(ret);
    }
}

通过以上代码实现后我们会发现一下几个问题:

1. 任何类型数据都可以存放
2. 这段代码会发生报错,这是因为ret字符串是String类型的,但是myArrary这个数组却是Object类型的,因此我们需要对其进行强制转换。
虽然在这种情况下,当前数组任何数据都可以存放,但是,更多情况下,我们还是希望他只能够持有一种数据类型。而不是同时持有这么多类型。 所以,泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译器去做检查。 此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型。

1、泛型的语法

class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> {
        }
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
// 可以只使用部分类型参数
        }

那么此时,我们利用泛型就可以将之前的那段代码改成这个样子:

class MyArray<T> {
    public T[] array = (T[])new Object[10];//1
    public T getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos,T val) {
        this.array[pos] = val;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray<>();//2
        myArray.setVal(0,10);
        myArray.setVal(1,12);
        int ret = myArray.getPos(1);//3
        System.out.println(ret);
        myArray.setVal(2,"Hello");//4
    }
}

现在,我们对代码进行一下分析:

1. 类名后的 <T> 代表占位符,表示当前类是一个泛型类
除此之外,我们还可以了解一个常见的规范:
【规范】 类型形参一般使用一个大写字母表示,常用的名称有: Java初始泛型-LMLPHP

 2. 不能new泛型类型的数组

那么这也就意味着:

T[] ts = new T[5];//是不对的
3. 类型后加入 <Integer> 指定当前类型
4. 不需要进行强制类型转换
5. 代码编译报错,此时因为在注释2处指定类当前的类型,此时在注释4处,编译器会在存放元素的时候帮助我们进行类型检查。

四、泛型类的使用

1、语法

泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象

2、示例

MyArray<Integer> list = new MyArray<Integer>();
这里我们的第二个<>里面的Integer是可以省略的,也就是说,我们可以将代码改成这个样子:
MyArray<Integer> list = new MyArray<>();

注意:泛型只能接受类,所有的基本数据类型必须使用包装类!


3、类型推导(Type Inference)

当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写
MyArray<Integer> list = new MyArray<>(); // 可以推导出实例化需要的类型实参为 Integer
小结:

六、泛型如何编译的 

1、擦除机制

那么,泛型到底是怎么编译的?泛型本质是一个非常难的语法,要理解好他需要一定的时间打磨。
现在,我们来 通过命令: javap -c 查看字节码文件,所有的 T 都是Object。 Java初始泛型-LMLPHP

 在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制

Java 的泛型机制是在编译级别实现的。 编译器生成的字节码在运行期间并不包含泛型的类型信息。
其它有关泛型擦除机制的文章介绍: https://zhuanlan.zhihu.com/p/51452375

2、为什么不能实例化泛型类型数组

我们先来看这么一段代码:

class MyArray<T> {
    public T[] array = (T[]) new Object[10];

    public T getPos(int pos) {
        return this.array[pos];
    }

    public void setVal(int pos, T val) {
        this.array[pos] = val;
    }

    public T[] getArray() {
        return array;
    }

    public static void main(String[] args) {
        MyArray<Integer> myArray1 = new MyArray<>();
        Integer[] strings = myArray1.getArray();
    }
}

此时代码会进行报错:Java初始泛型-LMLPHP

关于报错的原因,通俗讲就是:返回的 Object 数组里面,可能存放的是任何的数据类型,可能是 String ,可能是 Person ,运行的时候,直接转给Integer 类型的数组,编译器认为是不安全的。

现在,我们来看一下正确的方法【了解即可】

class MyArray<T> {
    public T[] array;
    public MyArray() {
    }
    /**
     * 通过反射创建,指定类型的数组
     * @param clazz
     * @param capacity
     */
    public MyArray(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }
    public T getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos,T val) {
        this.array[pos] = val;
    }
    public T[] getArray() {
        return array;
    }
}
public static void main(String[] args) {
        MyArray<Integer> myArray1 = new MyArray<>(Integer.class,10);
        Integer[] integers = myArray1.getArray();
        }

七、泛型的上界

在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。

1、语法

class 泛型类名称<类型形参 extends 类型边界> {
...
}

2、示例

public class MyArray<E extends Number> {
...
}

这段代码表示只接受 Number 的子类型作为 E 的类型实参

MyArray<Integer> l1; // 正常,因为 Integer 是 Number 的子类型
MyArray<String> l2; // 编译错误,因为 String 不是 Number 的子类型

那么当我们没有指定E的边界的时候,又该怎么判断范围呢?

没有指定类型边界 E,可以视为 E extends Object

八、泛型方法

1、定义语法

方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }

2、示例

public class Util {
    //静态的泛型方法 需要在static后用<>声明泛型类型参数
    public static <E> void swap(E[] array, int i, int j) {
        E t = array[i];
        array[i] = array[j];
        array[j] = t;
    }
}
04-09 13:34