注解
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 元素类型。目标注解指定以下元素类型之一作为其值:
- ElementType.ANNOTATION TYPE 可以应用于注解类型
- ElementType.CONSTRUCTOR 可以应用于构造数。
- ElementType.FIELD 可以应用于字段或属性。
- ElementType.LOCAL VARIABLE 可以应用于局部变量
- ElementType.METHOD 可以应用于方法级注解。
- ElementType.PACKAGE 可以应用于包声明。
- ElementType.PARMMETER 可以应用于方法的参数
- Elementrype.TYPE 可以应用于类的任何元素。
@Retention
注解指定标记注解的存储方式:
- RetentionPolicy.SOURCE-标记的注解仅保留在源级别中,并被编译器忽略。
- RetentionPolicy.cLAss-标记的注解在编译时由编译器保留,但java虛拟机(JM)会忽略。
- 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对象一样。而静态常量通常是基本数据类型(如int或String),它们只占用基本类型所需的固定内存大小。
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文件以达到修改代码执行逻辑的目的。在程序中有多处需要进行是否登录的判断。
如果我们使用普通的编程方式,需要在代码中进行 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
反射始于 Class,Class是一个类,封装了当前对象所对应的类的信息。
一个类中有属性,方法,构造器等,比如说有一个Person类,一个Order类,一个Book类,这些都是不同的类,现在需要一个类,用来描述类,这就是Class,它应该有类名,属性,方法,构造器等。Class是用来描述类的类。Class类是一个对象照镜子的结果,对象可以看到自己有哪些属性,方法,构造器,实现了哪些接口等等。对于每个类而言,IRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。对象只能由系统建立对象,一个类(而不是一个对象)在JM 中只会有一个Class实例。
获得 Class 对象
获取Class对象的三种方式
- 通过类名获取 类名.class
- 通过对象获取 对象名·getclass()
- 通过全类名获取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);
反射获取泛型真实类型
当我们对一个泛型类进行反射时,需要的到泛型中的真实数据类型,来完成如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分)
-
Java中用于声明注解的关键字是( )。
- A. annotation
- B. interface
- C. @annotation
- D. @interface
解析:Java中定义注解类型时,使用
@interface而不是interface或其他关键字 -
以下注解类型中,哪个类型指定的注解在运行期不会被JVM保留?( )
- A.
RetentionPolicy.RUNTIME - B.
RetentionPolicy.CLASS - C.
RetentionPolicy.SOURCE - D. 以上都保留在运行期
解析:
RetentionPolicy.CLASS注解保存在.class文件中,但在运行期会被JVM忽略,无法通过反射获取。SOURCE在编译后被丢弃,RUNTIME在运行期仍被保留。 - A.
-
要限制一个注解只能用于方法的参数上,应使用哪种注解?( )
- A.
@Target(ElementType.PARAMETER) - B.
@Retention(RetentionPolicy.SOURCE) - C.
@Documented - D.
@Inherited
解析:
@Target注解用于指定自定义注解可用在哪些Java元素上。ElementType.PARAMETER表示只能用于方法参数上。 - A.
-
获得某个类中指定方法的实例,应该调用哪一个反射方法?( )
- A.
getDeclaredConstructor() - B.
getDeclaredMethod() - C.
getDeclaredField() - D.
getConstructors()
解析:反射获取指定方法对象,应使用
getDeclaredMethod()方法,其它选项分别获取构造器、字段等,均不合适。 - A.
-
通过反射创建数组实例,应使用哪个类的方法?( )
- A.
Class - B.
Array - C.
Constructor - D.
Method
解析:Java通过反射创建数组需要使用
Array.newInstance()方法,而非Class、Constructor或Method。 - A.
-
在进行泛型反序列化时,Gson中常用的获取类型的方法为( )。
- A.
new TypeToken<Response<Data>>(){}.getType(); - B.
TypeVariable.getBounds() - C.
WildcardType.getUpperBounds() - D.
Class.getGenericSuperclass()
解析:Gson中泛型反序列化一般使用
TypeToken,通过子类匿名内部类的方式,利用字节码保留的泛型签名信息获取真实泛型类型。 - A.
二、多选题(每题4分,共16分)
-
关于Java注解的描述,正确的是?( )
- A. 注解是一种元数据
- B. 注解会直接改变代码逻辑
- C. 注解对被注解代码的直接操作没有影响
- D. 注解的保留策略包括SOURCE、CLASS、RUNTIME
-
在Java反射中,Class对象获取方式包括?( )
- A.
类名.class - B. 对象名
.getClass() - C.
Class.forName(全类名) - D. 类加载器调用
classLoader.loadClass(全类名)
- A.
-
下列哪些属于
ElementType定义的有效枚举类型?( )- A. METHOD
- B. FIELD
- C. RETENTION
- D. PARAMETER
解析:C中的RETENTION属于RetentionPolicy,与ElementType无关。
-
注解类型元素(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。