【Java基础】6.泛型

《Java基础》目录

例如:第一章 Python 机器学习入门之pandas的使用


文章目录

  • 《Java基础》目录
    • [6. 泛型](#6. 泛型)
      • [6.1 泛型类](#6.1 泛型类)
      • [6.2 泛型方法](#6.2 泛型方法)
      • [6.3 类型变量的限定](#6.3 类型变量的限定)
      • [6.4 泛型代码和虚拟机](#6.4 泛型代码和虚拟机)
        • [6.4.1 类型擦除](#6.4.1 类型擦除)
        • [6.4.2 转换泛型表达式](#6.4.2 转换泛型表达式)
        • [6.4.3 转换泛型方法](#6.4.3 转换泛型方法)
        • [6.4.4 总结](#6.4.4 总结)
      • [6.5 限制与局限性](#6.5 限制与局限性)
        • [6.5.1 不能用基本类型实例化类型参数](#6.5.1 不能用基本类型实例化类型参数)
        • [6.5.2 运行时类型查询只适用于原始类型](#6.5.2 运行时类型查询只适用于原始类型)
        • [6.5.3 不能创建参数化类型的数组](#6.5.3 不能创建参数化类型的数组)
        • [6.5.4 `Varargs`警告](#6.5.4 Varargs警告)
        • [6.5.5 不能实例化类型变量](#6.5.5 不能实例化类型变量)
        • [6.5.6 泛型类的静态上下文中类型变量无效](#6.5.6 泛型类的静态上下文中类型变量无效)
        • [6.5.7 不能抛出或捕获泛型类的实例](#6.5.7 不能抛出或捕获泛型类的实例)
        • [6.5.8 可以取消检查型异常的检查](#6.5.8 可以取消检查型异常的检查)
        • [6.5.9 擦除后的冲突](#6.5.9 擦除后的冲突)
      • [6.6 泛型类型的继承](#6.6 泛型类型的继承)
      • [6.7 通配符类型](#6.7 通配符类型)
        • [6.7.1 通配符概念](#6.7.1 通配符概念)
        • [6.7.2 通配符的超类限定](#6.7.2 通配符的超类限定)
        • [6.7.3 无限定通配符](#6.7.3 无限定通配符)
      • [6.8 反射和泛型](#6.8 反射和泛型)
        • [6.8.1 泛型`Class`类](#6.8.1 泛型Class类)

6. 泛型

泛型可以使代码对不同类型的对象重用。

6.1 泛型类

泛型类就是有一个或多个类型变量的类。该节使用Pair类进行示范,该类使我们只需要关注泛型,而不用为数据存储细节而分心。

java 复制代码
public class Pair<T>
{
    private T first;
    private T second;
    public Pair(T f,T s){first=f;second=s;}
    public T getFirst(){return first;}
}

Pair类引入了类型变量T,用尖括号括起来,放在类名的后面。泛型类可以有多个类型变量,用不同的变量表示。如public class Pair<T,U>

一般情况类型变量用短且大写的字母表示。

  • E:表示集合的元素类型
  • K,V:分别表示键和值的类型
  • T:必要时可以使用相邻的字母U或S表示任意类型

6.2 泛型方法

除了定义泛型类,该可以在普通类中定义带类型参数的泛型方法。

java 复制代码
class ArrayAlg{
    public static <T> T getMiddle(T... a){
        return a[a.length/2];
    }
}

类型变量要放在修饰符的后面,并且放在返回类型的签名。

当进行调用的时候,可以把具体类型放在尖括号中,放在方法名签名:

String s =ArrayAlg.<String>getMiddle("a","b","c")

在这种情况下,可以省略类型参数,编译器可以直接进行类型转换。

String s=ArrayAlg.getMiddle("a","b","c")

这个利用了Java的自动装箱机制,但当同时出现了不同类型的参数的时候,有可能会导致报错,只需要调整一下传递的参数类型即可。

6.3 类型变量的限定

如果定义了一个比较大小的泛型方法,但是如何确定这个具体类型中具有comparaTo的比较方法呢?

只需要在定义泛型方法的时候,对类型变量进行一个限定来实现这一要求。

public static <T extends Comparable> T min(T[] a)

实际上Comparable接口本身就是一个泛型类型,而这里使用的是extend而非implements

这里的extends是因为限定类型更接近于子类型的概念。同时多个限定可以用&来连接

6.4 泛型代码和虚拟机

虚拟机没有泛型类型,只有普通类,所以编译器在编译过程中会进行类型擦除。

6.4.1 类型擦除

无论如何定义一个泛型类型,都会自动提供一个与其对应的普通类,此时类型变量就会被擦除,替替换为其限定类型(若无限定类型则是Object类)。

6.4.2 转换泛型表达式

编写泛型方法时除了进行类型擦除,还有对其进行强制类型转换。

比如

java 复制代码
Pair<Empty> b=...
Empty d=b.getFirst();

其中getFirst进行类型擦除后返回的是Object类型,编译器自动插入转换到Empty的强制类型转换。

访问同一个泛型字段的时候也要进行强制类型转换。

6.4.3 转换泛型方法

类型擦除也会出现在泛型方法中。

例如:

public static <T extends Comparable> T min(T[] a)

进行类型擦除后只剩下一个方法

public static Comparable min(Comparable[] a)

类型擦除会和多态产生冲突

  1. 没有返回值类型
java 复制代码
class DateInterval extends Pair<LocalDate>{
    public void setFirst(LocalDate f){
        if(f.getFirst()>=0)
            super.setFirst(f);
    }
}

类型擦除后会出现两个同名方法:

  • public void setFirst(LocalDate f)
  • public void setFirst(Object f)

为解决这个方法,编译器在DateInterval类中生成一个桥方法

public void setFirst(Object f){setFirst((LocalDate) f)}

  1. 带有返回值类型

虚拟机中会由参数类型和返回值类型共同指定一个方法。所以如果只使用桥方法,在下面的情况就会很奇怪。

java 复制代码
class DateInterval extends Pair<LocalDate>{
    public LocalDate getFirst(){
        return (LocalDate) super.getFirst();
    }
}

但是就会出现两种方法:

  • public LocalDate getFirst()
  • public Object getFirst()

这种情况可以通过对不同的方法生成字节码,虚拟机就可以正确的处理这种情况。

6.4.4 总结

对于Java的类型转换,会遇到一下事实:

  • 虚拟机中没有泛型,只有普通的类和方法
  • 所有的类型参数都会替换为他们的限定类型
  • 会合成桥方法在类型擦除下保持多态
  • 为保持类型安全性,必要时会插入强制类型转换

6.5 限制与局限性

Java中泛型类型的限制,大部分都是由于编译器进行类型擦除所引起的。

6.5.1 不能用基本类型实例化类型参数

不能用基本类型替代类型参数,所以没有Pair<double>只有Pair<Double>

原因就是类型擦除后的Pair类只有Object字段,而Object不能存储double值。

6.5.2 运行时类型查询只适用于原始类型

虚拟机中的对象总有一个特定的非泛型类型。因此所有类型查询只产生原始类型。

例如a instanceof Pair<String>实际上测试a instanceof Pair<T>或强制类型转换

为了提示这一风险,在对泛型类型进行查询时,会得到一个编译器错误,或是警告。

6.5.3 不能创建参数化类型的数组

不能实例化参数化类型的数组。table=new Pair[10]

因为执行类型擦除后,就会变成Object[],数组会记住他的数据类型,如果数组试图存储其他类型的元素,就会抛出异常。

可以声明但不可以实例化创建

6.5.4 Varargs警告

向参数可变长方法传递泛型参数。

为了调用这个方法,Java虚拟机创建一个数组来存放参数,就违反了不能实例化类型数组的规则。但不会报错,只会提供警告。

可以采用两种方法来解决。

  1. 为调用的方法增加注解SuppressWarnings("unchecked")
  2. 直接使用SafeVarargs注解(该注解只能用于声明为staticfinalprivate的构造器或方法)
6.5.5 不能实例化类型变量

不能使用诸如new T()的构造方法,因为不会有人希望会new一个Object对象。

java8之后,最好的解决办法就是提供一个构造器表达式,比如

Pair<String> p=Pair.makePair(String::new);

该方法接受一个Supplier<T>,这是一个函数式接口,表示一个无参函数且返回类型为T的函数。

传统的方法是使用Java的反射机制,同时不能使用T.class,需要适当设计API

6.5.6 泛型类的静态上下文中类型变量无效

不能在静态字段或方法中引用类型变量。例如

java 复制代码
public class Singleton<T>{
    private static T singleInstance;
}

进行类型擦除后,只会剩下包含一个singleInstance字段的Singleton类,所以禁止使用带有类型变量的静态字段和方法。

6.5.7 不能抛出或捕获泛型类的实例

既不能抛出也不能捕获泛型类的对象,实际上泛型类可以扩展Throwable甚至都不是合法的。

catch语句中不能使用类型变量,因为会导致不能编译。

但是在异常规范中,使用类型变量是允许的。

不允许:

java 复制代码
public static <T extends Throwable> void do(){
    try{
        do...
    }catch(T e){
        ...
    }
}

允许:

java 复制代码
public static <T extends Throwable> void do(){
    try{
        do...
    }catch(Throwable e){
        ...
    }
}
6.5.8 可以取消检查型异常的检查

Java异常处理的原则就是,必须为所有检查型异常提供一个处理器。不过可以利用泛型取消该机制。

使用该方法

java 复制代码
@SuppressWarnings("unchecked")
static <T extends Throwable> void throws(Throwable t) throws T{
    throw (T) t;
}

通过使用泛型类,擦除和注解,实现欺骗编译器,使其将异常识别为非检查型异常,达到消除Java类型系统的部分基本限制。

6.5.9 擦除后的冲突

当泛型类型被擦除后,不允许创建引发冲突的条件。

为了支持擦除转换,倘若两个接口类型是同一个接口的不同参数化,一个类或类型变量就不能同时作为这两个接口类型的子类。

6.6 泛型类型的继承

泛型类可以继承泛型类,但是泛型类的具体类型没有继承关系。

比如:ManagerEmployee是继承关系,但是Pair<Manager>Pair<Employee>没有继承关系。

同理,ArrayList<T>继承了List<T>,但是ArrayList<Manager>List<Manager>没有继承关系。

6.7 通配符类型

6.7.1 通配符概念

在通配符类型中,允许类型参数发生变化,例如Pair<? extends Employee>表示任何泛型Pair类型,他的我类型参数是Employee的子类。

加入要实现这个方法:

java 复制代码
public static void print(Pair<Employee> p){
    ...
}

8.6 所说,不能将Pair<Manager>传递给这个方法,但是可以使用通配符解决这个问题。如:

java 复制代码
public static void print(Pair<? extends Employee> p){
    ...
}
6.7.2 通配符的超类限定

通配符限定和类型变量限定很相似,但是还有附加的功能,就是可以指定一个超类型限定,如:

? super Manager

可以限制为Manager的所有超类型。

6.7.3 无限定通配符

还可以使用根本无限顶的通配符,例如Pair<?>

带有无限定通配符的可以被任意Object对象调用原始类中的方法。

6.8 反射和泛型

6.8.1 泛型Class

Class类是泛型的,例如String.class实际就是Class<String>类的对象。

Class方法 说明
T newInstance() 返回无参构造器构造的一个新实例
T cast(Object obj) 如果obj为null或有可能转换成类型T,则返回obj;否则抛出异常
T[] getEnumConstants() 如果T是枚举类型,返回所有值组成的数组;否则返回null
Class<? super T> getSuperclass() 返回这个类的超类,如果不是类或Object对象,返回null
Constructor<T> getConstructor(Class... parameterTypes) 获得公共构造器
Constructor<T> getDeclaredConstructor(Class... parameterTypes) 获得有给定参数类型的构造器
TypeVariable[] getTypeParameters() 这个类型被声明为泛型,则获得泛型变量,否则获得长度为0的数组
Type getGenericSuperclass() 获得这个类型所声明的超类的泛型类型;否则返回null
Type[] getGenericInterfaces() 获得这个类所声明的接口的泛型类型;否则返回长度为0的数组
Constructor方法 说明
T newInstance(Object... parameters) 返回用指定参数构造的新实例
Method方法 声明
TypeVariable[] getTypeParameters() 获得泛型类型变量;否则返回长度0的数组
Type getGenericReturnType() 获得这个方法声明的泛型返回类型
Type[] getGenericParameterTypes() 获得声明的泛型参数类型;否则返回长度0数组
TypeVariable方法 声明
String getName() 获得这个类型变量的名字
Type[] getBounds() 获得这个类型变量的子类限定,否则返回长度0数组
WildcardType方法 声明
Type[] getUpperBounds() 获得子类限定;否则返回长度0数组
Type[] getLowerBounds() 获得超类限定;否则返回长度0数组
ParameterizedType方法 声明
Type getRawType() 获得原始类型
Type[] getActualTypeArguments() 获得参数化类型声明的类型参数
Type getOwnerType() 如果是内部类型,返回其外部类型;否则返回null
GenricArrayType方法 声明
Type getGenericComponentType 获得数组类型声明的泛型元素类型
相关推荐
青云交1 小时前
Java 大视界 -- Java 大数据在智能安防视频摘要与检索技术中的应用(128)
java·大数据·图像处理·机器学习·视频检索·智能安防·视频摘要
Biehmltym1 小时前
【架构差异】SpringとSpringBoot:Bean机制的深入剖析与自动配置原理
java·spring boot·spring
XiaoyuEr_66882 小时前
在java中使用RabbitMQ的步骤
java·rabbitmq·java-rabbitmq
恋恋风辰3 小时前
QT系列教程(16) 定时器事件
开发语言·qt·命令模式
Tttian6224 小时前
Spring
java·后端·spring
南山不太冷4 小时前
Spring(4)——响应相关
java·后端·spring
kill bert4 小时前
第27周JavaSpringboot电商进阶开发 1.企业级用户验证
java·前端·数据库
拓端研究室TRL6 小时前
R软件线性模型与lmer混合效应模型对生态学龙类智力测试数据层级结构应用
开发语言·r语言
于慨7 小时前
计算机考研C语言
c语言·开发语言·数据结构
GGGGGGGGGGGGGG.7 小时前
使用dockerfile创建镜像
java·开发语言