赞
踩
目录
1.什么是泛型
1.泛型类的定义
- class 泛型类名称<类型形参列表> {
- // 这里可以使用类型参数
- }
- class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
- // 这里可以使用类型参数
- }
举个例子:
- 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 class TestDemo {
- public static void main(String[] args) {
- MyArray<Integer> 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)类名后的 <T> 代表占位符,表示当前类是一个泛型类,类型形参一般使用一个大写字母表示,常用的名称有:
(2)不能new泛型类型的数组
即:T[] ts = new T[5];//error
(3)第12行代码处:类型后加入 <Integer> 指定当前类型。泛型只能接受类,所有的基本数据类型必须使用包装类!所以<int>error!
2.泛型类的使用
- 泛型类<类型实参> 变量名; // 定义一个泛型类引用
-
- new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
注意:当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写,如:
MyArray<Integer> 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<T> {
- //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<String> myArray = new MyArray<>();
- String[] ret = myArray.getArray();//第20行代码
- }
- }
数组和泛型之间的一个重要区别是它们如何强制执行类型检查。具体来说,数组在运行时存储和检查类型信息。然而,泛型在编译时检查类型错误。
- import java.lang.reflect.Array;
- class MyArray<T> {
- public T[] array;
- public MyArray() {}
-
- 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();
- }
- class 泛型类名称<类型形参 extends 类型边界> {
- ...
- }
例如:
- class MyArray<T extends Number> {//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<String> myArray = new MyArray<>();//第18行代码
- MyArray<Integer> myArray2 = new MyArray<>();
- }
- }
第18行代码从编译开始就出错了,因为String 不是 Number 的子类型,但是Integer是。
注意:
(1)泛型只有上界,没有下界
(2)没有指定边界时,默认边界为Object
- class MyArray<T>{//默认边界为Object
- ......
- }
一个比较复杂的例子:写一个泛型类,求出数组当中的最大值
- class Alg<T extends Comparable<T>>{//此时传入的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<array[i]//error 泛型传入的类型均为类类型,不能直接用><=比较
- //②也不能用equals方法,它只能比较true和false,比较不了大小关系。两个引用比较大小,要用CompareTo方法
- max = array[i];
- }
- }
- return max;
- }
- }
- public class TestDemo {
- public static void main(String[] args) {
- Alg<Integer> alg = new Alg<>();
- Integer[] array = {1,22,3,4};
- System.out.println(alg.findMax(array));
- }
- }
编译并运行该代码,输出如下:
22
注意Integer是实现了Comparable<>接口的,如图所示:
该代码有一个不好的地方,我们每次想调用findMax方法时,必须要new一个对象;我们对其进行优化一下
1.
- 方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) {
- ...
- }
优化如下:
- class Alg2/*<T extends Comparable<T>>*/{// "/* */"的这一部分写和不写都是一样的
- public static<T extends Comparable<T>> T findMax(T[] array){//我们发现当我们加一个static后,整个代码都报错了,这是因为加了static后,我们的方法不再依赖
- //于对象,我们在使用该方法时不再new对象,但是我们的泛型传参在new对象时传参,所以如果加了一个static的
- //的话,相当于没有传参;因此我们在static处加上<T>,但是要用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./*<Integer>*/findMax(array));//"/* */"的这一部分写和不写都是一样的,它会通过array的类型来推导方法中的T是什么类型【类型推导】
- }
- }
2.泛型中的父子类关系
- public class MyArrayList<E> {
- ...
- }
- // MyArrayList<Object> Object不是 MyArrayList<Number> 中的Number的父类类型
- // MyArrayList<Number> Number也不是 MyArrayList<Integer> 的Integer父类类型
因为在编译的时候它们都被擦除了
举个例子:
- class Alg<T extends Comparable<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 = array[i];
- }
- }
- return max;
- }
- }
- public class TestDemo {
- public static void main(String[] args) {
- Alg<Integer> alg1 = new Alg<>();
- Alg<Integer> alg2 = new Alg<>();
- System.out.println(alg1);
- System.out.println(alg2);
- }
- }
编译并运行该代码,输出如下:
我们发现打印的内容中不存在<>,这说明它们被擦除了
? 用于在泛型的使用,即为通配符
通配符是用来解决泛型无法协变的问题的,协变指的就是如果 Student 是 Person 的子类,那么 List<Student> 也应该是 List<Person> 的子类。但是泛型是不支持这样的父子类关系的
- class Alg3{
- public static<T> void print(ArrayList<T> list){
- for(T x:list){
- System.out.println(x);
- }
- }
- //此时代码的参数是T,此时的T一定是将来指定的一个泛型参数
- public static<T> void print2(ArrayList<?> list){
- for(Object x:list){
- System.out.println(x);
- }
- }
- //代码中使用了统配符,和代码1相比,此时传入printList2的,具体是什么数据类型,我们是不清楚的。这就是通配符。
- }
(Ⅰ)语法
与泛型上界类似,<? extends 上界>;如:<? extends Number>//可以传入的实参类型是Number或者Number的子类
举个例子:假设有如下关系:
- public static void print(List<Animal> list) {
- ......
- }
- public static <T extends Animal> void print2(List<T> 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方法, <T extends Animal> 对T进行了限制,只能是Animal的子类 比如:传入Cat,那么类型就是Cat
②对于通配符实现的print3方法,首先不用再static后使用尖括号,其次相当于对Animal进行了规定,允许你传入Animal 的子类。具体哪个子类,此时并不清楚。比如:传入了Cat,实际上声明的类型是Animal,使用多态才能调用Cat的toString方法
(Ⅱ)通配符上界的父子类关系
(Ⅲ)通配符的上界的特点
通配符的上界不适合写入数据
- public class TestDemo {
- public static void main(String[] args) {
- ArrayList<Integer> arrayList1 = new ArrayList<>();
- ArrayList<Double> 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<Integer> arrayList1 = new ArrayList<>();
- ArrayList<Double> 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
- }
- }
通配符的下界适合写入数据,不适合读取数据
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。