13_Java泛型总结
2022/1/3 14:37:18
本文主要是介绍13_Java泛型总结,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
文章目录
- 1. 泛型概述
- 1.1 了解泛型
- 1.2 泛型的好处:
- 1.3 泛型标识符
- 2. 泛型类与接口
- 2.1 定义泛型类与接口
- 2.2 使用泛型类与接口
- 3. 泛型方法
- 3.1 为什么使用泛型方法
- 3.2 泛型方法的定义语法
- 4. 类型擦除
- 4.1 概念
- 4.2 类型擦除前后比较
- 4.3 代码验证:
- 5. 类型通配符
- 5.1 通配符的引入
- 5.2 类型通配符的上限
- 5.3 类型通配符的下限
- 6. 泛型与数组
1. 泛型概述
1.1 了解泛型
-
泛型,即参数化类型。
-
泛型的本质是为了参数化类型
-
参数化类型,即将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)
-
在没有泛型之前,我们只能定义一个类型为Object的集合,使得该集合可以存放任意的数据类型对象
然后在我们使用该集合之后,我们必须明确知道这个集合存储元素的数据类型,否则很容易引发ClassCastException异常,此时只能使用强制类型转换
-
但有了泛型之后,我们在定义类时使用泛型,这样子也使得这个类可以存放任意的数据类型对象
当使用这个类时通过给泛型赋值,就确定了这个类中存取的数据类型,这样子如果我们存放的数据类型不正确时,会直接在编译中报错提醒程序员写错了
1.2 泛型的好处:
由上面介绍,很自然的,泛型的好处为:
- 类型安全
- 消除了强制类型的转换
1.3 泛型标识符
- E - Element (在集合中使用,因为集合中存放的是元素)
- T - Type(Java 类)
- K - Key(键)
- V - Value(值)
- N - Number(数值类型)
- ? - 表示不确定的java类型,在使用泛型的时候才用这个,是实参,以上都是定义的时候使用
- 定义泛型类或方法时,直接将这些泛型标识符当成具体数据类型使用即可
2. 泛型类与接口
2.1 定义泛型类与接口
- 泛型类的定义语法:
class 类名 <泛型标识, 泛型标识...> { private 泛型标识 变量名; ... }
- 泛型接口的定义语法:
interface 接口名 <泛型标识, 泛型标识...> { 泛型标识 方法名(); // 返回值为泛型的方法 ... }
2.2 使用泛型类与接口
- 泛型类对象的使用语法:
类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>(); // 从jdk1.7开始,后面的<>中具体的数据类型可省略不写: 类名<具体的数据类型> 对象名 = new 类名<>(); // 示例:使用集合类 List<String> list = new ArrayList<>();
-
泛型类的派生子类:
1.如果子类也是泛型类,则父类和子类的泛型类型要一致(可以扩容):
class Child<T> extends Parent<T>
2.如果子类不是泛型类,则子类继承时要指定父类泛型的具体数据类型:
class Child extends Parent<String>
-
泛型接口的使用(实现类):
1.实现类也是泛型类,则实现类和接口的泛型类型要一致(可以扩容):
class Child<T,E> implement 接口名<T> { // 扩容的例子 // 可以定义泛型变量与实现方法了 }
2.如果实现类不是泛型类,则要明确接口泛型的数据类型:
class Child implement 接口名<String>
泛型类的注意事项:
-
如果使用泛型类定义对象时,没有指定具体的数据类型,则默认数据类型为Object
-
泛型的类型参数只能是类类型,不能是基本数据类型
-
泛型类型在逻辑上可以看成多个不同的类型,但实际上都是相同类型(运行前会进行类型擦除)
因此也就不能使用泛型类型不一样的方法重载了(也可以理解为类型擦除之后都一样)
-
同时不能对确切的泛型类型使用instanceof操作
- 以ArrayList部分源码作为示例:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { public E set(int index, E element) { Objects.checkIndex(index, size); E oldValue = elementData(index); elementData[index] = element; return oldValue; } private void add(E e, Object[] elementData, int s) { if (s == elementData.length) elementData = grow(); elementData[s] = e; size = s + 1; } }
3. 泛型方法
3.1 为什么使用泛型方法
- 泛型类定义的泛型,在整个类中有效。
- 泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了,所以如果此时在泛型类中直接定义方法,其实方法里的泛型类型早就确定了
- 因此引入了泛型方法,泛型方法能使方法独立于类而产生变化,以下是一个定义泛型的基本指导原则:
无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法能将整个类泛型化,那么就应该使用泛型方法。另外对于一个
static
的方法而言,无法访问泛型类型的参数。所以如果static
方法要使用泛型能力,就必须使其成为泛型方法。
- 特殊之处:
静态方法不可以访问类上定义的泛型
如果静态方法操作的应用数据类型不确定,可以将泛型定义在方法上。
-
泛型类与泛型方法的区别:
泛型类:是在实例化该类时指定泛型的具体类型
泛型方法:是在调用方法时指定泛型的具体类型
3.2 泛型方法的定义语法
- 语法:
权限修饰符 <T,E...> 返回值类型 方法名(形参列表){ ... } // 示例: // 表示返回值类型为E,参数类型也为E的方法: public <E> E fun(E e){ }
4. 类型擦除
4.1 概念
由于泛型是java1.5之后才引入的概念,在此之前是没有泛型的
那为什么泛型代码能向下兼容呢?
原因是泛型信息只存在代码编译阶段,在进入JVM之前与泛型相关的信息都会被擦除,也就是说,泛型信息不会进入到运行时阶段
4.2 类型擦除前后比较
- 无限制类型的类型擦除:
public class A<T> { private T key; public T getKey(){ return key; } public void setKey(T key){ this.key = key; } } // 泛型擦除后: public class A { private Object key; public Object getKey(){ return key; } public void setKey(Object key){ this.key = key; } }
- 有限制类型的类型擦除:
public class A<T extends Number> { private T key; public T getKey(){ return key; } public void setKey(T key){ this.key = key; } } // 泛型擦除后: public class A { private Number key; public Number getKey(){ return key; } public void setKey(Number key){ this.key = key; } }
- 泛型方法的类型擦除:
public <T extends Number> T getValue(T value){ return value; } // 泛型擦除后: public Number getValue(Number value){ return value; }
- 非泛型类实现泛型接口后实现方法的类型擦除使用桥接方法:
public interface Info<T> { T info(T var); } public class InfoImpl implement Info<Integer> { @Override public Integer info(Integer var) { return var; } } // 类型擦除后: public interface Info { Object info(Object var); } public class InfoImpl implement Info { public Integer info(Integer var) { return var; } // 桥接方法,保证接口与类的实现关系 @Override public Object info(Object var) { return info( (Integer)var ); } }
4.3 代码验证:
List<String> strList = new ArrayList<>(); List<Integer> intList = new ArrayList<>(); Class classStrList = strList.getClass(); Class classIntList = intList.getClass(); if(classStrList.equals(classIntList)){ System.out.println("泛型类型实质是相同的"); }
输出结果:
泛型类型实质是相同的
5. 类型通配符
5.1 通配符的引入
我们知道Ingeter是Number的一个子类,同时在类型擦除中我们也验证过Generic<Ingeter>与Generic<Number>实际上是相同的一种基本类型。那么问题来了,在使用Generic<Number>作为形参的方法中,能否使用Generic<Ingeter>的实参传入呢?在逻辑上类似于Generic<Number>和Generic<Ingeter>是否可以看成具有父子关系的泛型类型呢?
实际上,这种类似多态的形参实参传入方法是错误的,因为同一种泛型可以对应多个版本(因为参数类型是不确定的),而不同版本的泛型类实例是不兼容的。
对此,Java引入了泛型类型通配符:?
,类型通配符可以代替具体的类型实参
也就是说,?
是一个类型实参,而不是类型形参
以ArrayList构造器示例:
public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // defend against c.toArray (incorrectly) not returning Object[] // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
我们注意到,在ArrayList构造器中的参数中,写法是<? extends E>,而E是ArrayList定义类时使用的泛型标识符,那这个extends又是什么意思呢?这就涉及了通配符的上限与下限了:
5.2 类型通配符的上限
语法:
类/接口<? extends 实参类型1>
这表示该泛型的类型只能是实参类型1或者是实参类型1的子类,这也就限定了类型通配符的上限
代码示例:
public void show(List<? extends Number> list) { ... // 如果只使用?,不使用extends,则定义变量list时list中的泛型参数默认为Object,此时list中的元素变成其他类型需要强转 // 但使用extends之后,要求list中的泛型参数默认为上限Number,下限则可以是任意Number的子类 }
5.3 类型通配符的下限
语法:
类/接口<? super 实参类型2>
表示该泛型的类型只能是实参类型2,或者是实参类型2的父类类型
6. 泛型与数组
-
当创建泛型数组的时候,可以说明带泛型的数组引用(即等号前面),但是不能直接创建带泛型的数组对象
因为数组在编程期需要全程知道数据类型,而泛型在编译期中会有类型擦除
但是可以通过匿名数组的形式将数组引用传递给泛型数组:
ArrayList<String>[] listarr = new ArrayList<>[5]; // 报错 // 通过匿名数组引用传递给泛型数组: ArrayList<String>[] listarr = new ArrayList[5];// 后半部分为匿名非泛型数组 // 相当于: ArrayList[] list = new ArrayList[5];// 注意两个的后半部分 ArrayList<String>[] listarr = list; // 以上相当于这俩句 /** 但是要使用匿名数组引用传递给泛型数组 而不能创建普通数组再传递(防止因为类型被改来改去不一致) */
-
可以通过java.lang.reflect.Array的newInstance(Class, int)来创建T<>数组(Array.newInstance)
(但是在真正开发中尽量使用泛型集合代替泛型数组)
返回值 方法 描述 static Object newInstance(类<?> componentType(成分类型, int length) 创建具有指定组件类型和长度的新数组 static Object newInstance(类<?> componentType, int… dimensions) 创建具有指定组件类型和尺寸的新数组
注意: 因为返回Object所以需要强转!
T[] arr = new T(5); // 报错,因为还不知道T类型 // 只能通过Array.newInstance来创建T<>数组 // 示例: public class Fruit<T> { private T[] arr; // 不能直接初始化 // 通过构造方法调用Array.newInstance创建泛型数组 public Fruit(Class<T> clz, int len){ arr = (T[])Array.newInstance(clz, len); } // 填充数组放入元素 public void put(int index, T item){ arr[index] = item; } // 获取数组元素 public T get(int index){ return arr[index]; } // 获取数组 public T[] getArr(){ return arr; } } // 使用上面写的类 public class Test{ public static void main(String[] agrs){ Fruit<String> fruit = new Fruit<>(Stirng.class, 3); // 创建String类型,将String.class传进去 // 接着通过上面创建的三个方法使用 } }
这篇关于13_Java泛型总结的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南