13_Java泛型总结
作者:互联网
文章目录
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,class,类型,擦除,key,泛型,public 来源: https://blog.csdn.net/qq_41781632/article/details/122287070