Java反射和注解

简介: Java反射和注解Reflection今天来挑战一下如何在2000字以内把Reflection作用说明白?Reflection is commonly used by programs which require the ability to examine or modify the runt...

Java反射和注解
Reflection
今天来挑战一下如何在2000字以内把Reflection作用说明白?

Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible.

https://docs.oracle.com/javase/tutorial/reflect/index.html

Java Reflection反射机制:Java可以获取/调用任意已加载类的所有信息(字段/方法/构造函数)。甚至改变类中成员的各种属性(又如private改成public)。官方API说明都在java.lang.reflect。

假设目前我们只有奥迪车需要测试运行速度。

package com.car.test;
// Car.java
class Car {int velocity;}
// Runnable.java
interface Runnable {public void run();}
// Audi.java
public class Audi extends Car implements Runnable {

Audi(int velocity){this.velocity = velocity;}
public void run() {
  String className = this.getClass().getSimpleName();
  System.out.println(className + " run " + velocity + "km/h");
}

}
// CarFactory.java
public class CarFactory {

public static void main(String[] args) {
  Audi audi = new Audi(120);
  audi.run();
}

}
上面因为我们知道需要测试的只有Audi这一种车型,所以在main里面可以直接用调用对应的构造函数进行测试,但是当我们的车型增加时(特斯拉也来啦),我们就不得不再次修改main函数。

public class CarFactory {
public static void main(String[] args) {

Tesla tesla = new tesla(150);
tesla.run();

}
}
为了更好的测试不断新加车型,同时不修改我们的工厂测试主函数。我们可以:

package com.car.test;
// Tesla.java
// 省略以前已有不变的Audi
public class Tesla extends Car implements Runnable {
Tesla(int velocity){this.velocity = velocity;}
public void run() {

String name = this.getClass().getSimpleName();
System.out.println(name + " run " + velocity + "km/h");

}
}
package com.car.test;
// CarFactory.java
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class CarFactory {
public static void main(String[] args) {

try {
  //通过命令行把动态所需要测试的类名传入测试主函数。
  //args[0] = "com.car.test.Tesla".
  //args[1] = "140".
  //得到类名.此函数需catch异常 ClassNotFoundException.
  Class<?> c = Class.forName(args[0]);            
  //找到对应的构造函数并构造出实例
  int velocity = Integer.parseInt(args[1]);
  Object car = c.getDeclaredConstructor(int.class).newInstance(velocity);
  //找到需要测试的函数定义 
  Method method = c.getDeclaredMethod("run");
  //执行对应函数
  method.invoke(car);
} catch (ClassNotFoundException e) {
   e.printStackTrace();
} catch (NoSuchMethodException e) {
   e.printStackTrace();
} catch (IllegalAccessException e) {
   e.printStackTrace();
} catch (InstantiationException e) {
   e.printStackTrace();
} catch (InvocationTargetException e) {
   e.printStackTrace();
}

}
}
这样只要我们在命令行传入对应的类名,就可以执行对应的测试函数啦。

完美做到新加车型,不需要修改主测试函数。这就是java反射机制在运行时的一个基本示例。但是对于一个静态语言来说,这种动态调用太过灵活,所以需要每一步都要小心(上面的每个函数都需要catch异常)

With great power comes great responsibility.

上面就是事先不知道我们需要测试的是什么类,只有到了运行时才能得到对应的类,这就是应用反射机制的常用场景。它在运用在真实场景中一个典型例子就是Junit,它过去枚举了类中所有的方法getDeclaredMethods(),并把以testXXX开头的方法假设为测试函数并执行它们。但在JUit4后使用了注解(annotations)来替换了它。

不过注解的本质也是通过反射来实现的。

Annotations
在文章的开始处写反射是对类的属性/方法/构造函数的操作,并没有提到注解。但是通过Reflection API列表我们可以看到他有getAnnotation之类的函数,所以注解也是可以读写操作的。不过想对于上面说的,稍微复杂一点。

解析注解的方式有两种,编译期检查和运行期反射。

1.编译期检查
常见到的就是@Override,编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法,也就是比较父类中是否具有一个同样的方法签名。比如我们在上面的Car类中增加一个方法得到名字:

package com.car.test;
//car.java
class Car {
int velocity;
public String getName() {return "Car";}
}
// Tesla.java
public class Tesla extends Car implements Runnable {
Tesla(int velocity){this.velocity = velocity;}
// @Override
public get_name() { return "Tesla";}
// 省略以前有的
}
}
Tesla继承了Car,并想重写它的getName函数。但不小心手误写成了get_name,这时Tesla就同时有了这两个函数。为了避免这种低级错误,就使用@Overide,这告诉编译器,此方法是重写父类方法的,如果方法的定义(名字/返回值/参数)与父类不一致,则编译不通过。PS: 打开上面的注释,你就会得到一个编译报错。

可见@Override作用于方法,只在编译期解析,编译结束后,使命就完成了。不会把信息存到字节码中。

其它内置的注解还有

@Deprecated标记当前类/方法/字段不再被推荐使用,下次版本可能会不在支持它。
@SuppressWarnings明确告诉编译器这个警告我已发现了,你不用再来烦我。
2.元注解
为了在注解定义时规定生命周期(编译期/永久保存etc),作用范畴(字段/方法etc),又引入了注解的注解,也就是元注解,它是主要用于修饰注解的注解。比如在@override的定义中

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Target表示作用目标,METHOD作用于方法,还有其它的FIELD,PARAMETER之类的。
@Retention表示生命周期SOURCE编译器可见,不写入class文件;CLASS类加载时丢弃,会写入class文件,RUNTIME永久保存,可以通过反射读取。
@Documented是否在JavaDoc文档中出现。
Inherited是否允许子类继承该注解。
3.运行期注解
下面我们稍微改造一个上面car的例子来说明一下运行期的注解操作。

通过新建一个注解(@DriveAccess来表示控制可以允许运行run函数进行函数(当然,你可以有更好的方法来做这件事,这里只是为了用来演示注解如何工作)。

package com.car.test;
// DriveAccess.java
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME) // 永久保存
@Target(ElementType.METHOD) //作用于方法
public @interface DriveAccess {
public boolean canDrive() default false; //默认返回false
}
// Tesla.java
public class Tesla extends Car implements Runnable {
Tesla(int velocity) { this.velocity = velocity; }

@Override
public String getName() { return "Tesla"; }

@DriveAccess(canDrive = true)
public void run() {

String name = this.getClass().getSimpleName();
System.out.println(name + " run " + velocity + "km/h");
}

}
// CarFactory.java
import java.lang.reflect.Method;

public class CarFactory {
public static void main(String[] args) throws Exception {

Class<?> car = Class.forName(args[0]);
for (Method method : car.getDeclaredMethods()) {
  if (method.isAnnotationPresent(DriveAccess.class)) {
    DriveAccess access = method.getAnnotation(DriveAccess.class);
    String methodName = method.toGenericString();
    if (access.canDrive()) {
      System.out.println(methodName + " method can be accessed... ");
      Object c = car.getDeclaredConstructor(int.class).newInstance(100);
      method.invoke(c);
    } else {
      System.out.println(methodName + " method can not be accessed... ");
    }
  }else {
   System.out.println(methodName + " don't have DriveAccess Annotation...");
 }

}
}
}
}
运行 java CarFactory com.car.test.Tesla得到

public void com.car.test.Tesla.run() method can be accessed...
Tesla run 100km/h
public java.lang.String com.car.test.Tesla.getName() don't have DriveAccess Annotation...
如果Tesla中的canDirve改成false则:

public void com.car.test.Tesla.run() method can not be accessed...
public java.lang.String com.car.test.Tesla.getName() don't have DriveAccess Annotation...
Summary
运用Java Reflection API可以读取/操作类中所有的元素。非常灵活强大,因为灵活,也会带来很多不确定的危险。所以如果可以用其它方法实现的,最好不要用反射。
原文地址https://www.cnblogs.com/zhongwencool/p/reflection_annotations.html

相关文章
|
9天前
|
Java
【专栏】Java中的反射机制与应用实例
【4月更文挑战第27天】本文探讨了Java反射机制,该机制允许程序在运行时获取类信息、动态创建对象、调用方法和访问属性。反射通过Class、Constructor、Method和Field类实现。文中列举了反射的应用场景,如动态创建对象、调用方法、访问属性和处理注解,并提供了相关实例代码演示。
|
22小时前
|
Java C++
Java反射的简单使用
Java反射的简单使用
10 3
|
23小时前
|
SQL 存储 Java
【Java反射详解】
【Java反射详解】
6 1
|
23小时前
|
Java 编译器 Android开发
Java注解你知多少?
Java注解你知多少?
5 1
|
5天前
|
Java
JAVA难点包括异常处理、多线程、泛型和反射,以及复杂的分布式系统知识
JAVA难点包括异常处理、多线程、泛型和反射,以及复杂的分布式系统知识。入坑JAVA因它的面向对象特性、平台无关性、强大的标准库和活跃的社区支持。
19 2
|
8天前
|
Java 测试技术
滚雪球学Java(24):Java反射
【4月更文挑战第13天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
16 0
滚雪球学Java(24):Java反射
|
8天前
|
Java 编译器 Maven
一文解读|Java编译期注解处理器AbstractProcessor
本文围绕编译器注解都是如何运行的呢? 又是怎么自动生成代码的呢?做出了详细介绍。
|
10天前
|
设计模式 Java Spring
来聊聊Java的反射机制(下)
来聊聊Java的反射机制(下)
7 0
|
10天前
|
Java 编译器 Spring
来聊聊Java的反射机制(上)
来聊聊Java的反射机制
11 0
|
10天前
|
Java 关系型数据库 MySQL
万字长文带你详聊Java注解本质
万字长文带你详聊Java注解本质
17 0