目录
▮ 为什么需要泛型?
有三种数组:int[ ], String[ ] , Animal[ ],分别是基本数据类型的数组,字符串数组,类数组。这三个数组的定义格式都是一样的,都是 “ 类型[ ] 引用 = new 类型[ ]{...}; ”。对于他们的共性部分,能不能提取出来?
我能不能把“数据类型”作为参数?像方法那样的参数一般,传入到“定义格式”中。把数据类型作为参数传递,实现复用代码,降低耦合。
▮泛型类
▪泛型类实例
•代码示例
class MyArray<E>{ private E[] arr = (E[]) new Object[10]; //返回一个数组引用 public E[] getArray(){ return arr; } } public class Main { public static void main(String[] args) { //传入类型Integer MyArray<Integer> array = new MyArray<>(); //得到一个Integer[] Integer[] arr = array.getArray(); } }
1.类名后多个<E>,<>是泛型的标志,E是类型参数
2.引用arr的类型是 E[ ],new出的新对象转换成E[ ]
3.泛型类实例化时,在类名后的<>传入类型
4.泛型是类型参数化,处理的数据类型不再固定,可以作为参数传入
▪泛型的规范
E本来只是一个参数名,没有什么特殊含义。但为了增加代码的可读性,Java规范了类型参数名,以及它对应的含义。
▮泛型的实现原理
▪擦除机制
泛型是只存在于编译阶段的语法,在程序运行时,根本不知道有泛型这个东西的存在。因为在编译阶段,编译器会把所有的类型参数E给擦除,然后替换成Object,再加上必要的类型转换;所以在程序运行时,就只剩下强转(E)和Object了
▪泛型参数不能实例化(个人见解)
• 泛型参数不能实例化,不是泛型类不能实例化
一、因为泛型的实现机制就是强转Object,“new E[10]”,擦除后就变成了“new (E)Object[10]”。这是一个有问题的语法,因为还没new出对象就进行强转。
二、泛型的本质就是依赖Object,那完全可以直接定义Object数组来进行操作,在最后需要数据时再添加类型转换。所以没必要去精准的定义泛型实例。
•代码实例
/* Java中ArrayList泛型类的部分原码 */ public class ArrayList<E> { //一个Object数组 Object[] elementData; //输出下标处的数据,返回值是E类型的,Object转成了E public E get(int index) { rangeCheck(index);//不用在意这个方法,它的作用是检查下标是否合法 //调用了下方的方法 return elementData(index); } //把对应下标的成员强转为E类型的数据 E elementData(int index) { return (E) elementData[index]; } }
这里的elementDate直接用Object来定义,没有使用泛型参数E。类里还有很多操作elementDate的方法,这些方法都是把它当作Object来处理。当外界要获取elemenDate时,再使用泛型参数E强转
因为elementDate被封装,只能通过get()等方法被外界所调用,而这些方法都会对elementDate进行E类型转换。
▮关于泛型的小细节
▪泛型参数不接受基本数据类型
/* 错误示范 */ MyArray<int> array = new MyArray<int>();
泛型参数可以传入类,也可以传入接口(接口本身就差不多等于类),但不能传入“int”这中基本数据类型。对此,Java中设有包装类来解决这个问题,实现基本数据类型向类类型的转换
▪省略类型参数传入
MyArray<Integer> array = new MyArray<Integer>(); //编译器可以根据前文推到出类型参数,所以省略了后面的<>里的内容 MyArray<Integer> array = new MyArray<>();
▪泛型参数不可实例化
• 泛型参数不能实例化,不是泛型类不能实例化
/* 错误示范 */ private E[] arr = new E[10]; /* 正确写法 */ private E[] arr = (E[]) new Object[10];
▪泛型参数不可跟static同时出现
/* 错误示范 */ private static E[] arr = new E[10];
"static T[]"是一个错误的用法,因为它违反了泛型的设计逻辑。static定义的代码只在程序中运行一次;而泛型可以传入不同类型,进行多次运行,创建多个对象。
▮泛型方法
//打印数组里的所有成员 public <E> void show(E[] arr){ for(E a : arr){ System.out.print(a + " "); } System.out.println(); }
1.返回类型前加上<E>
2.参数类型为<E>
▪调用方法的实例
public class Main { public static void main(String[] args) { Integer[] arr1 = new Integer[]{1,2,3,4}; Character[] arr2 = new Character[]{'h','e','l','l','o'}; Main main = new Main(); main.show(arr1); main.show(arr2); } //打印数组里的所有成员 public <E> void show(E[] arr){ for(E a : arr){ System.out.print(a + " "); } System.out.println(); } }
•运行截图
▪省略类型参数传入
main.show(arr1);
调用泛型方法时,可以不用手动传入类型,编译器会根据传入的实参自动传入类型。因为arr1的类型为Integer,所以自动传入了Integer。
▮泛型接口
public interface Comparable<T>{ public int compareTO(T o); } public interface Comparator<T>{ int compare(T o1,T o2); boolean equals(Object obj); }
1.接口名后有<>
2.接口里的方法,返回类型前无<>,但参数还是T
▪调用实例
public class Main implements Comparator<Integer>{ public static void main(String[] args) { Main test = new Main(); if(test.compare(1,2) < 0){ System.out.println("1 < 2"); } } @Override public int compare(Integer o1, Integer o2) { //形参类型要跟传入泛型相同 return o1 - o2; } }
当重写方法里的形参类型跟泛型相同时,方法才会构成重写。否则编译器就会报错,指出接口方法没有被重写。这里体现了泛型的起到的 “类型安全” 作用。