Java —— 反射和注解
- 1. 反射
- 2. 注解
1. 反射
动态语言:变量的类型和属性可以在运行时动态确定,而不需要在编译时指定 常见动态语言:Python,JavaScript,Ruby,PHP,Perl;常见静态语言(C,C++,C#,Go,Java) Java:Java并不算是严格意义上的动态语言,从反射角度来说属于半动态语言(能通过反射机制实现了部分动态编程的能力) Java反射机制:Java编程语言提供的一种强大的特性,它允许程序在运行时动态地获取类的信息,并通过该信息操作类的成员变量、方法和构造函数 |
Java 的反射机制:在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法
Java反射常用API
Class 类
:反射的核心类,可以获取类的属性,方法等信息
Field 类
:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值
Method类
: Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法
Constructor 类
: Java.lang.reflec 包中的类,表示类的构造方法
获取Class对象的3种方法
调用某个对象的getClass()
方法
❶ 类名 对象名 = new 类名();
❷ Class 类对象名 = 对象名.getClass();
使用Class类中的forName()
静态方法(安全且性能好)
❸ Class 类对象名 = Class.forName(“类的完全限定名”);
高版本Java创建:var 类对象名 = 类名(需要创建类对象的类).class;
Class<类型(类名)> 类对象名 = 类名(需要创建类对象的类.class;
- 对应类的常用方法
- 对应代码
package reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 反射机制
*/
public class TestDemo {
public static void main(String[] args) throws NoSuchMethodException,
InvocationTargetException, IllegalAccessException,
InstantiationException, NoSuchFieldException {
// 创建类对象
var classes = Information.class;
System.out.println("类名:" + classes.getSimpleName());
// 根据类获取当前类的完全限定名
String className = classes.getName();
System.out.println("当前类的完全限定名:" + className);
// 根据获取当前类的包名
String packageName = classes.getPackageName();
System.out.println("包名:" + packageName);
// 根据类获取当前普通方法名
List<String> list = new ArrayList<>();
Method[] methods = classes.getMethods();
for (Method method : methods
) {
list.add(method.getName());
}
System.out.println("public方法个数:" + list.size());
System.out.println("public方法:" + list);
// 获取构造器(无参构造器)
Constructor<Information> constructor = classes.getConstructor();
// 利用无参构造器创建实例
Object obj = constructor.newInstance();
// 获取指定的普通方法,带参数
Method publicMethod = classes.getMethod("publicMethod", String.class, int.class);
// 获取指定私有方法
Method privateMethod = classes.getDeclaredMethod("privateMethod", String.class);
System.out.println("获取指定私有方法:" + privateMethod);
// 强行开启私有方法访问权限
privateMethod.setAccessible(true);
// 获取所有普通public属性
Field[] fields = classes.getFields();
System.out.println("普通属性:" + Arrays.toString(fields));
// 获取所有属性
Field[] declaredFields = classes.getDeclaredFields();
System.out.println("所有属性:" + Arrays.toString(declaredFields));
// 获取属性名
for (Field field:declaredFields
) {
String name = field.getName();
System.out.print(name + ", ");
}
System.out.println();
// 访问单个私有属性
Field name = classes.getDeclaredField("name");
// 强行打开权限
name.setAccessible(true);
System.out.println("访问单个私有属性:名称:" + name.getName() + " 类型:" +
name.getType() + " 权限修饰符(二进制位标识的修饰符信息):" + name.getModifiers());
// 私有方法调用
privateMethod.invoke(obj, "零零");
// 方法调用
publicMethod.invoke(obj, "夏鸥", 22);
System.out.println("无参构造器创建的实例:" + obj);
// 获取有参构造器
Constructor<Information> constructors = classes.getConstructor(String.class, int.class, String.class);
// 有参构造器创建实例
Object object = constructors.newInstance("王菲", 20, "2020002");
// 获取指定的普通方法
Method classesMethod = classes.getMethod("publicMethod");
// 方法调用
classesMethod.invoke(object);
System.out.println("有参构造器创建的实例:" + object);
}
}
class Information{
private String name = "赫敏";
private int age = 20;
public String num = "2020001";
public Information() {
}
public Information(String name, int age, String num) {
this.name = name;
this.age = age;
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getNum() {
return num;
}
public void setNum(String num) {
this.num = num;
}
@Override
public String toString() {
return "TestDemo{" +
"name='" + name + '\'' +
", age=" + age +
", num='" + num + '\'' +
'}';
}
private void privateMethod() {
System.out.println(name + " 私有方法");
}
private void privateMethod(String name) {
this.name = name;
System.out.println(name + " 私有方法");
}
public void publicMethod() {
System.out.println(name + " 普通方法");
}
public void publicMethod(String name, int age) {
this.name = name;
this.age = age;
System.out.println(name + age + " 普通方法");
}
}
- 应用实例
package reflect;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.net.URL;
/**
* 要求:实例化与当前类在统一和包中的所有类
*/
public class Instantiations {
public static void main(String[] args) throws ClassNotFoundException, URISyntaxException, NoSuchMethodException,
InvocationTargetException, InstantiationException, IllegalAccessException {
/* 1. 获取当前包中的所有类名 */
// 获取包名
String packageName = Instantiations.class.getPackageName();
// 获取当前绝对路径资源 ".":表示当前路径
URL path = Instantiations.class.getResource(".");
// 获取路径下的所有文件
File file = new File(path.toURI());
// 将所需文件过滤存储到数组(所有类)
File[] files = file.listFiles(f -> f.getName().endsWith(".class"));
// 获取到
for (File sub:files
) {
String name = sub.getName();
// 获取类名
String className = name.replace(".class", "");
System.out.println("实例化类:" + packageName + "." + className);
Class<?> classes = Class.forName(packageName + "." + className);
// 通过无参构造器创建实例
Object obj = classes.getConstructor().newInstance();
System.out.println(obj);
}
}
}
2. 注解
注解:元数据 / 注解(注解Annotation是一个接口),一种提供程序中元素信息和数据的途径和方法 作用:在不改变程序主体逻辑的情况下,为程序员提供额外的元数据信息(标记一段代码的功能、作用范围、参数要求等信息) |
4种标准元注解:元注解的作用是负责注解其他注解
❶ @Target 注解修饰范围 / 修饰的目标元素类型
❷ @Retention:注解生命周期 / 保留级别 / 被保留的时间长短
❸ @Documented注解是否会被包含在Java文档中生成
❹ @Inherited注解是否可以被继承
@Target元素类型
ElementType.TYPE
:类、接口或枚举类型
ElementType.FIELD
:字段或属性
ElementType.METHOD
:方法
ElementType.PARAMETER
:方法参数
ElementType.CONSTRUCTOR
:构造函数
ElementType.LOCAL_VARIABLE
:局部变量
ElementType.ANNOTATION_TYPE
:注解类型
ElementType.PACKAGE
:包
@Retention的三个值
RetentionPolicy.SOURCE
:注解仅存在于源代码中,编译时会被丢弃
RetentionPolicy.CLASS
:注解存在于源码和编译后的字节码文件中,但在运行时会被丢弃(默认值)
RetentionPolicy.RUNTIME
:注解在运行时保留在字节码文件中,可以通过反射机制读取
@Documented和@Inherited
一个注解被@Documented修饰,那么它的信息将会被包含在生成的文档中
一个注解被@Inherited修饰,表示该注解可以被子类继承
- 基本原则:不直接干扰程序代码的运行
Java 注解是一种元数据,它可以提供对程序元素(如类、方法、字段等)的额外信息或配置,并且不会直接影响程序的运行逻辑,只是一种机制,用于存储和传递额外的信息
-
注解分类
-
声明注解及变长参数
package reflect.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 注解类型
@Target(ElementType.METHOD)
// 注解级别
@Retention(RetentionPolicy.RUNTIME)
// 声明注解
public @interface AutoRunMethod {
// 注解的参数(当注解下的参数只有一个时,一般用value)
int value() default 1;
String name() default "张三";
}
- 使用注解
package reflect;
import reflect.annotations.AutoRunClass;
import reflect.annotations.AutoRunMethod;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.util.Arrays;
public class Reflect {
public static void main(String[] args) {
// 加载包下被注解的类被注解的方法
try {
String packName = Reflect.class.getPackage().getName();
File dir = new File(Reflect.class.getResource(".").toURI());
File[] subs = dir.listFiles(f->f.getName().endsWith(".class"));
for (File sub:subs
) {
String className = sub.getName().replace(".class","");
Class classes = Class.forName(packName + "." + className);
if (classes.isAnnotationPresent(AutoRunClass.class)){
System.out.println("实例化:" + className);
Constructor constructor = classes.getConstructor();
Object obj = constructor.newInstance();
System.out.println(obj);
// 方法名获取
Method[] methods = classes.getDeclaredMethods();
for (Method method:methods
) {
// 判断该方法是否被注解
if (method.isAnnotationPresent(AutoRunMethod.class)){
System.out.println("调用方法:" + method.getName() + "()");
// 获取注解参数
AutoRunMethod arm = method.getAnnotation(AutoRunMethod.class);
// 获取值(定义时将其认为参数,调用时看作方法)
int value = arm.value();
String name = arm.name();
System.out.println("注解参数的值:" + value + name);
// 根据注解参数的值调用方法
for (int i = 0; i < value; i++) {
// 方法调用
method.invoke(obj);
}
}
}
}
}
} catch (URISyntaxException | ClassNotFoundException |
NoSuchMethodException | InstantiationException |
IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
// 注解类
@AutoRunClass
class Student {
public String address = "贵阳市花溪区";
private String name = "赵涛";
private char gender = '男';
private int age = 15;
public Student() {
}
public Student(String name, char gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 注解方法
@AutoRunMethod(3)
public void sayHi() {
System.out.println(name + ":你好");
}
@AutoRunMethod(value = 2, name = "李四")
public void sleep() {
System.out.println(name + ":睡觉");
}
@AutoRunMethod(name = "王五")
public void watchTV() {
System.out.println(name + ":看电视");
}
public void playGame() {
System.out.println(name + ":玩游戏");
}
public void say(String info) {
System.out.println(name + "说:" + info);
}
public void say(String info, int count) {
System.out.println(name + "说了" + count + "次," + info);
}
public void study() {
System.out.println(name + "学习");
}
@AutoRunMethod
public void doHomework() {
System.out.println(name + ":做作业");
}
// **********************普通方法(参数变长)只是Java编译器认可(最终为数组)***************************
// 变长参数只能在方的最后一个参数,且一个方法只能有一个变长参数
public void doSome(String... s) {
System.out.println("参数个数:" + s.length);
System.out.println("参数:" + Arrays.toString(s));
}
private void privateMethod() {
System.out.println("这是一个私有方法");
}
@Override
public String toString() {
return "Student{" +
"address='" + address + '\'' +
", name='" + name + '\'' +
", gender=" + gender +
", age=" + age +
'}';
}
}
- 变长参数只能在方的最后一个参数,且一个方法只能有一个变长参数
- 注解格式:
类型 参数名() [default 默认值]
注:不指定默认值时使用注解必须传递对应参数- 注解传参机制:
当注解仅有一个参数,且参数名为value时,直接传入参数(不需要参数名)
当注解仅有一个参数,且参数名不为value时,正常使用注解传参语法:参数名=参数值
多个参数传参使用参数名进行传参,传参顺序可以与注解定义时参数顺序不一致
有多个参数时,即使一个注解的参数名为value,在实际使用时参数名也不可以忽略