Java注解与反射

Java注解

在 java 中以 @ 符号开头标在类上、函数上、属性等等上的就是 java 中的注解 ( Annotation ),他是在 JDK5 引进的技术,我们在初学 java 的时候就接触过注解,只是没有留意过罢了。

常见的注解

常见注解 作用描述
@Override 表示该方法是复写父类/接口的方法
@Deprecated 表示该方法为过期方法
@SuppressWarnings 忽略代码警告
@FunctionalInterface 表示当前接口是函数式接口,JDK8开始支持

代码举例

1
2
3
4
@FunctionalInterface // 表示当前接口是函数式接口 JDK8
public interface Demo1Interfase {
void func();
}
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
public class Demo1 implements Demo1Interfase {

@Override // 表示该方法是复写父类/接口的方法
public void func() {
System.out.println("复写Demo1Interfase接口的方法");
}

@Deprecated // 表示该方法为过期方法
public void print(){
System.out.println("我已经过期了");
}

@SuppressWarnings(value = "all") // 忽略全部警告
public void good(){
String hello ="hello"; // 定义变量却没有使用
System.out.println("good morning");
}

public static void main(String[] args) {
Demo1 demo1 = new Demo1();
demo1.print(); // 过期方法会有一道横线(IDEA)
demo1.func();
// 使用lambda表达式来创建函数式接口实例
Demo1Interfase d =()-> System.out.println("函数式接口");
d.func();
}

}

元注解介绍

如果说注解是为我们写的类、函数、属性提供的功能,那么 ==元注解就是为注解提供的功能==,他规定了注解的一些使用规则。

元注解名称 作用描述
@Target 限制注解可以在什么位置使用,例如类,函数,属性等
@Retention 限制注解保存级别,例如运行时,编译时或者源码时
@Document 如果使用该元注解,可以被 javadoc 此类的工具文档化
@Inherited 可以让子类继承父类中的注解
@Repeatable 表示当前注解可以在同一个类,接口,函数中多次使用,JDK8 开始支持
  • @target 的属性值是 ElementType 得枚举类型,用于约束作用范围:
    • TYPE: 可以作用在类,接口,注解,枚举类上
    • FIELD:可以作用在类的属性上
    • METHOD:可以作用在方法上
    • CONSTRUCTOR:可以作用在构造方法中
    • PARAMETER:可以作用在函数的参数列表中
    • LOCAL_VARIABLE:可以作用在方法内的局部变量中
    • ANNOTATION_TYPE:可以作用在注解上,TYPE中已经包含了
    • PACKAGE:可以作用在包上
    • TYPE_USE:能标注任何类型上,JDK8 开始支持
  • @Retention 可以限制注解的保存级别,值为 RetentionPolicy 的枚举类型:
    • SOURCE:可以保存在源代码中
    • CLASS:可以在编译时保存在 class 文件中
    • RUNTIME:在运行时保存,==一般情况下使用这个就好了==

自定义注解

好了,现在我们已经知道什么时注解了,并且知道了用于约束注解的元注解,接下来就来基于这些约束来创建自定义的注解

创建自定义注解

创建自定义注解其实很简单,首先我们先创建一个接口,然后在 interfase 关键字前面加上 @ 符号,这样一个注解就创建好了

1
2
@Retention(RetentionPolicy.RUNTIME) // 建议使用RUNTIME
public @interface MyAnno1 { }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@MyAnno1
public class Demo2 {

@MyAnno1 String user = "张";

@MyAnno1
public void func1(){
@MyAnno1 String name = "涵哲";
System.out.println(this.user + name +":func1 function run");
}

public static void main(String[] args) {
new Demo2().func1();
}
}

基于元注解加一些约束

可以看到,上面的注解已经创建成功了,接下来我们让注解只能在函数上生效

1
2
3
@Target(ElementType.METHOD) // 只在函数中生效
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno1 { }

注解参数

现在我们的注解需要接收一些参数,在注解中添加参数列表也很简单,就像接口中书写方法一样就可以了

1
2
3
4
5
6
7
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno1 {
String user();
String[] name();
String gender() default "男";
}

需要注意的是,在接口中定义参数列表后,使用该注解的时候必须将值填写完整,不然会报错,如果使用了 dufault 设置默认值的话可以不用传值,写法如下:

1
2
@MyAnno1(user = "张", name = {"涵哲", "哈哈"})
public void func1(){ ... }

如果数组 name 只需要一个参数的话,那么花括号也可以省略不写

1
2
@MyAnno1(user = "张", name = "涵哲")
public void func1(){ ... }

如果注解中只有一个参数的话,建议变量名命名为 value,这样的话在传参的时候可以省略不写,代码如下:

1
2
3
4
5
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno2 {
String value();
}
1
2
@MyAnno2("哈哈哈哈")
public class Demo2 { ... }

收尾工作

目前来说注解就已经学完了,但是我们现在学的注解都是 “鸡肋”,都是 “空架子”,他光有注解的型但是没用功能,想要让我们的注解含有功能,我们需要依赖于反射技术。

Java反射

java 的反射机制允许程序在运行过程中获取任何类的完整结构信息 ( 注意,是类的信息,不是实例的信息 ),并可以操作其属性以及方法。

在 java 中针对反射机制也提供了一个类:Class,可以通过 class 实例进行相关操作

获取Class实例

获取到某个类的 class 实例,有大概四种方法,如下所示

  1. 通过实例的 getClass() 函数获取
  2. 通过目标类的 class 属性获取
  3. 通过Class类的静态函数 forName() 获取
  4. 如果是基本类型包装类可以用 TYPE 属性获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获取class对象的4种方法
public class Demo1 {

public static void main(String[] args) throws Exception{
// 创建一个Person实例
Person p = new Person();
// 演示四种方法获取class实例
Class c1 = p.getClass();
Class c2 = Person.class;
Class c3 = Class.forName("site.hanzhe.entity.Person");
Class c4 = Integer.TYPE;
// 打印测试
System.out.println(c1); System.out.println(c2);
System.out.println(c3); System.out.println(c4);
}

}

这里需要注意的是,java 中每种类型只有一个 class 实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// java中每一个类只有一个class实例,无论通过什么方法获取的
public class Demo2 {

public static void main(String[] args) throws Exception{
Person p = new Person();
Class c1 = p.getClass();
Class c2 = Person.class;
Class c3 = Class.forName("site.hanzhe.entity.Person");
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
}

}

java 中几乎所有类型都可以获取到 class 实例对象

  • 类,数组,枚举的 class 实例打印出来是以 class 开头的,且数组后面的 [ 根据维度进行变化的
  • 接口,注解打印的 class 实例打印出来是以 interface 开头的
  • void 就是 void
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// java中几乎所有类型都可以获取到class实例对象,包括他本身
public class Demo3 {
public static void main(String[] args) {
Class c1 = Class.class;
Class c2 = Person.class;
Class c3 = Runnable.class;
Class c4 = int[].class;
Class c5 = int[][].class;
Class c6 = Deprecated.class;
Class c7 = ElementType.class;
Class c8 = void.class;
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
}
}

通过Class实例获取相关信息

我们可以通过以上四种方法获取到某个类的 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
31
32
33
34
// 通过class实例可以获取到类的任何信息
public class Demo4 {
public static void main(String[] args) throws Exception {
// 获取到Person类的class实例
Class<?> c1 = Class.forName("site.hanzhe.entity.Person");
// 获取属性信息
for (Field field : c1.getFields())
System.out.println("获取所有属性: " + field);
for (Field field : c1.getDeclaredFields())
System.out.println("获取全部属性(含私有): " + field);
System.out.println("获取指定属性" + c1.getField("name"));
System.out.println("获取指定属性(含私有)" + c1.getDeclaredField("age"));
System.out.println("------------------------------------------------------");
// 获取函数信息
for (Method method : c1.getMethods())
System.out.println("获取所有函数: " + method);
for (Method method : c1.getDeclaredMethods())
System.out.println("获取所有函数(含私有): " + method);
System.out.println("获取指定函数: " + c1.getMethod("getName"));
System.out.println("获取指定函数(带参数): " +
c1.getMethod("setName", String.class));
System.out.println("获取指定函数(含私有): " + c1.getDeclaredMethod("show"));
System.out.println("------------------------------------------------------");
// 获取构造器信息
for (Constructor<?> constructor : c1.getConstructors())
System.out.println("获取所有构造器: " + constructor);
for (Constructor<?> constructor : c1.getDeclaredConstructors())
System.out.println("获取所有构造器(含私有): " + constructor);
System.out.println("获取指定函数(带参数): " +
c1.getConstructor(String.class, int.class));
System.out.println("获取指定函数(含私有): " +
c1.getDeclaredConstructor(int.class));
}
}

class.get***:获取目标类 ( 包括父类 ) 的 字段/函数

class.getDeclared***:获取目标类 ( 包含私有但不包含父类 ) 的 字段/函数

动态创建执行

刚刚我们已经可以通过 class 实例获取到所有的函数和属性了,既然可以获取那么就一定可以调用了

通过反射获取类的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class UseInstance {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("site.hanzhe.entity.Person");
// 1. 通过class直接创建person对象,但是这样有个弊端,
// 只有在目标类提供了 public 的空参构造才可以获取到
Object o = clazz.newInstance();
System.out.println(o);
System.out.println("======================================================");
// 2、先获取到class的构造方法,然后通过构造方法获取到实例对象
// 这种方法可以获取到任何参数的构造函数
// -- 获取具有两个String作为参数的构造函数
Constructor constructor =
clazz.getDeclaredConstructor(String.class, String.class);
Object o1 = constructor.newInstance("张", "涵哲");
System.out.println(o1);
}
}

invoke:获取到类中的函数并执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class UseMethod {
public static void main(String[] args) throws Exception{
Class<?> clazz = Class.forName("site.hanzhe.entity.Person");
Constructor constructor =
clazz.getDeclaredConstructor(String.class, String.class);
// 获取到类的实例
Object obj = constructor.newInstance("张", "涵哲");
// 获取到类中的setName函数,需要一个String作为参数
Method setName = clazz.getDeclaredMethod("setName", String.class);
// 使用obj实例执行setName方法
setName.invoke(obj, "三");
System.out.println(obj);
}
}

get-set:获取到类中的属性进行操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class UseField {
public static void main(String[] args) throws Exception{
Class<?> clazz = Class.forName("site.hanzhe.entity.Person");
Constructor constructor = clazz.getDeclaredConstructor(String.class, String.class);
// 获取到类的实例
Object obj = constructor.newInstance("张", "涵哲");
// 获取类中名称为name的变量
Field name = clazz.getDeclaredField("name");
// set修改obj的属性值
name.set(obj, "麻子");
// get获取到obj实例中的属性值
System.out.println(name.get(obj));
}
}

我们的代码是没有问题的,但是执行的时候却抛出了异常,我们来看一下异常信息:

1
java.lang.IllegalAccessException: Class site.hanzhe.reflect.UseField can not access a member of class site.hanzhe.entity.Person with modifiers "private"

获取访问权限

上面获取变量的时候抛出了 IllegalAccessException 异常,提示我们没有足够的权限操作 private 私有属性,这时我们可以通过 取消权限检查 来暴力获取到属性的权限

方法名 作用
isAccessible() 获取到是否拥有访问权限的相关信息,返回一个boolean值
setAccessible( boolean flag) 设置访问权限,true 为可访问,false 为不可访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UseField {
public static void main(String[] args) throws Exception{
Class<?> clazz = Class.forName("site.hanzhe.entity.Person");
Constructor constructor =
clazz.getDeclaredConstructor(String.class, String.class);
// 获取到类的实例
Object obj = constructor.newInstance("张", "涵哲");
// 获取类中名称为name的变量
Field name = clazz.getDeclaredField("name");
System.out.println("是否具有权限操作:" + name.isAccessible()); // false
name.setAccessible(true);
System.out.println("是否具有权限操作:" + name.isAccessible()); // true
name.set(obj, "麻子");
System.out.println(name.get(obj));
}
}

高级扩展

Java内存分析

当程序使用某个类的时候会去内存中检查,如果这个类还没有被加载到内存中,则系统会通过以下三个步骤对类进行初始化加载:

1.【类的加载】 就是编译好的 .class 文件读入虚拟机

**2.【类的链接】**类的链接大概可以分为三个流程:

  1. **验证:**检查 class 是否符合 JVM 规范,检查安全问题防止破坏 JVM
  2. **准备:**为该类中的 static 修饰的成员变量设置初始值 ( 例如 int 为0,String 为 null 之类的 )
  3. **解析:**符号引用转换为直接引用,这个…以后在研究

**3.【初始化】**这里会执行类构造器 <clinit>,他会自动收集类中所有变量的赋值动作和静态代码块融在一起产生的方法,会按照类中的编码的顺序从上到下的流程执行

类加载器

上面简单学习了一下关于类加载的过程,知道了类想要使用必须要先加载到内存中,在 java 中类加载器就是负责将类加载到内存中的,java 中的类加载器分为三种:

**1.【系统类加载器】**系统类加载器是最常用的类加载器,负责加载 classpath 下的类或加载 java.class.path 所指的目录下的 jar 包装入,我们所写的 Person 类,User 类都是通过系统类加载器加载到内存中去的

**2.【扩展类加载器】**扩展类加载器加载的是 jre 目录下 lib/ext 下的 jar 包或 java.ext.path 目录下的 jar 包

**3.【引导类加载器】**主要负责加载核心的类,例如 String,Scanner 等等,处于这个状态下 java 是没法工作的,所以这个类加载器使用 C 编写的,也就意味着 java 获取不到这个类加载器

获取类加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 获取类的三中加载器
public class Demo4 {
public static void main(String[] args) throws Exception{
// 获取系统类加载器
ClassLoader loader1 = ClassLoader.getSystemClassLoader();
System.out.println(loader1);
// 获取扩展类加载器
ClassLoader extLoader = loader1.getParent();
System.out.println(extLoader);
// 获取引导类加载器
ClassLoader bootstrapLoader = extLoader.getParent();
System.out.println(bootstrapLoader);
System.out.println("---------------------------------------");
// 查看自己创建的类是被那个加载器加载的
Class c1 = Class.forName("site.hanzhe.examle.Demo4");
ClassLoader loader = c1.getClassLoader();
System.out.println(loader);
}
}

Java注解与反射
http://example.com/2021/04/13/Java笔记/注解与反射/
Author
Shi_Kang
Posted on
April 13, 2021
Licensed under