反射的应用
- 自定义注解
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String value(); }
- 自定义接口
public interface MyInterface { void method(); }
- 自定义带泛型的类
public class Creature<T> { boolean gender; public int id; public void breath(){ System.out.println("呼吸"); } private void info(){ System.out.println("我是一个生物"); } }
- Person 类
@MyAnnotation("t_persons") public class Person extends Creature<String> implements Comparable<Person>,MyInterface{ private String name; public int age = 1; @MyAnnotation("info") private static String info; public Person(){ System.out.println("Person()..."); } protected Person(int age){ this.age = age; } private Person(String name, int age){ this.name = name; this.age = age; } public void show() throws RuntimeException,ClassNotFoundException{ System.out.println("你好,我是一个Person"); } @MyAnnotation(value="show_nation") private String showNation(String nation,int age){ System.out.println("showNation..."); return "我的国籍是:" + nation + ",生活了" + age + "年"; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override public int compareTo(Person o) { return 0; } @Override public void method() { } public static void showInfo(){ System.out.println("我是一个人"); } }
创建运行时类的对象
- 利用反射创建 Person 类的对象
- 通过 Class 实例对象调用 newInstance() 方法即可
- 运行时类中必须提供一个空参构造器
- 并且空参构造器的权限必须足够
- newInstance() 方法,由于使用该方法的要求较高,所以在JDK9开始不被推荐使用,推荐使用
Class实例对象.getDeclaredConstructor().newInstance()
- getDeclaredConstructor() 得到公开的构造器,使用得到的构造器进行对象的创建
// InstantiationException 没有空参构造器会抛出该异常 // IllegalAccessException 空参的构造器权限不足会抛出该异常 @org.junit.Test public void test01() throws ClassNotFoundException, InstantiationException, IllegalAccessException { // 加载类到内存,获取 Person 类对应的运行时类对象 Class<?> personClass = Class.forName("study02.data.Person"); // 创建 Person 类的实例对象 Person person = (Person) personClass.newInstance(); System.out.println(person); }
JavaBean 应当提供一个公共的空参构造器
- 子类对象在实例化时,子类的构造器的首行默认调用父类空参的构造器。
- 在反射中,经常用来创建运行时类的对象。那么我们要求各个运行时类都提供一个空参的构造器,便于我们编写通用的创建运行时类对象的代码。
获取运行时类的属性
getFields()
- 获取运行时类及其父类中声明为public访问权限的属性。
@org.junit.Test public void test1() { Class personClass = Person.class; // 使用getFields()获取运行时类及其父类中声明为public访问权限的属性。 // 返回值为一个Field类型的数组 Field[] fields = personClass.getFields(); for (Field field : fields) { System.out.println(field); } }
getDeclaredField()
- 获取运行时类中声明的所有属性,不包含父类中声明的属性。
@Test public void test2() { Class personClass = Person.class; // getDeclaredField()获取运行时类中声明的所有属性,不包含父类中声明的属性。 // 返回值为一个Field类型的数组 Field[] fields = personClass.getDeclaredFields(); for (Field field: fields) { System.out.println(field); } }
获取属性的结构
- 属性的权限修饰符、数据类型、变量名等也可以通过反射进行获取。
field.getModifiers()
- 获取属性的权限修饰符
- 得到的为权限修饰符对应的数字,默认权限为0
- 可以使用
Modifier.toString(modifiers)
将权限修饰符对应数字转换为对应的权限修饰符字符串
field.getType()
- 获取属性的数据类型
- 返回值类型为Class
- type.getName()会返回数据类型的全类名
field.getName()
- 获取属性的变量名
@Test public void test3() { Class personClass = Person.class; Field[] fields = personClass.getDeclaredFields(); for (Field field: fields) { // 1. 获取属性的权限修饰符 // field.getModifiers()得到的为权限修饰符对应的数字 // 默认权限为0 // 可以使用Modifier.toString(modifiers)将权限修饰符对应数字转换为对应的权限修饰符字符串 int modifiers = field.getModifiers(); System.out.println(Modifier.toString(modifiers)); // 2. 获取属性的数据类型 // 返回值类型为Class Class type = field.getType(); // type.getName()会返回数据类型的全类名 System.out.println(type.getName()); // 3. 获取属性的变量名 String fieldName = field.getName(); System.out.println(fieldName); System.out.println("---------------"); } }
权限修饰符对应的数字转换为字符串的对应关系
// 对于权限修饰符,将其数字转换为对应的字符串的对应关系 // 0x是十六进制 // * PUBLIC = 0x00000001; 1 1 // * PRIVATE = 0x00000002; 2 10 // * PROTECTED = 0x00000004; 4 100 // * STATIC = 0x00000008; 8 1000 // * FINAL = 0x00000010; 16 10000 public static String toString(int mod) { StringJoiner sj = new StringJoiner(" "); if ((mod & PUBLIC) != 0) sj.add("public"); if ((mod & PROTECTED) != 0) sj.add("protected"); if ((mod & PRIVATE) != 0) sj.add("private"); /* Canonical order */ if ((mod & ABSTRACT) != 0) sj.add("abstract"); if ((mod & STATIC) != 0) sj.add("static"); if ((mod & FINAL) != 0) sj.add("final"); if ((mod & TRANSIENT) != 0) sj.add("transient"); if ((mod & VOLATILE) != 0) sj.add("volatile"); if ((mod & SYNCHRONIZED) != 0) sj.add("synchronized"); if ((mod & NATIVE) != 0) sj.add("native"); if ((mod & STRICT) != 0) sj.add("strictfp"); if ((mod & INTERFACE) != 0) sj.add("interface"); return sj.toString(); }
获取运行时类的方法
getMethods()
- 获取运行时类及其所有父类中声明为public访问权限的方法。
@Test public void test1() { Class personClass = Person.class; // getMethods()获取运行时类及其所有父类中声明为public访问权限的方法。 // 返回值为Method类型的数组 Method[] methods = personClass.getMethods(); for (Method method: methods) { System.out.println(method); } }
getDeclaredMethods()
- 获取运行时类中声明的所有方法,不包含父类中声明的方法
@Test public void test2() { Class personClass = Person.class; // getDeclaredMethods()获取运行时类中声明的所有方法,不包含父类中声明的方法 // 返回值为Method类型的数组 Method[] methods = personClass.getDeclaredMethods(); for (Method method: methods) { System.out.println(method); } }
获取方法的结构
- 方法的结构:
@Xxxx 权限修饰符 返回值类型 方法名(参数类型1 参数名1, ...) throws Exception {}
method.getAnnotations()
- 获取方法声明的注解
- 返回值为Annotation类型的数组,因为一个方法上可以加多个注解
method.getModifiers()
- 得到的为权限修饰符对应的数字
- Modifier.toString()将权限修饰符数字转换为对应的字符串
method.getReturnType()
- 获取方法的返回值类型
- type.getName() 获取返回值类型对应的字符串
method.getName()
- 获取方法的方法名
method.getParameterTypes()
- 获取方法的形参列表
- 返回 Class 类型的数组
method.getExceptionTypes()
- 获取方法抛出的异常
- 返回 Class 类型的数组,因为抛出的异常可能有多个也可能没有异常的抛出
@Test public void test4() { Class clazz = Person.class; Method[] methods = clazz.getDeclaredMethods(); for (Method m : methods) { //1.获取方法声明的注解 //返回值为Annotation类型的数组,因为一个方法上可以加多个注解 Annotation[] annos = m.getAnnotations(); for (Annotation a : annos) { System.out.println(a); } //2.权限修饰符 //m.getModifiers()得到的为权限修饰符对应的数字 //Modifier.toString()将权限修饰符数字转换为对应的字符串 System.out.print(Modifier.toString(m.getModifiers()) + "\t"); //3.返回值类型 System.out.print(m.getReturnType().getName() + "\t"); //4.方法名 System.out.print(m.getName()); System.out.print("("); //5.形参列表 Class[] parameterTypes = m.getParameterTypes(); //判断方法是否有形参列表 if (!(parameterTypes == null || parameterTypes.length == 0)) { // 循环输出参数类型 for (int i = 0; i < parameterTypes.length; i++) { // 最后一个形参的输出进行特殊处理 if (i == parameterTypes.length - 1) { System.out.print(parameterTypes[i].getName() + " args_" + i); break; } System.out.print(parameterTypes[i].getName() + " args_" + i + ","); } } System.out.print(")"); //6.抛出的异常 //抛出的异常可能有多个也可能没有异常的抛出 Class[] exceptionTypes = m.getExceptionTypes(); //判断是否有异常的抛出 if (exceptionTypes.length > 0) { System.out.print("throws "); for (int i = 0; i < exceptionTypes.length; i++) { if (i == exceptionTypes.length - 1) { System.out.print(exceptionTypes[i].getName()); break; } System.out.print(exceptionTypes[i].getName() + ","); } } System.out.println("{}"); } }
获取运行时类的构造器
getConstructors()
- 获取运行时类中声明为public访问权限的构造器。
@Test public void test1() { Class personClass = Person.class; // getConstructors()获取运行时类中声明为public访问权限的构造器。 // 返回值为Constructor类型的数组,构造器可能有多个 Constructor[] constructors = personClass.getConstructors(); for (Constructor constructor : constructors) { System.out.println(constructor); } }
getDeclaredConstructors()
- 获取运行时类中声明的所有构造器
@Test public void test2() { Class personClass = Person.class; // getDeclaredConstructors()获取运行时类中声明的所有构造器 // 返回值为Constructor类型的数组,构造器可能有多个 Constructor[] constructors = personClass.getDeclaredConstructors(); for (Constructor constructor : constructors) { System.out.println(constructor); } }
- 通过反射也可以获取构造器的结构,获取方法与获取方法的结构类似。
获取运行时类的父类
@Test public void test() { Class personClass = Person.class; // 获取运行时类的父类 // 运行时类的父类的类型也为Class Class superClass = personClass.getSuperclass(); System.out.println(superClass); }
获取运行时类的带泛型的父类
@Test public void test() { Class personClass = Person.class; // 获取运行时类的带泛型的父类 Type genericSuperclass = personClass.getGenericSuperclass(); System.out.println(genericSuperclass); }
获取运行时类的带泛型的父类的泛型
@Test public void test() { Class personClass = Person.class; // 先获取带泛型的父类 // Type 是一个接口,Class 实现了该接口 Type genericSuperclass = personClass.getGenericSuperclass(); System.out.println(genericSuperclass); // 已经确定genericSuperclass带有泛型,将其强转为带有参数类型ParameterizedType类型 ParameterizedType paramType = (ParameterizedType) genericSuperclass; //调用getActualTypeArguments()获取泛型 //返回的为数组,是由于有时候泛型有多个,如Map<K, V>就有两个泛型 Type[] actualTypeArguments = paramType.getActualTypeArguments(); System.out.println(actualTypeArguments[0].getTypeName()); }
获取运行时类实现的接口
获取运行时类实现的接口
@Test public void test() { Class clazz = Person.class; // 获取运行时类实现的接口 // 由于实现的接口可能有多个,所以返回的为数组 Class[] interfaces = clazz.getInterfaces(); for(Class c : interfaces){ System.out.println(c); } }
获取运行时类的父类实现的接口
@Test public void test() { Class clazz = Person.class; //获取运行时类的父类实现的接口 //先获取运行时类的父类 //然后获取运行时类的父类实现的接口 Class[] interfaces1 = clazz.getSuperclass().getInterfaces(); for(Class c : interfaces1){ System.out.println(c); } }
获取运行时类所在的包
@Test public void test() { Class clazz = Person.class; Package pack = clazz.getPackage(); System.out.println(pack); }
获取运行时类的注解
- JDK 5.0 在 java.lang.reflect 包下新增了 AnnotatedElement 接口, 该接口代表程序中可以接受注解的程序元素。当一个 Annotation 类型被定义为运行时 Annotation 后, 该注解才是运行时可见,当 class 文件被载入时保存在 class 文件中的 Annotation 才会被虚拟机读取,程序可以调用 AnnotatedElement对象的如下方法来访问 Annotation 信息:
@Test public void test() { Class clazz = Person.class; Annotation[] annotations = clazz.getAnnotations(); for(Annotation annos : annotations){ System.out.println(annos); } }
调用指定的属性
- 调用指定属性的步骤:
- 步骤1:通过Class实例调用getDeclaredField(String fieldName),获取运行时类指定名的属性
- 步骤2:setAccessible(true):确保此属性是可以访问的
- 步骤3:通过Filed类的实例调用get(Object obj) (获取的操作)或 set(Object obj,Object value) (设置的操作)进行操作。
@org.junit.Test public void test03() throws Exception { // 加载类,获取该类对应的Class实例 Class<?> personClass = Class.forName("study02.data.Person"); // 创建对象 Person person = (Person) personClass.getDeclaredConstructor().newInstance(); /* * 公共的非静态的属性 */ // 获取运行时类的指定名的属性 Field age = personClass.getField("age"); // 该属性公共的非静态的 // 获取属性值 // 由于是非静态的,所以要指明获取哪个对象的属性 Object personAge = age.get(person); // 返回的为该对象的属性值 System.out.println("person age 属性的初始值为:" + personAge); // 设置属性值 age.set(person, 22); // 设置person的age属性的属性值为22 System.out.println("person age 属性的当前值为:" + age.get(person)); /* * 私有的属性(获取当前运行时类的公共属性或私有属性都可以采用下面的方法) */ // getField() 获取运行时类及其父类中声明为 public 访问权限的属性 // 获取私有属性需要使用 getDeclaredField() 获取运行时类中声明的所有属性,不包含父类中声明的属性 Field name = personClass.getDeclaredField("name"); // 私有权限默认不能进行访问 // 要访问私有权限,需要修改其是否可以访问 确保属性可以访问 name.setAccessible(true); // 修改为可以进行访问 name.set(person, "张三"); // 设置person的name为 “张三” String personName = (String) name.get(person); // 获取person的name System.out.println("person 的 name 为:" + personName); /* * 运行时类的静态属性 */ Field info = personClass.getDeclaredField("info"); // 获取属性 info.setAccessible(true); // 设置为可以访问 // 对应静态变量,info.set(null, "Person 类的信息..."); 也是可以的 // 因为获取 静态属性 时已经知道该属性是哪个类的了,无向再次指明 info.set(personClass, "Person 类的信息..."); // 由于是静态属性所以调用该方法的对象为类对应的Class实例 // info.get(null) Object PersonInfo = info.get(personClass); // 获取属性值 System.out.println(PersonInfo); }
调用指定的方法
- 调用指定方法的步骤:
- 步骤1.通过Class的实例调用getDeclaredMethod(String methodName,Class … args),获取指定的方法
- 步骤2. setAccessible(true):确保此方法是可访问的
- 步骤3.通过Method实例调用invoke(Object obj,Object … objs),即为对Method对应的方法的调用。
- invoke()的返回值即为Method对应的方法的返回值
- 特别的:如果Method对应的方法的返回值类型为void,则invoke()返回值为null
@org.junit.Test public void test04() throws Exception { // 获取运行时类Class对象 Class<Person> personClass = Person.class; // 创建Person对象 Person person = personClass.getDeclaredConstructor().newInstance(); // 获取方法 private String showNation(String nation,int age) {} // 参数一:方法名 // 参数二:可变形参,方法的形参列表中的各参数的类型(参数类型要对应) // 类型不能自动装箱,类型的值才可以自动装箱 Method showNation = personClass.getDeclaredMethod("showNation", String.class, int.class); // 设置方法是否可以访问 showNation.setAccessible(true); // 参数一:调用哪个对象的该方法 // 参数二:可变形参,方法的形参 // 返回值为方法的返回值 Object invokeBack = showNation.invoke(person, "中国", 23); System.out.println(invokeBack); }
调用指定的构造器
- 调用指定构造器的步骤:
- 步骤1.通过Class的实例调用getDeclaredConstructor(Class … args),获取指定参数类型的构造器
- 步骤2.setAccessible(true):确保此构造器是可以访问的
- 步骤3.通过Constructor实例调用newInstance(Object … objs),返回一个运行时类的实例。
@org.junit.Test public void test05() throws Exception { // 获取运行时类 Class<Person> personClass = Person.class; // 获取构造器 private Person(String name, int age) {} // 参数:可变形参,构造器形参的类型 Constructor<Person> personConstructor = personClass.getDeclaredConstructor(String.class, int.class); // 设置构造器可以进行访问 personConstructor.setAccessible(true); // 利用获取的构造器创建对象 Person person = personConstructor.newInstance("张三", 34); System.out.println(person); }
获取指定的注解
自定义注解
/** * ClassName: Table * Package: PACKAGE_NAME * Description: * 普通Java对象和数据库中哪个表对应 * * @Author tcw * @Create 2023-05-25 9:25 * @Version 1.0 */ @Target(ElementType.TYPE) // 该注解只能使用在类、接口、枚举、记录上 @Retention(RetentionPolicy.RUNTIME) // 在运行时需要可以通过反射获取该注解,所以RUNTIME public @interface Table { String value(); } /** * ClassName: Column * Package: PACKAGE_NAME * Description: * * @Author tcw * @Create 2023-05-25 9:32 * @Version 1.0 */ @Target(ElementType.FIELD) // 该注解只能在属性上 @Retention(RetentionPolicy.RUNTIME) // 在运行时需要可以通过反射获取该注解,所以RUNTIME public @interface Column { String columnName(); // 对应数据库表中字段的字段名 String columnType(); // 对应数据库表中字段的字段类型 }
Java 类
/** * ClassName: Customer * Package: PACKAGE_NAME * Description: * * @Author tcw * @Create 2023-05-25 9:29 * @Version 1.0 */ @Table("t_customer") // 该Java类与数据库中的t_customer表对应 public class Customer { @Column(columnName = "name", columnType = "varchar(10)") private String name; @Column(columnName = "age", columnType = "int") private Integer age; public Customer() { } public Customer(String name, Integer age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "Customer{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
获取注解
@Test public void test() throws Exception { // 加载类获取类对应的Class实例 Class<?> customer = Class.forName("Customer"); // 获取类上的注解(Table) Table customerDeclaredAnnotation = customer.getDeclaredAnnotation(Table.class); // 获取注解的属性 System.out.println("该Java类对应的数据库表为:" + customerDeclaredAnnotation.value()); // 获取属性 Field[] customerDeclaredFields = customer.getDeclaredFields(); // 获取属性上的注解 for (Field customerDeclaredField : customerDeclaredFields) { Column fieldAnnotation = customerDeclaredField.getAnnotation(Column.class); System.out.print("对应数据库中的字段:" + fieldAnnotation.columnName()); System.out.println(" 类型为:" + fieldAnnotation.columnType()); } }
反射练习
案例:榨汁机榨水果汁,水果分别有苹果(Apple)、香蕉(Banana)、桔子(Orange)等。
提示:
1、声明(Fruit)水果接口,包含榨汁抽象方法:void squeeze(); /skwiːz/
2、声明榨汁机(Juicer),包含运行方法:public void run(Fruit f),方法体中,调用f的榨汁方法squeeze()
3、声明各种水果类,实现水果接口,并重写squeeze();
4、在src下,建立配置文件:config.properties,并在配置文件中配上fruitName=xxx(其中xx为某种水果的全类名)
5、在FruitTest测试类中,
(1)读取配置文件,获取水果类名,并用反射创建水果对象,
(2)创建榨汁机对象,并调用run()方法
接口声明
/** * ClassName: Fruit * Package: PACKAGE_NAME * Description: * * @Author tcw * @Create 2023-05-25 10:07 * @Version 1.0 */ public interface Fruit { /** * 水果榨汁的方法 */ void squeeze(); }
水果类
/** * ClassName: Apple * Package: PACKAGE_NAME * Description: * * @Author tcw * @Create 2023-05-25 10:09 * @Version 1.0 */ public class Apple implements Fruit{ @Override public void squeeze() { System.out.println("苹果汁..."); } } public class Banana implements Fruit{ @Override public void squeeze() { System.out.println("香蕉汁...."); } } public class Orange implements Fruit{ @Override public void squeeze() { System.out.println("橘子汁..."); } }
榨汁机类
public class Juicer { /** * 使用需要进行榨汁的水果榨汁 * * @param fruit 水果 */ public static void run(Fruit fruit) { fruit.squeeze(); // 榨汁 } }
配置文件
fruitName=Apple
测试
/** * ClassName: FruitTest * Package: PACKAGE_NAME * Description: * * @Author tcw * @Create 2023-05-25 10:14 * @Version 1.0 */ public class FruitTest { public static void main(String[] args) throws Exception { // 利用类加载器加载配置文件 ClassLoader classLoader = FruitTest.class.getClassLoader(); InputStream resource = classLoader.getResourceAsStream("config.properties"); // 默认从src下开始查找文件 // 使用Properties对象加载文件中的内容 Properties properties = new Properties(); properties.load(resource); // 获取需要进行榨汁的水果(获取全类名) String fruitName = (String) properties.get("fruitName"); // 利用反射创建相应水果的实例对象 Class<?> fruitClass = Class.forName(fruitName); // 加载类 Fruit fruit = (Fruit) fruitClass.getDeclaredConstructor().newInstance(); // 虽然不知道是什么水果但是一定是水果 // 调用榨汁机榨汁方法进行榨汁 Juicer.run(fruit); } }