0%

Java注解以及反射

Java Anotation || Java Reflection 使用方法

Java Annotation

Annotation是从JDK5.0开始引入的新技术

Annotation的作用:

            不是程序本身,可以对程序做出解释
                                
            **可以被其他程序(如编译器等)读取**

Annotation的使用:

            附加在包,类,方法,属性等上,添加额外的辅助信息
                                
            我们可以通过反射机制编程实现对这些元数据的访问

内置注解

  • @Override
  • @Deprecated
  • @SuppressWarning

eg 方法重写注解

1
2
3
4
5
6
// target 作用在METHOD上
@Target(ElementType.METHOD)
// Retention 只在SOURCE级别起作用
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

元注解

元注解的作用是负责解释其他注解

Java定义了四个标注的mete-annotation类型,他们被用来提供对其他annotation类型作说明,这些类型和他们支持的类可以在Java.lang.annotation包中找到。

  • @Target:用于描述注解的使用范围

    支持的类型有

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
    * Type parameter declaration
    *
    * @since 1.8
    */
    TYPE_PARAMETER,

    /**
    * Use of a type
    *
    * @since 1.8
    */
    TYPE_USE
    }
  • @Retention:表示需要在什么级别保存该注解信息

    SOURCE < CLASS < RUNTIME

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public enum RetentionPolicy {
    /**
    * Annotations are to be discarded by the compiler.
    */
    SOURCE,

    /**
    * Annotations are to be recorded in the class file by the compiler
    * but need not be retained by the VM at run time. This is the default
    * behavior.
    */
    CLASS,

    /**
    * Annotations are to be recorded in the class file by the compiler and
    * retained by the VM at run time, so they may be read reflectively.
    *
    * @see java.lang.reflect.AnnotatedElement
    */
    RUNTIME
    }
  • @Document:说明该注解将被包含在JavaDoc中

  • @Inherited:说明子类可以继承父类中的该注解

自定义注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口

注意

  • 使用@interface来声明注解
  • 每一个方法实际上都是声明了一个配置参数
  • 方法名称就是参数的名称 —》 参数类型 参数名 ()
  • 返回值类型就是参数的类型(返回值只能是基本类型,Class,String,Enum)
  • 可以通过default来声明参数的默认值,一般使用空字符串,0作为默认值
  • 如果只有一个参数成员,一般参数名为value,在使用注解时可以省略注解属性名字
  • 使用注解元素必须要有值,可以通过default值省略传值

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 自定义注解
* @author yancy0109
*/
public class MyAnnotation{

@MyAnnotation1(ages = 1)
public void test(){

}

@MyAnnotation2("")
@MyAnnotation3
public void test1(){

}
}
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation1 {

// 注解参数 : 参数类型 参数名 ()
String name() default "";
int ages();

}

@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
String value();
}
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation3{
}

注解信息获取

通过反射获取注解信息!

Java Reflection

通过反射使Java动态化。

反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,可以直接操作任意对象的内部属性及方法

Class对象

加载完类之后,在堆内存的方法区中会产生一个Class类型的对象(一个类只有一个Class对象),该对象包含了完整的类的结构信息。

正常: 引入包类名称 –> 通过new实例化 –> 取得实例化对象

反射: 实例化对象 –> getClass() –> 得到完整的包类名称

反射特点

优点:动态创建对象和编译,更为灵活

缺点:是一种解释操作,影响性能。

基本实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package org.example.learning;

/**
* @author yancy0109
*/
public class Heelo {

private void test(String name) {
System.out.println(name);
}


public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
// 获取Class对象
Class<Heelo> hello = Heelo.class;
Class<?> helloClass = Class.forName("org.example.learning.Heelo");
// 通过反射创建对象
Heelo helloObect = hello.newInstance();

// 全部相同 --> 一个类只在JVM存在一个Class对象
System.out.println(hello.hashCode());
System.out.println(helloClass.hashCode());
System.out.println(helloObect.getClass().hashCode());
}
}

Class对象方法

Field,Method,Constructor,SuperClass,Interface,Annotation……

  • 实现的全部接口Interface
  • 所继承的父类SuperClass
  • 全部的方法Method
  • 全部的Field
  • 注解Annotation
  • …….

image-20230104235825056

等等等…. 可见有很多方法了,例如

newInstance() 生成对象

getAnnotation() 获取注解信息

补充

  • 所有类型都有Class对象?..

int.class表示基本数据类型int的Class对象

TYPE是Integer中的静态常量,表示的是基本数据类型int的class实例

Integer.class表示引用数据类型Integer的class对象

注意:由.class来创建Class对象的引用时,不会自动初始化Class对象.

1
2
3
4
public static void main(String[] args) {
System.out.println(int.class==Integer.TYPE);//结果为true
System.out.println(int.class==Integer.class);//结果为false
}

类加载内存分析

Java内存

    • 存放new的对象以及数组
    • 可以被所有的线程共享,不会存放别的对象引用
    • 存放基本变量类型(会包含基本类型的具体数值)
    • 引用对象的变量(会存放这个引用在堆里的具体地址)
  • 方法区
    • 可以被所有的线程共享
    • 包含了所有的class和static变量

加载过程

  • 加载(加载class字节码内容至内存中,在方法区保存)
  • 链接(合并至JVM允许状态之中,从方法区中将数据加载为对于Class对象,保存在堆中)
    • 验证(确保类规范)
    • 准备(在方法区中为所有类变量分配内存并设置默认初始值)
    • 解析(将虚拟机常量池内符号引用(变量名)替换为直接引用(地址)的过程)
  • 初始化(Class对象创建初始化)(主动引用/被动引用——只有主动引用才会发生类的初始化)
    • 主动引用(发生类的初始化)
      • 虚拟机启动,先初始化main方法所在的类
      • new一个类的对象
      • 调用类的静态成员
      • 反射调用
      • 初始化类时,优先初始化父类
    • 被动引用(不会发生类的初始化)
      • 访问一个静态域,只有真正声明这个域的类才会初始化(子类调用父类静态变量,不会导致子类初始化)
      • 数组定义类引用,不会触发类的初始化
      • 引用final常量不会触发类的初始化(在链接阶段就存入了调用类的常量池中)

类加载器

类加载:将class文件加载至内存,并生成一个代表类的Class对象作为方法区中类数据的访问入口
类缓存:类被加载后,将缓存一段时间,JVM垃圾回收机制可以回收Class对象

分类

  • 引导类加载器:负责Java平台核心库
  • 扩展类加载器:负责jre/lib/ext目录下jar包
  • 系统类加载器:负责加载项目下的类与jar包
  • 自定义加载器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
// 获取系统类的加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
// 系统类加载器
System.out.println(classLoader); //sun.misc.Launcher$AppClassLoader@1
// 扩展类加载器
System.out.println(classLoader.getParent()); //sun.misc.Launcher$ExtClassLoader
// 根加载器
System.out.println(classLoader.getParent().getParent()); //null 无法获取

System.out.println(Hello.class.getClassLoader()); //sun.misc.Launcher$AppClassLoader
System.out.println(Object.class.getClassLoader()); //null

// 获取系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path")); //各种Jar包位置,以及本项目class文件生成目录
}

Class对象方法

获取类的运行时结构

剩余不做更多演示

获得名字
获得属性
获得方法
获得构造器
获得注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static void main(String[] args) throws NoSuchFieldException {
Class<Person> personClass = Person.class;

// 获得名字
System.out.println(personClass.getName()); //org.example.learning.Person
System.out.println(personClass.getSimpleName()); //Person

// 获得属性
//[public java.lang.String org.example.learning.Person.publicName]
System.out.println(Arrays.toString(personClass.getFields()));
//public java.lang.String org.example.learning.Person.publicName
System.out.println(personClass.getField("publicName"));
//[public java.lang.String org.example.learning.Person.publicName, private java.lang.String org.example.learning.Person.privateName]
System.out.println(Arrays.toString(personClass.getDeclaredFields()));
//private java.lang.String org.example.learning.Person.privateName
System.out.println(personClass.getDeclaredField("privateName"));
//public java.lang.String org.example.learning.Person.publicName
System.out.println(personClass.getDeclaredField("publicName"));

// 获得方法
System.out.println(Arrays.toString(personClass.getMethods())); // 本类及其父类全部public方法
System.out.println(Arrays.toString(personClass.getDeclaredMethods())); // 本类所有方法,不包含构造

// 获得构造器
Constructor<Person> constructor = personClass.getConstructor();
Person person = constructor.newInstance(null);

// 获得注解
System.out.println(Arrays.toString(personClass.getAnnotations())); //[@org.example.learning.MyAnnotation(name=haha)]
}

动态创建对象执行方法

调用方法

见代码

操作属性

见代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class<Person> personClass = Person.class;
Person person = personClass.newInstance();
System.out.println(person); // Person{publicName='null', privateName='null'}
Constructor<Person> constructor = personClass.getConstructor(String.class);
Person person1 = constructor.newInstance("哇卡");
System.out.println(person1); // Person{publicName='哇卡', privateName='null'}

// 通过反射调用普通方法
Method privateMethods = personClass.getDeclaredMethod("privateMethod", String.class);
privateMethods.setAccessible(true); // 允许访问私有方法
privateMethods.invoke(person1, "哈哈");
System.out.println(person1); // Person{publicName='哇卡', privateName='哈哈'}

// 通过反射操作属性
Field privateName = personClass.getDeclaredField("privateName");
privateName.setAccessible(true); // 允许访问私有属性
privateName.set(person1, "私有属性");
System.out.println(person1); // Person{publicName='哇卡', privateName='私有属性'}
}
补充

invoke(Object, Object..args) 调用方法 目标对象, 参数

set(Object, Object… args) 修改属性 目标对象,参数

setAccessible(boolean)

  • Method,Field,Constructor对象都有此方法

  • 启动或禁用访问安全检查的开关

  • 允许访问可以提高反射效率,但也使得私有成员也可以访问

  • default false,反射对象应实施Java语言访问检查

性能分析

  • 普通方式调用
  • 反射方式调用
  • 反射方式调用(关闭安全检测)

普通 >>>> 关闭安全检测反射方式 > 反射方式

反射操作泛型

在编译完成后,所有和泛型有关的类型全部擦除

  • Tpye:所有类型的公共父类接口

  • ParameterizedType:表示一种参数化类型,例如Collection

  • GenericArrayType:表示一种元素类型时参数化类型或者类型变量的数组类型

  • TypeVariable:是各种类型变量的公共父类接口

    • 表示泛型类型参数或者又叫做类型变量。比如:void method(E e){}中的E就是该接口类型
  • WildcardType:代表一种通配符类型表达式

    • 比如?, ? extends Number, ? super Integer | wildcard是一个单词:就是“通配符”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void test1(List<String> stringList) {}
List<String> test2 () {return null;}
public static void main(String[] args) throws NoSuchMethodException {
Class<Test3> aClass = Test3.class;
// 方法一:参数为泛型
Method method = aClass.getDeclaredMethod("test1", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes(); // 获得泛型参数
System.out.println(Arrays.toString(genericParameterTypes)); // [java.util.List<java.lang.String>]
for (Type genericParameterType : genericParameterTypes) {
// 如果为参数化类型
if (genericParameterType instanceof ParameterizedType) {
// 获得真实参数信息
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
System.out.println(Arrays.toString(actualTypeArguments)); // [class java.lang.String]
}
}
// 方法二:返回值为泛型
method = aClass.getDeclaredMethod("test2");
Type genericReturnType = method.getGenericReturnType();
System.out.println(method.getReturnType()); // interface java.util.List
System.out.println(genericReturnType); // java.util.List<java.lang.String>
if (genericReturnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
System.out.println(Arrays.toString(actualTypeArguments)); // [class java.lang.String]
}
}

获取注解信息

区别

  • getAnnotations():返回此元素上存在的所有注解的数组,包括从父类继承的
  • getDeclaredAnnotations():返回直接存在于此元素上的所有注解的数组,不包括父类的注解

使用

通过反射获取注解中的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 通过反射获取注解中的信息
* @author yancy0109
*/
public class Test4 {
public static void main(String[] args) throws NoSuchFieldException {
Class<Student> studentClass = Student.class;
// 获得类注解
Annotation[] declaredAnnotations = studentClass.getDeclaredAnnotations();
System.out.println(Arrays.toString(declaredAnnotations)); // [@org.example.learning.TableAnnotation(tableName=student)]
// 获得类指定注解
TableAnnotation tableAnnotation = studentClass.getAnnotation(TableAnnotation.class);
System.out.println(tableAnnotation.tableName()); // student

// 获得属性注解
Field field = studentClass.getDeclaredField("id");
FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
System.out.println(fieldAnnotation.filedName()); //id
}
}

@Data
@TableAnnotation(tableName = "student")
class Student {
@FieldAnnotation(filedName = "id")
private int id;
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableAnnotation {
String tableName() default "tablename";
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldAnnotation {
String filedName() default "filedname";
}