Java注解

143 阅读20分钟

注解

      Java 注解(Annotation)又称Java 标注,是JDK5.0 引入的一种注释机制。 注解是元数据的一种形式,提供有关于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。

注解声明

声明一个注解类型

Java中所有的注解,默认实现 Annotation 接口:

package java.lang.annotation;
public interface Annotation {
    boolean equals(object obj)
    int hashcode();
    String tostring();
    Class<? extends Annotation> annotationType();
}

与声明一个"Class"不同的是,注解的声明使用 @interface 关键字。一个注解的声明如下:

public @interface MyAnnotation{
}

元注解

      在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta.annotation(元注解)。一般的,我们在定义自定义注解时,需要指定的元注解有两个: 另外还有@Documented与@Inherited 元注解,前者用于被javadoc工具提取成文档,后者表示允许子类继承父类中定义的注解。

@Target

      注解标记另一个注解,以限制可以应用注解的Java 元素类型。目标注解指定以下元素类型之一作为其值:

  1. ElementType.ANNOTATION TYPE 可以应用于注解类型
  2. ElementType.CONSTRUCTOR 可以应用于构造数。
  3. ElementType.FIELD 可以应用于字段或属性。
  4. ElementType.LOCAL VARIABLE 可以应用于局部变量
  5. ElementType.METHOD 可以应用于方法级注解。
  6. ElementType.PACKAGE 可以应用于包声明。
  7. ElementType.PARMMETER 可以应用于方法的参数
  8. Elementrype.TYPE 可以应用于类的任何元素。

@Retention

注解指定标记注解的存储方式:

  1. RetentionPolicy.SOURCE-标记的注解仅保留在源级别中,并被编译器忽略。
  2. RetentionPolicy.cLAss-标记的注解在编译时由编译器保留,但java虛拟机(JM)会忽略。
  3. RetentionPolicy.RUNTIME-标记的注解由 JM 保留,因此运行时环境可以使用它。

RUNTIME包含SOURCE@Retention 三个值中 SOURCE< CLASS <RUNTIME, 即CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS,下文会介绍他们不同的应用场景。

请看下边这个例子:

//@Target(ElementType.TYPE) 只能在类上标记该注解
@Target({ElementType.TYPE,ElementType.FIELD})// 允许在类与类属性上标记该注解
@Retention(RetentionPolicy.SOURCE)//注解保留在源码中
public @interface MyAnnotation{}

注解类型元素

在上文元注解中,允许在使用注解时传递参数。我们也能让自定义注解的主体包含 annotation type element(注解类型元素) 声明,它们看起来很像方法,可以定义可选的默认值。

@Target({ElementType.TPE,ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Lance {
    String value(); //无默认值
    int age()default 1;//有默认值
}

注意:在使用注解时,如果定义的注解中的类型元素无默认值,则必须进行传值。

@MyAnnotation("帅")//如果只存在value元素需要传值的情况,则可以省略: 元素名:@MyAnnotation(value:"师", age=2)int i

注解应用场景

按照@Retention 元注解定义的注解存储方式,注解可以被在三种场景下使用:

SOURCE

RetentionPolicy.SOURCE,作用于源码级别的注解,可提供给IDE语法检查(IDE实现,或者IDE插件实现)、APT等场景使用,不会影响编译。比如安卓中常用的@DrawableRes、@IntDef等。

IDE语法检查

在Android开发中, support·annotations 与 androidx.annotation)中均有提供 @IntDef 注解,此注解的定义如下:

@Retention(SOURCE) //源码级别注解
@Target({ANNOTATION TYPE})
public @interface IntDef {
    int[] value()default {};
    boolean flag()default false;
    boolean open()default false;
}

Java中Enum(枚举)的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中。 比常量多5到10倍的内存占用。这主要由枚举的内部实现和使用方式决定。枚举在Java中是一种特殊的类类型,它提供了比静态常量更多的功能和灵活性,但这些优点也带来了额外的内存开销。下面是一些导致枚举占用更多内存的原因:

1. 类型安全

枚举提供了类型安全的特性,每个枚举都是java.lang.Enum类的子类。这意味着枚举值可以携带与之相关的状态和行为,就像其他Java对象一样。而静态常量通常是基本数据类型(如intString),它们只占用基本类型所需的固定内存大小。

2. 内部结构

每个枚举值实际上是枚举类的一个实例,这意味着它们不仅存储常量值,还可能存储其他与实例相关的字段。因此,如果枚举类定义了额外的属性和方法,这些都会增加每个枚举实例的内存占用。

3. 元数据

作为一种特殊的类类型,枚举在运行时也会携带一些额外的元数据,比如类信息、方法信息等。这些元数据有助于反射操作和类型安全,但也增加了内存使用。

4. 单例模式

枚举值通常以单例模式存在。Java虚拟机(JVM)会为每个枚举值维护一个唯一的实例,这与静态常量(尤其是基本类型的常量)相比,可能会导致更多的内存使用。 此注解的意义在于能够取代枚举,实现如方法入参限制。

如:我们定义方法 test ,此方法接收参数 teacher 需要在:A、B中选择一个。如果使用枚举能够实现为:

public enum Teacher{
    A,B
}
public void test(Teacher teacher){}

而现在为了进行内存优化,我们现在不再使用枚举,则方法定义为

public static final int A = 1;
public static final int B = 2;
public void test(int teacher){
}

然而此时,调用 test 方法由于采用基本数据类型int,将无进行类型限定,@IntDef增加自定义注解:

public static final int A = 1;
public static final int B = 2;
@IntDeF(value =(A, B})//限定为LN
@Target(ElenentTyPe.PARAMETER)//作用于数
RetentioniRetentionPolicv.SOURCE)//源码级别的注释
public @interface Teacher{}
public void test(@Teacher int teacher) {}

此时,我们再去调用 test 方法,如果传递的参数不是 A 或者 B 则会显示Inspection 警告(编译不会报错). 以上注解均为 SOURCE 级别,本身IDEA/AS 就是由lava开发的,工具实现了对lava语法的检查,借助注解能对被注解的特定语法进行额外检查。

APT注解处理器

APT全称为:"Anotation Processor Tools",意为注解处理器。顾名思义,其用于处理注解。编写好的]ava源文件,需要经过 javac 的编译,翻译为虛拟机能够加载解析的字节码Class文件。注解处理器是javac 自带的一个工具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac调起,并将注解信息传递给注解处理器进行处理。

  • 注解处理器是对注解应用最为广泛的场景。在Glide、EventBus3、Buterknifer、Tinker、ARouter等等常用框架中都有注解处理器的身影。但是你可能会发现,这些框架中对注解的定义并不是 SOURCE 级别,更多的是 CLASS 级别,别忘了:CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS.

CLASS

定义为 CLAS5 的注解,会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。此时完全符合此种注解的应用场景为字节码操作。如:Aspect)、热修复Roubust中应用此场景。所谓字节码操作即为,直接修改字节码Class文件以达到修改代码执行逻辑的目的。在程序中有多处需要进行是否登录的判断。

image.png

如果我们使用普通的编程方式,需要在代码中进行 if-e1se 的判断,也许存在十个判断点,则需要在每个判断点加入此项判断。此时,我们可以借助AOP(面向切面)编程思想,将程序中所有功能点划分为: 需要登录 与 无需登录两种类型,即两个切面。对于切面的区分即可采用注解。

//Java源码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Login {
@Login
public void jumpA(){
    startActivity(new Intent(this,AActivity.class));
}
public void jumpB(){
    startActivity(new Intent(this,BActivity.class));
}

在上诉代码中, jumpÀ 方法需要具备登录身份。而 Login 注解的定义被设置为 CLSS 。因此我们能够在该类所编译的字节码中获得到方法注解 Login 。在操作字节码时,就能够根据方法是否具备该注解来修改class中该方法的内容加入 if-else 的代码段:

@Login
public void jumpA(){
if(this.isLogin){
    this.startActivity(new Intent(this, LoginActivity.class));}else{
    this.startActivity(new Intent(this, AActivity.class));
}
public void jumpB(){
    startActivity(new Intent(this,BActivity.class));
}

注解能够设置类型元素(参数),结合参数能实现更为丰富的场景,如:运行期权限判定等.

RUNTIME

注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。

反射

一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的,并且能够获得此类的引用。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。 反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用JDK 提供的反射 API进行反射调用。反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。是Java被视为动态语言的关键。

Java反射机制主要提供了以下功能:

  • 在运行时构造任意一个类的对象
  • 在运行时获取或者修改任意一个类所具有的成员变量
  • 在运行时调用任意一个对象的方法(属性)

Class

反射始于 ClassClass是一个类,封装了当前对象所对应的类的信息。 一个类中有属性,方法,构造器等,比如说有一个Person类,一个Order类,一个Book类,这些都是不同的类,现在需要一个类,用来描述类,这就是Class,它应该有类名,属性,方法,构造器等。Class是用来描述类的类。Class类是一个对象照镜子的结果,对象可以看到自己有哪些属性,方法,构造器,实现了哪些接口等等。对于每个类而言,IRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。对象只能由系统建立对象,一个类(而不是一个对象)在JM 中只会有一个Class实例。

获得 Class 对象

获取Class对象的三种方式

  1. 通过类名获取 类名.class
  2. 通过对象获取 对象名·getclass()
  3. 通过全类名获取class.forName(全类名) classLoader.loadClass(全类名)
  • 使用 Class 类的 forName 静态方法
public static class<?>forName(String className)
  • 直接获取某一个对象的 class
Class<?>klass= int.class;
Class<?>classInt = Integer.TYPE;
  • 调用某个对象的 getclass()方法
StringBuilder str=new stringBuilder("123");
Class<?>klass=str.getclass();

判断是否为某个类的实例

一般地,我们用 instanceof 关键字来判断是否为某个类的实例。同时我们也可以借助反射中 Class 对象的isInstance()方法来判断是否为某个类的实例,它是一个 native 方法:

public native boolean isInstance(object obj);

判断是否为某个类的类型

public boolean isAssignableFrom(class<?> cls)

通过反射来生成对象主要有两种方式。

  • 使用Class对象的newInstance()方法来创建Class对象对应类的实例,
Class<?>c=String.class;
Object str=c.newInstance();
  • 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance0方法来创建实例。这种方法可以用指定的构造器构造类的实例。
//获取string所对应的class对象
Class<?>c=String.class;
//获取string类带-个string参数的构造器
Constructor constructor=c.getConstructor(String.class);
//根据构造器创建实例
Object obj= constructor.newInstance("23333");
System.out.println(obj);

获取构造器信息

Constructor getconstructor(class[lparams)--获得使用特殊的参数类型的public构造函数(包括父类)
Constructor[l getconstructors()获得类的所有公共构造函数
getDeclaredconstructor(class[] params)-- 获得使用特定参数类型的构造函数(包括私有)
ConstructorConstructor[] getDeclaredconstructors()-- 获得类的所有构造函数(与接入级别无关)

获取类构造器的用法与上述获取方法的用法类似,主要是通过Class类的getConstrudor方法得到Constructor类的一个实例,而Constructor类有一个newinstance方法可以创建一个对象实例:

publicTnewnstance(object ...initargs)

获取类的成员变量(字段)信息

获得字段信息的方法:

Field getField(string name)--获得命名的公共字段
Field[] getFields()-- 获得类的所有公共字段
Field getDeclaredField(string name)-- 获得类声明的命名的字段
Field[]getDeclaredFields()-- 获得类声明的所有字段

调用方法

获得方法信息的方法:

Method getMethod(string name, class[] params)
Method[]getMethods()-- 获得类的所有公共方法
Method getDeclaredMethod(string name,class[]params)-- 使用特写的参数类型,获得类声明的命名的方法
Methodl] getDeclaredMethods()--获得类声明的所有方法

当我们从类中获取了一个方法后,我们就可以用invoke()方法来调用这个方法。 invoke 方法的原型为:

public object invoke(object obj,bject... args)

利用反射创建数组

数组在|ava里是比较特殊的一种类型,它可以赋值给一个0bject Reference 其中的Array类为java.lang.reflect.Array类。我们通过Array.newInstance()创建数组对象,它的原型是:

public static object newInstance(class<?>componentType, int length);

反射获取泛型真实类型

QQ图片20240318104318.png 当我们对一个泛型类进行反射时,需要的到泛型中的真实数据类型,来完成如ison反序列化的操作。此时需要通过 Type 体系来完成。 Type 接口包含了一个实现类(Class)和四个实现接口,他们分别是:

  • TypeVariable泛型类型变量。可以泛型上下限等信息;
  • ParameterizedTypeo 具体的泛型类型,可以获得元数据中泛型签名类型(泛型真实类型)
  • GenericArrayType 当需要描述的类型是泛型类的数组时,比如List0,Map[],此接口会作为Type的实现.。
  • WildcardType 通配符泛型,获得上下限信息;

TypeVariable

public class TestType <K extends Comparable & Serializable,V> {
    K key;
    V value;
    public static void main(string[] args)throws Exception {
            // 获取字段的类型
            Field fk= TestType.class.getDeclaredField("key")
            Field fv=TestType.class.getDeclaredField("value")
            TypeVariable keyType=(TypeVariable)fk.getGenenicType();
            TypeVariable valueType =(TypeVariable)fv.getGenericType()
            // getName 方法
            System.out.println(keyType.getName());
            System.out .printin(valueType.getName())
            // getGenericDeclaration 方法
            System.out.printin(keyType,getGenericDeclaration());// class com.test.TestType
            System.out.printin(valueType,getGenericDeclaration());// class com.test.TestType
            // getBounds 方法
            System.out.printin("K 的上界:”);// 有两个
            for(Type type :keyType.getBounds()){//interface java.lang.Comparable
               System.out.println(type); //interface java.io.Serializable
            }
            System.out.printin("v 的上界:")
            for(Type type :valueType.getBounds()){// 没明确声明上界的,默认上界是 object
                System.out.println(type);
            }
       }
}

ParameterizedType

/**
 * ParameterizedType
 * 具体的泛型类型, 如Map<String, String>
 * 有如下方法:
 * <p>
 * Type getRawType(): 返回承载该泛型信息的对象, 如上面那个Map<String, String>承载范型信息的对象是Map
 * Type[] getActualTypeArguments(): 返回实际泛型类型列表, 如上面那个Map<String, String>实际范型列表中有两个元素, 都是String
 */
public class TestType {
    Map<String, String> map;

    public static void main(String[] args) throws Exception {
        Field f = TestType.class.getDeclaredField("map");
        System.out.println(f.getGenericType());                               // java.util.Map<java.lang.String, java.lang.String>
        ParameterizedType pType = (ParameterizedType) f.getGenericType();
        System.out.println(pType.getRawType());                               // interface java.util.Map
        for (Type type : pType.getActualTypeArguments()) {
            System.out.println(type);                                         // 打印两遍: class java.lang.String
        }
    }
}

GenericArrayType

/**
 * GenericArrayType
 * 泛型数组,组成数组的元素中有范型则实现了该接口; 它的组成元素是ParameterizedType或TypeVariable类型,它只有一个方法:
 * <p>
 * Type getGenericComponentType(): 返回数组的组成对象
 *
 * @param <T>
 */
public class TestType<T> {

    List<String>[] lists;

    public static void main(String[] args) throws Exception {
        Field f = TestType.class.getDeclaredField("lists");
        GenericArrayType genericType = (GenericArrayType) f.getGenericType();
        System.out.println(genericType.getGenericComponentType());
    }
}

WildcardType

/**
 * WildcardType
 * 该接口表示通配符泛型, 比如? extends Number 和 ? super Integer 它有如下方法:
 *
 * Type[] getUpperBounds(): 获取范型变量的上界
 * Type[] getLowerBounds(): 获取范型变量的下界
 * 注意:
 *
 * 现阶段通配符只接受一个上边界或下边界, 返回数组是为了以后的扩展, 实际上现在返回的数组的大小是1
 */
public class TestType {
    private List<? extends Number> a;  // 上限
    private List<? super String> b;     //下限

    public static void main(String[] args) throws Exception {
        Field fieldA = TestType.class.getDeclaredField("a");
        Field fieldB = TestType.class.getDeclaredField("b");
        // 先拿到范型类型
        ParameterizedType pTypeA = (ParameterizedType) fieldA.getGenericType();
        ParameterizedType pTypeB = (ParameterizedType) fieldB.getGenericType();
        // 再从范型里拿到通配符类型
        WildcardType wTypeA = (WildcardType) pTypeA.getActualTypeArguments()[0];
        WildcardType wTypeB = (WildcardType) pTypeB.getActualTypeArguments()[0];
        // 方法测试
        System.out.println(wTypeA.getUpperBounds()[0]);   // class java.lang.Number
        System.out.println(wTypeB.getLowerBounds()[0]);   // class java.lang.String
        // 看看通配符类型到底是什么, 打印结果为: ? extends java.lang.Number
        System.out.println(wTypeA);
    }
}

Gson反序列化

public class Deserialize {

    static class Response<T> {
        T data;
        int code;
        String message;

        @Override
        public String toString() {
            return "Response{" +
                    "data=" + data +
                    ", code=" + code +
                    ", message='" + message + ''' +
                    '}';
        }

        public Response(T data, int code, String message) {

            this.data = data;
            this.code = code;
            this.message = message;
        }
    }

    static class Data {
        String result;

        public Data(String result) {
            this.result = result;
        }

        @Override
        public String toString() {
            return "Data{" +
                    "result=" + result +
                    '}';
        }
    }

    static class ChildTypeRefrence{
        Response<Data>  t;
    }

    public static void main(String[] args) {
        Response<Data> dataResponse = new Response(new Data("数据"), 1, "成功");

        Gson gson = new Gson();
        String json = gson.toJson(dataResponse);
        System.out.println(json);

        //反序列化......

        /**
         *  有花括号: 代表是匿名内部类,创建一个匿名内部类的实例对象,在class文件中是一个具体的类。相当于创建了一个子类ChildTypeRefrence,把给具象化了成为了Response<Data>  t;
         *  没花括号:创建实例对象,在class 文件中是一个object。
         */
        //  signature Lcom/enjoy/reflect/type/gson/Deserialize$Response<Lcom/enjoy/reflect/type/gson/Deserialize$Data;>;
        //  declaration: dataResponse extends com.enjoy.reflect.type.gson.Deserialize$Response<com.enjoy.reflect.type.gson.Deserialize$Data>
//        Type type = new TypeToken<Response<Data>>(){}.getType();
        Type type = new TypeRefrence<Response<Data>>(){}.getType();

        System.out.println(type);
        Response<Data> response = gson.fromJson(json, type);
        System.out.println(response.data.getClass());
    }
}

public class TypeRefrence<T> {
    Type type;
    T t;

    protected TypeRefrence()> {
        //获得泛型类型
        Type genericSuperclass = getClass().getGenericSuperclass();
        ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
        //因为类泛型可以定义多个  A<T,E..> 所以是个数组
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        type = actualTypeArguments[0];
    }

    public Type getType() {
        return type;
    }
}

在进行GSON反序列化时,存在泛型时,可以借助 TypeToken 获取Type以完成泛型的反序列化。但是为什么TypeToken 要被定义为抽象类呢? 因为只有定义为抽象类或者接口,这样在使用时,需要创建对应的实现类,此时确定泛型类型,编译才能够将泛型signature信息记录到Class元数据中。

自定义注解的使时机,使在.java 文件转化成.class文件中的时候。

测试源码链接:github.com/xingchaozha…

学后测验

一、单选题(每题3分,共18分)

  1. Java中用于声明注解的关键字是( )。

    • A. annotation
    • B. interface
    • C. @annotation
    • D. @interface

    解析:Java中定义注解类型时,使用@interface而不是interface或其他关键字

  2. 以下注解类型中,哪个类型指定的注解在运行期不会被JVM保留?( )

    • A. RetentionPolicy.RUNTIME
    • B. RetentionPolicy.CLASS
    • C. RetentionPolicy.SOURCE
    • D. 以上都保留在运行期

    解析RetentionPolicy.CLASS注解保存在.class文件中,但在运行期会被JVM忽略,无法通过反射获取。SOURCE在编译后被丢弃,RUNTIME在运行期仍被保留。

  3. 要限制一个注解只能用于方法的参数上,应使用哪种注解?( )

    • A. @Target(ElementType.PARAMETER)
    • B. @Retention(RetentionPolicy.SOURCE)
    • C. @Documented
    • D. @Inherited

    解析@Target注解用于指定自定义注解可用在哪些Java元素上。ElementType.PARAMETER表示只能用于方法参数上。

  4. 获得某个类中指定方法的实例,应该调用哪一个反射方法?( )

    • A. getDeclaredConstructor()
    • B. getDeclaredMethod()
    • C. getDeclaredField()
    • D. getConstructors()

    解析:反射获取指定方法对象,应使用getDeclaredMethod()方法,其它选项分别获取构造器、字段等,均不合适。

  5. 通过反射创建数组实例,应使用哪个类的方法?( )

    • A. Class
    • B. Array
    • C. Constructor
    • D. Method

    解析:Java通过反射创建数组需要使用Array.newInstance()方法,而非ClassConstructorMethod

  6. 在进行泛型反序列化时,Gson中常用的获取类型的方法为( )。

    • A. new TypeToken<Response<Data>>(){}.getType();
    • B. TypeVariable.getBounds()
    • C. WildcardType.getUpperBounds()
    • D. Class.getGenericSuperclass()

    解析:Gson中泛型反序列化一般使用TypeToken,通过子类匿名内部类的方式,利用字节码保留的泛型签名信息获取真实泛型类型。


二、多选题(每题4分,共16分)

  1. 关于Java注解的描述,正确的是?( )

    • A. 注解是一种元数据
    • B. 注解会直接改变代码逻辑
    • C. 注解对被注解代码的直接操作没有影响
    • D. 注解的保留策略包括SOURCE、CLASS、RUNTIME
  2. 在Java反射中,Class对象获取方式包括?( )

    • A. 类名.class
    • B. 对象名.getClass()
    • C. Class.forName(全类名)
    • D. 类加载器调用classLoader.loadClass(全类名)
  3. 下列哪些属于ElementType定义的有效枚举类型?( )

    • A. METHOD
    • B. FIELD
    • C. RETENTION
    • D. PARAMETER

    解析:C中的RETENTION属于RetentionPolicy,与ElementType无关。

  4. 注解类型元素(Annotation Type Element)声明时,以下正确的是?( )

    • A. 可以定义默认值
    • B. 一定要定义默认值
    • C. 不定义默认值时使用注解必须传入值
    • D. 定义默认值时使用注解可不传入值

三、判断题(每题3分,共18分)

  • (√)Java枚举类型相比常量,占用更多内存。

    • 解析:枚举类型是Java特殊类,每个枚举值为一个实例对象,相比基本类型常量占用更多内存空间。
  • (×)注解只能用于编译时,不能用于运行时。

    • 解析:Java注解可以有三种生命周期,包含编译时(SOURCE)、类加载时(CLASS)以及运行时(RUNTIME),故原题说法错误。
  • (√)Retention策略中,RUNTIME级别的注解可以在运行时通过反射获取。

    • 解析:RUNTIME策略注解在运行期保留,可以通过反射API获取相关信息。
  • (×)通过反射创建对象只能使用newInstance()方法。

    • 解析:反射创建对象除了Class.newInstance()(已废弃)之外,还可通过Constructor.newInstance()创建对象实例。
  • (√)ParameterizedType能获取泛型真实类型的信息。

    • 解析ParameterizedType提供方法(如getActualTypeArguments())获取泛型类型的真实信息。
  • (√)Class对象在Java虚拟机中每个类只有一个实例。

    • 解析:Class对象描述类的信息,JVM对每个类只维护一个Class对象实例,确保其唯一性。

四、简答题(共48分)

1. (8分)简述Java注解(Annotation)的主要用途?

参考答案:
Java注解主要用于提供程序的元数据。具体来说,它可以用于:

  • 编译期的检查(如@IntDef进行参数类型检查)
  • 生成文档(如@Documented)
  • 字节码修改(如使用RetentionPolicy.CLASS的注解在AOP框架中实现逻辑注入)
  • 运行期反射处理(如使用RetentionPolicy.RUNTIME在运行期获取注解信息)

2. (10分)以下代码存在什么问题,并给出正确写法:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnno {
    String value();
    int age();
}

@MyAnno("张三")
public void test() {}

参考答案:
该代码存在的问题是:

  • 定义注解MyAnno时,age()未指定默认值,因此使用时必须提供age的值。

正确写法:

@MyAnno(value = "张三", age = 25)
public void test() {}

或为age()指定默认值:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnno {
    String value();
    int age() default 0;
}

3. (10分)如何通过反射调用String类中带有一个String参数的构造器创建对象?写出代码示例。

参考答案:

Class<?> clazz = String.class;
Constructor<?> constructor = clazz.getConstructor(String.class);
Object obj = constructor.newInstance("Hello");
System.out.println(obj); // 输出Hello

4. (10分)解释为什么需要使用泛型反序列化时,通常需要通过子类匿名内部类方式获取真实泛型类型?

参考答案:
Java泛型在编译期进行类型擦除,因此运行期无法直接获取泛型的真实类型。
但当使用子类匿名内部类的方式时(如new TypeToken<T>(){}),Java会在子类字节码的Signature字段保留泛型类型信息,这样就能够在运行时通过反射获取到真实泛型类型。


5. (10分)以下代码反射的输出结果是什么?

public class Demo {
    List<String>[] array;
    public static void main(String[] args) throws Exception {
        Field field = Demo.class.getDeclaredField("array");
        GenericArrayType type = (GenericArrayType) field.getGenericType();
        System.out.println(type.getGenericComponentType());
    }
}

参考答案:
输出结果为:

java.util.List<java.lang.String>

说明:
GenericArrayType表示泛型数组,这里获得的是数组元素的泛型类型,即List<String>


6. (10分)下述代码的输出是什么?

public class TestReflect {
    private List<? super Integer> nums;
    public static void main(String[] args) throws Exception {
        Field field = TestReflect.class.getDeclaredField("nums");
        ParameterizedType pType = (ParameterizedType) field.getGenericType();
        WildcardType wType = (WildcardType) pType.getActualTypeArguments()[0];
        System.out.println(wType.getLowerBounds()[0]);
        System.out.println(wType.getUpperBounds()[0]);
    }
}

参考答案:

class java.lang.Integer
class java.lang.Object

说明:

  • ? super Integer表示类型的下界为Integer,上界默认是Object