目录
一、泛型类的引出
二、泛型类的定义和使用
三、泛型的编译
四、泛型的上界
五、泛型方法
六、通配符
1.什么是通配符:
2.通配符可以解决的泛型问题:
3.通配符的上界
4.通配符的下界
1.什么是泛型
1.泛型类的定义
class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
// 这里可以使用类型参数
}
举个例子:
class MyArray {
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 class TestDemo {
public static void main(String[] args) {
MyArray myArray = new MyArray<>();//第12行代码
myArray.setVal(0,10);
myArray.setVal(1,12);
int ret = myArray.getPos(1);
System.out.println(ret);
//myArray.setVal(2,"bit");//代码编译报错,此时因为在注释2处指定类当前的类型,此时在注释4处,编译器会在存放元素的时候帮助我们进行类型检查。
}
}
(1)类名后的
(2)不能new泛型类型的数组
即:T[] ts = new T[5];//error
(3)第12行代码处:类型后加入
2.泛型类的使用
泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
注意:当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写,如:
MyArray list = new MyArray<>(); // 可以推导出实例化需要的类型实参为 String
3.裸类型(Raw Type)
MyArray list = new MyArray();
注意:我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制
在Powershell窗口中查看“二”中的代码是如何编译的,如图所示:
通过命令:javap -c 查看字节码文件,所有的T都是Object。
那么现在问题来了,为什么T[] ts = new T[5]; 是不对的,编译的时候替换为Object,不是相当于Object[] ts = new Object[5]吗?
如下代码所示:
class MyArray {
//public T[] objects = new T[10];//error
public T[] objects = (T[])new Object[10];//理论上说这样也是错的
public void set(int pos, T val){
objects[pos] = val;
}
public T get(int pos){
return objects[pos];
}
public T[] getArray(){
return objects;
}
}
public class TestDemo {
public static void main(String[] args) {
MyArray myArray = new MyArray<>();
String[] ret = myArray.getArray();//第20行代码
}
}
数组和泛型之间的一个重要区别是它们如何强制执行类型检查。具体来说,数组在运行时存储和检查类型信息。然而,泛型在编译时检查类型错误。
import java.lang.reflect.Array;
class MyArray {
public T[] array;
public MyArray() {}
public MyArray(Class 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 myArray1 = new MyArray<>(Integer.class,10);
Integer[] integers = myArray1.getArray();
}
class 泛型类名称<类型形参 extends 类型边界> {
...
}
例如:
class MyArray {//T只能是Number或者Number的子类
public T[] objects = (T[])new Object[10];
public void set(int pos, T val){
objects[pos] = val;
}
public T get(int pos){
return objects[pos];
}
public T[] getArray(){
return objects;
}
}
public class TestDemo {
public static void main(String[] args) {
MyArray myArray = new MyArray<>();//第18行代码
MyArray myArray2 = new MyArray<>();
}
}
第18行代码从编译开始就出错了,因为String 不是 Number 的子类型,但是Integer是。
注意:
(1)泛型只有上界,没有下界
(2)没有指定边界时,默认边界为Object
class MyArray{//默认边界为Object
......
}
一个比较复杂的例子:写一个泛型类,求出数组当中的最大值
class Alg>{//此时传入的T一定要实现该接口 此处不是继承
public T findMax(T[] array){
T max = array[0];
for(int i = 1; i < array.length; i++){
if(max.compareTo(array[i]) < 0){//①max<=比较
//②也不能用equals方法,它只能比较true和false,比较不了大小关系。两个引用比较大小,要用CompareTo方法
max = array[i];
}
}
return max;
}
}
public class TestDemo {
public static void main(String[] args) {
Alg alg = new Alg<>();
Integer[] array = {1,22,3,4};
System.out.println(alg.findMax(array));
}
}
编译并运行该代码,输出如下:
22
注意Integer是实现了Comparable<>接口的,如图所示:
该代码有一个不好的地方,我们每次想调用findMax方法时,必须要new一个对象;我们对其进行优化一下
1.
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) {
...
}
优化如下:
class Alg2/*>*/{// "/* */"的这一部分写和不写都是一样的
public static> T findMax(T[] array){//我们发现当我们加一个static后,整个代码都报错了,这是因为加了static后,我们的方法不再依赖
//于对象,我们在使用该方法时不再new对象,但是我们的泛型传参在new对象时传参,所以如果加了一个static的
//的话,相当于没有传参;因此我们在static处加上,但是要用compareTo方法,还是要实现Comparable接口
T max = array[0];
for(int i = 1; i < array.length; i++){
if(max.compareTo(array[i]) < 0){
max = array[i];
}
}
return max;
}
}
public class TestDemo {
public static void main(String[] args) {
Integer[] array = {1,22,3,4};
System.out.println(alg./**/findMax(array));//"/* */"的这一部分写和不写都是一样的,它会通过array的类型来推导方法中的T是什么类型【类型推导】
}
}
2.泛型中的父子类关系
public class MyArrayList {
...
}
// MyArrayList
因为在编译的时候它们都被擦除了
举个例子:
class Alg>{
public T findMax(T[] array){
T max = array[0];
for(int i = 1; i < array.length; i++){
if(max.compareTo(array[i]) < 0){
max = array[i];
}
}
return max;
}
}
public class TestDemo {
public static void main(String[] args) {
Alg alg1 = new Alg<>();
Alg alg2 = new Alg<>();
System.out.println(alg1);
System.out.println(alg2);
}
}
编译并运行该代码,输出如下:
我们发现打印的内容中不存在<>,这说明它们被擦除了
? 用于在泛型的使用,即为通配符
通配符是用来解决泛型无法协变的问题的,协变指的就是如果 Student 是 Person 的子类,那么 List
class Alg3{
public static void print(ArrayList list){
for(T x:list){
System.out.println(x);
}
}
//此时代码的参数是T,此时的T一定是将来指定的一个泛型参数
public static void print2(ArrayList> list){
for(Object x:list){
System.out.println(x);
}
}
//代码中使用了统配符,和代码1相比,此时传入printList2的,具体是什么数据类型,我们是不清楚的。这就是通配符。
}
(Ⅰ)语法
与泛型上界类似, extends 上界>;如: extends Number>//可以传入的实参类型是Number或者Number的子类
举个例子:假设有如下关系:
public static void print(List list) {
......
}
public static void print2(List list) {
for (T animal : list) {
System.out.println(animal);
}
}
public static void print3(List extends Animal> list) {
for (Animal ani : list) {
System.out.println(ani);//传过来是谁,就调用谁的toString方法 (发生了向上转型)
}
}
区别如下:
①对于泛型实现的print2方法,
②对于通配符实现的print3方法,首先不用再static后使用尖括号,其次相当于对Animal进行了规定,允许你传入Animal 的子类。具体哪个子类,此时并不清楚。比如:传入了Cat,实际上声明的类型是Animal,使用多态才能调用Cat的toString方法
(Ⅱ)通配符上界的父子类关系
(Ⅲ)通配符的上界的特点
通配符的上界不适合写入数据
public class TestDemo {
public static void main(String[] args) {
ArrayList arrayList1 = new ArrayList<>();
ArrayList arrayList2 = new ArrayList<>();
List extends Number> list = arrayList1;
list.add(0,1);
list.add(1,10.9);
}
}
这样子写编译时是不会通过的,因为此时list的引用的子类对象有很多,再添加的时候,任何子类型都可以,为了安全,java不让这样进行添加操作。
但是通配符的上界适合读取数据:
public class TestDemo {
public static void main(String[] args) {
ArrayList arrayList1 = new ArrayList<>();
ArrayList arrayList2 = new ArrayList<>();
List extends Number> list = arrayList1;
Number o = list.get(0);
//Integer i = list.get(1);//error 类型太多,不一定是Integer(你怎么知道,获取的就是Integer呢?),所以读取时一定要用Number类型来接收
}
}
(Ⅰ)语法
super 下界>
super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型
(Ⅱ)通配符下界的父子类关系
public class TestDemo {
public static void main(String[] args) {
ArrayList super Person> arrayList1 = new ArrayList<>();
//ArrayList super Person> arrayList2 = new ArrayList<>(student);//error arrayList2只能引用Person或者Person父类类型的list
arrayList1.add(new Person());//为添加元素的时候,我们知道list引用的对象肯定是Person或者Person的父类的集合,我们能够确定此时存储元素的最小粒度比Person小的都可以。
//Student s = arrayList1.get(0);//error 读取的时候,我们不知道是读取到的是哪个子类
Object s2 = arrayList1.get(0);//可以
//Person s3 = arrayList1.get(0);//error
}
}
通配符的下界适合写入数据,不适合读取数据