Java中的Type类型详解

导读:本篇文章讲解 Java中的Type类型详解,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

概述

在JDK1.5之前之一原始类型。此时,所有的原始类型都通过字节码文件类Class进行抽象。Class类的一个具体对象就代表一个指定的原始类型。
JDK1.5加入了泛型类,扩充了数据类型,从只有原始类型基础上扩充了 参数化类型、类型变量类型、通配符类型、泛型数组类型。
Type是Java语言中所有类型(Class)的公共父接口。

Type类关系图

在这里插入图片描述
从做到右依次是:Class(原始/基本类型):实现了Type接口,GenericArrayType(泛型数组类型),ParameterizedType(参数化类型),WildcardType(通配符类型),TypeVariable(类型变量)。

  • Class:不仅包括我们平常所指的类、枚举、数组、注解,还包括基本类型int、float等等。
  • TypeVariable:比如List 中的T等。
  • WildcardType:也叫做泛型表达式类型,例如List<? extends Number> 这种。
  • ParameterizedType:就是我们平常所用到的泛型List、Map(注意和TypeVariable的区别,参数化类型表示的是List这样的一个整体而不是T)。
  • GenericArrayType:泛型数组类型,并不是我们工作中所使用的数组String[]、Byte[],这些都是Class,而是带有泛型的数组,即T[]。

Type接口本身算是一个标记接口,不提供任何需要复写的方法。

ParameterizedType 参数化类型

参数化类型,即平常用的泛型;例如:List、Map<K,V>等带有参数化的对象。
如何理解呢?
一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化(比如:List,T就是类型参数),类似于方法中的参数变量,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

public interface ParameterizedType extends Type {
 	// 获取类型内部的参数化类型,比如Map<K,V>里面的K,V类型;
 	// 注意该方法只返回最外层的<>中的类型,无论该<>内有多少个<>。
    Type[] getActualTypeArguments();
 	// 类的原始类型,一般都是Class
    Type getRawType();
	// 获取所有者类型(只有内部类才有所有者,比如Map.Entry的所有者就是Map),如果不是内部类,返回null。
    Type getOwnerType();
}

具体用法

@Slf4j
public class ParameterizedTypeTest {
    private Map<String, ParameterizedTypeTest> map;
    private Set<String> setStr;
    private Class<?> clz;
    private Holder<String> holder;
    private List<String> listStr;
    private ArrayList<String> arrayList;
    private Map.Entry<String, String> entry;

    // 非参数化类型
    private String str;
    private Integer i;
    private Set set;
    private List list;

    static class Holder<V> {
    }

    public static void main(String[] args) {
        try {
            // 拿到所有的字段
            Field[] fields = ParameterizedTypeTest.class.getDeclaredFields();
            Arrays.stream(fields).forEach(item -> {
                if (item.getGenericType() instanceof ParameterizedType) {
                    item.setAccessible(true);
                    ParameterizedType parameterizedType = (ParameterizedType) item.getGenericType();
                    log.info("{} :", item.getName());
                    log.info("getActualTypeArguments: {}", Arrays.asList(parameterizedType.getActualTypeArguments()));
                    log.info("getRawType: {}", parameterizedType.getRawType());
                    log.info("getOwnerType: {}", parameterizedType.getOwnerType());
                } else {
                    log.info("{} is not ParameterizedType", item.getName());
                }
            });
        } catch (Exception e) {
            log.info("exception:", e);
        }
    }
}

输出:

map :
getActualTypeArguments: [class java.lang.String, class com.study.concurrentprogramming.genericiy.ParameterizedTypeTest]
getRawType: interface java.util.Map
getOwnerType: null
setStr :
getActualTypeArguments: [class java.lang.String]
getRawType: interface java.util.Set
getOwnerType: null
clz :
getActualTypeArguments: [?]
getRawType: class java.lang.Class
getOwnerType: null
holder :
getActualTypeArguments: [class java.lang.String]
getRawType: class com.study.concurrentprogramming.genericiy.ParameterizedTypeTest$Holder
getOwnerType: class com.study.concurrentprogramming.genericiy.ParameterizedTypeTest
listStr :
getActualTypeArguments: [class java.lang.String]
getRawType: interface java.util.List
getOwnerType: null
arrayList :
getActualTypeArguments: [class java.lang.String]
getRawType: class java.util.ArrayList
getOwnerType: null
entry :
getActualTypeArguments: [class java.lang.String, class java.lang.String]
getRawType: interface java.util.Map$Entry
getOwnerType: interface java.util.Map
str is not ParameterizedType
i is not ParameterizedType
set is not ParameterizedType
list is not ParameterizedType

先看后面几个,发现即使是List,但是我们没给与泛型,它也不是ParameterizedType参数化类型,因此如果想成为泛型类型必须要指定泛型参数才行。

TypeVariable 类型变量

泛型信息在编译时会被转换成一个特定的类型,而TypeVariable就是用来反映在JVM编译该泛型前的信息。(通俗的讲,TypeVariable就是我们常用的List 、Map<K,V>中的T,K这种泛型变量)。
还可以对类型变量加上extends限定,这样会有类型变量对应的上限;值得注意的是,类型变量的上限可以有多个,必须使用&连接,例如:
List<T extends Number & Serializable>,其中&后必须是接口:

public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
 	// 类型变量对应的上边界,如果没有指定上限,返回Object 可以有多个
    Type[] getBounds();
    // 获取类型变量所在类的Type,比如TypeVariableTest<T>类,getGenericDeclaration()得到的就是TypeVariableTest。
    D getGenericDeclaration();
	// 获取类型变量在源码中定义的名称
    String getName();
	// JDK8新增的,获取注解类型的上限数组
     AnnotatedType[] getAnnotatedBounds();
}

具体用法

public class TypeVariableTest<K extends Number, T> {
    // K有指定上边界 Number
    private K key;
    // T没有指定上边界,其默认上边界为Object
    private T value;

    public static void main(String[] args) {
        TypeVariable<Class<TypeVariableTest>>[] typeParameters = TypeVariableTest.class.getTypeParameters();
        for (TypeVariable<Class<TypeVariableTest>> type : typeParameters) {
            int index = type.getBounds().length - 1;
            // 输出上边界
            System.out.println("---getBounds()-- " + type.getBounds()[index]);
            // 输出名称
            System.out.println("---getName()-- " + type.getName());
            // 输出所在的类的类型
            System.out.println("---getGenericDeclaration()-- " + type.getGenericDeclaration());
        }
    }
}

输出:

---getBounds()-- class java.lang.Number
---getName()-- K
---getGenericDeclaration()-- class com.study.concurrentprogramming.genericiy.TypeVariableTest
---getBounds()-- class java.lang.Object
---getName()-- T
---getGenericDeclaration()-- class com.study.concurrentprogramming.genericiy.TypeVariableTest

GenericArrayType 泛型数组类型

描述的是形如:A[](参数化类型数组)或者T[](类型变量数组)。
它的组成元素是ParameterizedType或TypeVariable类型。

无论从左向右右几个[]并列,这个方法仅仅脱去最右边的[]之后剩下的内容就作为这个方法的返回值。

public interface GenericArrayType extends Type {
    // 返回泛型数组中成员类型,即List<String>[]中的List<String>
    Type getGenericComponentType();
}

具体用法

public class GenericArrayTypeTest<T> {
    // 泛型数组类型
    private T[] value;
    private List<String>[] lists;

    // 非泛型数组类型
    private List<String> list;
    private T singleValue;

    public static void main(String[] args) {
        Field[] declaredFields = GenericArrayTypeTest.class.getDeclaredFields();
        Arrays.stream(declaredFields).forEach(field -> {
            field.setAccessible(true);
            // 输出当前变量是否为GenericArrayType类型
            System.out.println("Field: " + field.getName() + "; instanceof GenericArrayType: "
                    + (field.getGenericType() instanceof GenericArrayType));
            if (field.getGenericType() instanceof GenericArrayType) {
                // 输出泛型类型
                System.out.println("Field: " + field.getName() + "; getGenericComponentType():"
                        + ((GenericArrayType) field.getGenericType()).getGenericComponentType());
            }
            field.setAccessible(false);
        });
    }
}

输出:

Field: value; instanceof GenericArrayType: true
Field: value; getGenericComponentType():T
Field: lists; instanceof GenericArrayType: true
Field: lists; getGenericComponentType():java.util.List<java.lang.String>
Field: list; instanceof GenericArrayType: false
Field: singleValue; instanceof GenericArrayType: false

WildCardType 通配符类型

表示通配符类型,比如<?>,<? extends Number>等。

如果没有指定上边界,则默认Object,如果没有指定下边界,则默认为String。

public interface WildcardType extends Type {
 	// 获得泛型表达式上边界,表达式中使用extends
    Type[] getUpperBounds();
 	// 或者泛型表达式的下边界,表达式中使用super
    Type[] getLowerBounds();
}

具体用法

public class WildcardTypeTest {
    // 使用通配符类型参数的方法
    public void testWildcardType(List<? extends OutputStream> outputStreams, List<? super InputStream> inputStreams,
                                 List<Integer> list, InputStream inputStream) {
    }

    public static void main(String[] args) {
        // 获取WildcardTypeTest类的所有方法(本例中是testWildcardType方法)
        Method[] declaredMethods = WildcardTypeTest.class.getDeclaredMethods();
        for (Method method : declaredMethods) {
            System.out.println("method name: " + method.getName());
            // 获取方法的所有参数类型
            Type[] genericParameterTypes = method.getGenericParameterTypes();
            for (Type type : genericParameterTypes) {
                System.out.println("type: " + type.toString());
                // 如果不是参数化类型则直接continue
                if (!(type instanceof ParameterizedType)) {
                    continue;
                }
                // 将当前类型强转为参数化类型并获取实际参数(即含有通配符的泛型类型)
                Type actualTypeArgument = ((ParameterizedType) type).getActualTypeArguments()[0];
                // 输出其是否为通配符类型
                System.out.println("type instanceof WildcardType: "
                        + (actualTypeArgument instanceof WildcardType));
                if (actualTypeArgument instanceof WildcardType) {
                    int lowIndex = ((WildcardType) actualTypeArgument).getLowerBounds().length - 1;
                    int upperIndex = ((WildcardType) actualTypeArgument).getUpperBounds().length - 1;
                    // 输出上边界与下边界
                    System.out.println("getLowerBounds(): " +
                            (lowIndex >= 0 ? ((WildcardType) actualTypeArgument).getLowerBounds()[lowIndex] : "String")
                            + ";getUpperBounds(): " +
                            (upperIndex >= 0 ? ((WildcardType) actualTypeArgument).getUpperBounds()[upperIndex] : "Object"));
                }
            }
        }
    }
}

输出:

method name: main
type: class [Ljava.lang.String;
method name: testWildcardType
type: java.util.List<? extends java.io.OutputStream>
type instanceof WildcardType: true
getLowerBounds(): String;getUpperBounds(): class java.io.OutputStream
type: java.util.List<? super java.io.InputStream>
type instanceof WildcardType: true
getLowerBounds(): class java.io.InputStream;getUpperBounds(): class java.lang.Object
type: java.util.List<java.lang.Integer>
type instanceof WildcardType: false
type: class java.io.InputStream

表达式中没有指定上限,默认都是有上限class java.lang.Object,但是没有默认下线。

泛型中使用&(并且)操作符

我们有时候可以看到泛型搭配上 & 的使用方式,比如:

public <R extends Enum<R> & BaseIntEnum> List<R> parse2Enums(...){...}

说明一点:& 不能用于 ? 通配符上(因为通配符不能放在泛型的申明上),& 只能放在泛型的声明上,比如类似这种:

    // 泛型类上申明,约束泛型类变量
    class WildcardTypeT<T extends Comparable<T> & List<T> & Serializable> {
        
    }
    // 方法上申明
    public <R extends Enum<R> & Serializable> List<R> parse2Enums(){}

需要注意的是,&后面只能放置接口,不能是具体的类型,即使是Object也不行。
因此当需要多重约束的时候,可以使用&操作符,但是它不能用于super上 ,因为java有规定:

    // 合法的
    class A<T extends Number & Serializable> {}
    // 不合法的
    class B<T super Number & Serializable>

原因参考:https://stackoverflow.com/questions/37411256/why-super-keyword-in-generics-is-not-allowed-at-class-level

与泛型有关的类型不能和原始类型统一到Class的原因

产生泛型擦除的原因

  • 原始类型和新产生的类型都应该统一成各自的字节码文件类型对象。但是由于泛型不是最初Java中的成分,如果真的加入了泛型,涉及到JVM指令集的修改,这是非常致命的(简单的说就是Java要向下兼容,所以它的泛型是个加东西,只是语法层面的技术)。
  • 泛型仅仅存在于编译阶段,当在JVM运行的过程中,于泛型相关的信息将会被擦除,如果List于List都将会在运行时被擦除成List这个类型。而类型擦除机制存在的原因正是因为如果在运行时存在泛型,那么将要修改JVM指令集,这是非常致命的。
  • 我们其实可以通过定义类的方式,在类信息中保留泛型信息,从而在运行时获得这些泛型信息。
    简而言之,Java的泛型擦除是有范围的,即类定义中的泛型是 不会 被擦除的。

引入Type的原因

原始类型会生成字节码文件对象,而泛型相关的类型不会生成与其相对应的字节码文件(因为泛型类型将会被擦除),因此,无法将泛型相关的新类型与class相统一。
因此为了程序的扩展性以及为了开放需要去反射操作这些类型,就引入了Type这个类型,并且新增了ParameterizedType、TypeVariable、GenericArrayType、WildcardType四个表示泛型相关的类型,再加上Class,这样就可以用Type类型的的参数来接收以上5种子类的实参或者返回值类型就是Type类型的参数。统一了与泛型有关的类型和原始类型Class。

总结

  • Type是JDK5开始引入的,其引入主要是为了泛型,没有泛型之前,只有所谓的原始类型。此时,所有的原始类型都通过字节码文件类Class类进行抽象。Class类的一个具体对象就代表一个指定的原始类型。
  • 泛型出现之后,也就扩充了数据类型。从只有原始类型扩充了参数化类型、类型变量类型、泛型数组类型,也就是Type的子接口。
  • 那为什么没有统一到Class下,而是增加了一个Type呢?(Class是种类的意思,Type是类型的意思)。
  1. 是为了程序的扩展性,最终引入了Type接口作为Class、ParameterizedType、GenericArrayType、TypeVariable和WildcardType这几种类型的总的父接口。
  2. 这样实现Type类型参数接受以上五种子类实参或者返回值类型就是Type类型的参数,Type作为媒介统一处理原始类型和泛型相关类型。

List<T ? extends String>[]:这里List<T ? extends String>就是ParameterizedType,T是TypeVariable,T ? extends String是WildcardType(注意,WildcardType不是java类型,而是一个表达式),整个List<T ? extends String>[]就是GenericArrayType。

参考

https://cloud.tencent.com/developer/article/1497707
https://juejin.cn/post/6844903597977632776

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/100292.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!