我是南城余!阿里云开发者平台专家博士证书获得者!
欢迎关注我的博客!一同成长!
一名从事运维开发的worker,记录分享学习。
专注于AI,运维开发,windows Linux 系统领域的分享!
本章节对应知识库
https://www.yuque.com/nanchengcyu/java
本内容来自尚硅谷课程,此处在知识库做了个人理解
————————————————
4. 反射的基本应用
有了Class对象,能做什么?
4.1 应用1:创建运行时类的对象
这是反射机制应用最多的地方。创建运行时类的对象有两种方式:
方式1:直接调用Class对象的newInstance()方法
要 求: 1)类必须有一个无参数的构造器。2)类的构造器的访问权限需要足够。
方式2:通过获取构造器对象来进行实例化
方式一的步骤:
1)获取该类型的Class对象 2)调用Class对象的newInstance()方法创建对象
方式二的步骤:
1)通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器
2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
3)通过Constructor实例化对象。
如果构造器的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)
示例代码:
package com.atguigu.reflect; import org.junit.Test; import java.lang.reflect.Constructor; public class TestCreateObject { @Test public void test1() throws Exception{ // AtGuiguClass obj = new AtGuiguClass();//编译期间无法创建 Class<?> clazz = Class.forName("com.atguigu.ext.demo.AtGuiguClass"); //clazz代表com.atguigu.ext.demo.AtGuiguClass类型 //clazz.newInstance()创建的就是AtGuiguClass的对象 Object obj = clazz.newInstance(); System.out.println(obj); } @Test public void test2()throws Exception{ Class<?> clazz = Class.forName("com.atguigu.ext.demo.AtGuiguDemo"); //java.lang.InstantiationException: com.atguigu.ext.demo.AtGuiguDemo //Caused by: java.lang.NoSuchMethodException: com.atguigu.ext.demo.AtGuiguDemo.<init>() //即说明AtGuiguDemo没有无参构造,就没有无参实例初始化方法<init> Object stu = clazz.newInstance(); System.out.println(stu); } @Test public void test3()throws Exception{ //(1)获取Class对象 Class<?> clazz = Class.forName("com.atguigu.ext.demo.AtGuiguDemo"); /* * 获取AtGuiguDemo类型中的有参构造 * 如果构造器有多个,我们通常是根据形参【类型】列表来获取指定的一个构造器的 * 例如:public AtGuiguDemo(String title, int num) */ //(2)获取构造器对象 Constructor<?> constructor = clazz.getDeclaredConstructor(String.class,int.class); //(3)创建实例对象 // T newInstance(Object... initargs) 这个Object...是在创建对象时,给有参构造的实参列表 Object obj = constructor.newInstance("尚硅谷",2022); System.out.println(obj); } }
4.2 应用2:获取运行时类的完整结构
可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)。
4.2.1 相关API
//1.实现的全部接口 public Class<?>[] getInterfaces() //确定此对象所表示的类或接口实现的接口。 //2.所继承的父类 public Class<? Super T> getSuperclass() //返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。 //3.全部的构造器 public Constructor<T>[] getConstructors() //返回此 Class 对象所表示的类的所有public构造方法。 public Constructor<T>[] getDeclaredConstructors() //返回此 Class 对象表示的类声明的所有构造方法。 //Constructor类中: //取得修饰符: public int getModifiers(); //取得方法名称: public String getName(); //取得参数的类型: public Class<?>[] getParameterTypes(); //4.全部的方法 public Method[] getDeclaredMethods() //返回此Class对象所表示的类或接口的全部方法 public Method[] getMethods() //返回此Class对象所表示的类或接口的public的方法 //Method类中: public Class<?> getReturnType() //取得全部的返回值 public Class<?>[] getParameterTypes() //取得全部的参数 public int getModifiers() //取得修饰符 public Class<?>[] getExceptionTypes() //取得异常信息 //5.全部的Field public Field[] getFields() //返回此Class对象所表示的类或接口的public的Field。 public Field[] getDeclaredFields() //返回此Class对象所表示的类或接口的全部Field。 //Field方法中: public int getModifiers() //以整数形式返回此Field的修饰符 public Class<?> getType() //得到Field的属性类型 public String getName() //返回Field的名称。 //6. Annotation相关 get Annotation(Class<T> annotationClass) getDeclaredAnnotations() //7.泛型相关 //获取父类泛型类型: Type getGenericSuperclass() //泛型类型:ParameterizedType //获取实际的泛型类型参数数组: getActualTypeArguments() //8.类所在的包 Package getPackage()
4.2.2 获取所有的属性及相关细节
package com.atguigu.java2; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import org.junit.Test; import com.atguigu.java1.Person; public class FieldTest { @Test public void test1(){ Class clazz = Person.class; //getFields():获取到运行时类本身及其所有的父类中声明为public权限的属性 // Field[] fields = clazz.getFields(); // // for(Field f : fields){ // System.out.println(f); // } //getDeclaredFields():获取当前运行时类中声明的所有属性 Field[] declaredFields = clazz.getDeclaredFields(); for(Field f : declaredFields){ System.out.println(f); } } //权限修饰符 变量类型 变量名 @Test public void test2(){ Class clazz = Person.class; Field[] declaredFields = clazz.getDeclaredFields(); for(Field f : declaredFields){ //1.权限修饰符 /* * 0x是十六进制 * PUBLIC = 0x00000001; 1 1 * PRIVATE = 0x00000002; 2 10 * PROTECTED = 0x00000004; 4 100 * STATIC = 0x00000008; 8 1000 * FINAL = 0x00000010; 16 10000 * ... * * 设计的理念,就是用二进制的某一位是1,来代表一种修饰符,整个二进制中只有一位是1,其余都是0 * * mod = 17 0x00000011 * if ((mod & PUBLIC) != 0) 说明修饰符中有public * if ((mod & FINAL) != 0) 说明修饰符中有final */ int modifier = f.getModifiers(); System.out.print(Modifier.toString(modifier) + "\t"); // //2.数据类型 Class type = f.getType(); System.out.print(type.getName() + "\t"); // // //3.变量名 String fName = f.getName(); System.out.print(fName); // System.out.println(); } } }
4.2.3 获取所有的方法及相关细节
package com.atguigu.java2; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import org.junit.Test; import com.atguigu.java1.Person; public class MethodTest { @Test public void test1() { Class clazz = Person.class; // getMethods():获取到运行时类本身及其所有的父类中声明为public权限的方法 // Method[] methods = clazz.getMethods(); // // for(Method m : methods){ // System.out.println(m); // } // getDeclaredMethods():获取当前运行时类中声明的所有方法 Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method m : declaredMethods) { System.out.println(m); } // } // 注解信息 // 权限修饰符 返回值类型 方法名(形参类型1 参数1,形参类型2 参数2,...) throws 异常类型1,...{} @Test public void test2() { Class clazz = Person.class; Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method m : declaredMethods) { // 1.获取方法声明的注解 Annotation[] annos = m.getAnnotations(); for (Annotation a : annos) { System.out.println(a); } // 2.权限修饰符 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(); } } }
4.2.4 获取其他结构(构造器、父类、接口、包、注解等)
package com.atguigu.java2; import com.atguigu.java1.Person; import org.junit.Test; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; /** * @author 尚硅谷-宋红康 * @create 2020 下午 2:47 */ public class OtherTest { /* 获取当前类中的所有的构造器 */ @Test public void test1(){ Class clazz = Person.class; Constructor[] cons = clazz.getDeclaredConstructors(); for(Constructor c :cons){ System.out.println(c); } } /* 获取运行时类的父类 */ @Test public void test2(){ Class clazz = Person.class; Class superclass = clazz.getSuperclass(); System.out.println(superclass);//class com.atguigu.java1.Creature } /* 获取运行时类的所在的包 */ @Test public void test3(){ Class clazz = Person.class; Package pack = clazz.getPackage(); System.out.println(pack); } /* 获取运行时类的注解 */ @Test public void test4(){ Class clazz = Person.class; Annotation[] annos = clazz.getAnnotations(); for (Annotation anno : annos) { System.out.println(anno); } } /* 获取运行时类所实现的接口 */ @Test public void test5(){ Class clazz = Person.class; Class[] interfaces = clazz.getInterfaces(); for (Class anInterface : interfaces) { System.out.println(anInterface); } } /* 获取运行时类的带泛型的父类 */ @Test public void test6(){ Class clazz = Person.class; Type genericSuperclass = clazz.getGenericSuperclass(); System.out.println(genericSuperclass);//com.atguigu.java1.Creature<java.lang.String> } }
4.2.5 获取泛型父类信息(选讲)
示例代码获取泛型父类信息:
/* Type: * (1)Class * (2)ParameterizedType * 例如:Father<String,Integer> * ArrayList<String> * (3)TypeVariable * 例如:T,U,E,K,V * (4)WildcardType * 例如: * ArrayList<?> * ArrayList<? super 下限> * ArrayList<? extends 上限> * (5)GenericArrayType * 例如:T[] * */ public class TestGeneric { public static void main(String[] args) { //需求:在运行时,获取Son类型的泛型父类的泛型实参<String,Integer> //(1)还是先获取Class对象 Class clazz = Son.class;//四种形式任意一种都可以 //(2)获取泛型父类 // Class sc = clazz.getSuperclass(); // System.out.println(sc); /* * getSuperclass()只能得到父类名,无法得到父类的泛型实参列表 */ Type type = clazz.getGenericSuperclass(); // Father<String,Integer>属于ParameterizedType ParameterizedType pt = (ParameterizedType) type; //(3)获取泛型父类的泛型实参列表 Type[] typeArray = pt.getActualTypeArguments(); for (Type type2 : typeArray) { System.out.println(type2); } } } //泛型形参:<T,U> class Father<T,U>{ } //泛型实参:<String,Integer> class Son extends Father<String,Integer>{ }
4.2.6 获取内部类或外部类信息(选讲)
public Class<?>[] getClasses():返回所有公共内部类和内部接口。包括从超类继承的公共类和接口成员以及该类声明的公共类和接口成员。
public Class<?>[] getDeclaredClasses():返回 Class 对象的一个数组,这些对象反映声明为此 Class 对象所表示的类的成员的所有类和接口。包括该类所声明的公共、保护、默认(包)访问及私有类和接口,但不包括继承的类和接口。
public Class<?> getDeclaringClass():如果此 Class 对象所表示的类或接口是一个内部类或内部接口,则返回它的外部类或外部接口,否则返回null。
Class<?> getEnclosingClass() :返回某个内部类的外部类
@Test public void test5(){ Class<?> clazz = Map.class; Class<?>[] inners = clazz.getDeclaredClasses(); for (Class<?> inner : inners) { System.out.println(inner); } Class<?> ec = Map.Entry.class; Class<?> outer = ec.getDeclaringClass(); System.out.println(outer); }
4.2.7 小 结
- 在实际的操作中,取得类的信息的操作代码,并不会经常开发。
- 一定要熟悉java.lang.reflect包的作用,反射机制。
4.3 应用3:调用运行时类的指定结构
4.3.1 调用指定的属性
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。
(1)获取该类型的Class对象
Class clazz = Class.forName(“包.类名”);
(2)获取属性对象
Field field = clazz.getDeclaredField(“属性名”);
(3)如果属性的权限修饰符不是public,那么需要设置属性可访问
field.setAccessible(true);
(4)创建实例对象:如果操作的是非静态属性,需要创建实例对象
Object obj = clazz.newInstance(); //有公共的无参构造
Object obj = 构造器对象.newInstance(实参…);//通过特定构造器对象创建实例对象
(4)设置指定对象obj上此Field的属性内容
field.set(obj,“属性值”);
如果操作静态变量,那么实例对象可以省略,用null表示
(5)取得指定对象obj上此Field的属性内容
Object value = field.get(obj);
如果操作静态变量,那么实例对象可以省略,用null表示
示例代码:
package com.atguigu.reflect; public class Student { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + '}'; } }
package com.atguigu.reflect; import java.lang.reflect.Field; public class TestField { public static void main(String[] args)throws Exception { //1、获取Student的Class对象 Class clazz = Class.forName("com.atguigu.reflect.Student"); //2、获取属性对象,例如:id属性 Field idField = clazz.getDeclaredField("id"); //3、如果id是私有的等在当前类中不可访问access的,我们需要做如下操作 idField.setAccessible(true); //4、创建实例对象,即,创建Student对象 Object stu = clazz.newInstance(); //5、获取属性值 /* * 以前:int 变量= 学生对象.getId() * 现在:Object id属性对象.get(学生对象) */ Object value = idField.get(stu); System.out.println("id = "+ value); //6、设置属性值 /* * 以前:学生对象.setId(值) * 现在:id属性对象.set(学生对象,值) */ idField.set(stu, 2); value = idField.get(stu); System.out.println("id = "+ value); } }
关于setAccessible方法的使用:
- Method和Field、Constructor对象都有setAccessible()方法。
- setAccessible启动和禁用访问安全检查的开关。
- 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
- 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
- 使得原本无法访问的私有成员也可以访问
- 参数值为false则指示反射的对象应该实施Java语言访问检查。
4.3.2 调用指定的方法
(1)获取该类型的Class对象
Class clazz = Class.forName(“包.类名”);
(2)获取方法对象
Method method = clazz.getDeclaredMethod(“方法名”,方法的形参类型列表);
(3)创建实例对象
Object obj = clazz.newInstance();
(4)调用方法
Object result = method.invoke(obj, 方法的实参值列表);
如果方法的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)
如果方法是静态方法,实例对象也可以省略,用null代替
示例代码:
package com.atguigu.reflect; import org.junit.Test; import java.lang.reflect.Method; public class TestMethod { @Test public void test()throws Exception { // 1、获取Student的Class对象 Class<?> clazz = Class.forName("com.atguigu.reflect.Student"); //2、获取方法对象 /* * 在一个类中,唯一定位到一个方法,需要:(1)方法名(2)形参列表,因为方法可能重载 * * 例如:void setName(String name) */ Method setNameMethod = clazz.getDeclaredMethod("setName", String.class); //3、创建实例对象 Object stu = clazz.newInstance(); //4、调用方法 /* * 以前:学生对象.setName(值) * 现在:方法对象.invoke(学生对象,值) */ Object setNameMethodReturnValue = setNameMethod.invoke(stu, "张三"); System.out.println("stu = " + stu); //setName方法返回值类型void,没有返回值,所以setNameMethodReturnValue为null System.out.println("setNameMethodReturnValue = " + setNameMethodReturnValue); Method getNameMethod = clazz.getDeclaredMethod("getName"); Object getNameMethodReturnValue = getNameMethod.invoke(stu); //getName方法返回值类型String,有返回值,getNameMethod.invoke的返回值就是getName方法的返回值 System.out.println("getNameMethodReturnValue = " + getNameMethodReturnValue);//张三 } @Test public void test02()throws Exception{ Class<?> clazz = Class.forName("com.atguigu.ext.demo.AtGuiguClass"); Method printInfoMethod = clazz.getMethod("printInfo", String.class); //printInfo方法是静态方法 printInfoMethod.invoke(null,"尚硅谷"); } }
4.3.3 练习
读取user.properties文件中的数据,通过反射完成User类对象的创建及对应方法的调用。
配置文件:user.properties
className:com.atguigu.bean.User methodName:show
User.java文件:
package com.atguigu.bean; /** * @author 尚硅谷-宋红康 * @create 18:41 */ public class User { private String name; public User() { } public User(String name) { this.name = name; } public void show(){ System.out.println("我是一个脉脉平台的用户"); } }
ReflectTest.java文件:
package com.atguigu.java4; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.util.Properties; /** * @author 尚硅谷-宋红康 * @create 18:43 */ public class ReflectTest { @Test public void test() throws Exception { //1.创建Properties对象 Properties pro = new Properties(); //2.加载配置文件,转换为一个集合 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); InputStream is = classLoader.getResourceAsStream("user.properties"); pro.load(is); //3.获取配置文件中定义的数据 String className = pro.getProperty("className"); String methodName = pro.getProperty("methodName"); //4.加载该类进内存 Class clazz = Class.forName(className); //5.创建对象 Object instance = clazz.newInstance(); //6.获取方法对象 Method showMethod = clazz.getMethod(methodName); //7.执行方法 showMethod.invoke(instance); } }
5. 应用4:读取注解信息
一个完整的注解应该包含三个部分:
(1)声明
(2)使用
(3)读取
5.1 声明自定义注解
package com.atguigu.annotation; import java.lang.annotation.*; @Inherited @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Table { String value(); }
package com.atguigu.annotation; import java.lang.annotation.*; @Inherited @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Column { String columnName(); String columnType(); }
- 自定义注解可以通过四个元注解@Retention,@Target,@Inherited,@Documented,分别说明它的声明周期,使用位置,是否被继承,是否被生成到API文档中。
- Annotation 的成员在 Annotation 定义中以无参数有返回值的抽象方法的形式来声明,我们又称为配置参数。返回值类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组
- 可以使用 default 关键字为抽象方法指定默认返回值
- 如果定义的注解含有抽象方法,那么使用时必须指定返回值,除非它有默认值。格式是“方法名 = 返回值”,如果只有一个抽象方法需要赋值,且方法名为value,可以省略“value=”,所以如果注解只有一个抽象方法成员,建议使用方法名value。
5.2 使用自定义注解
package com.atguigu.annotation; @Table("t_stu") public class Student { @Column(columnName = "sid",columnType = "int") private int id; @Column(columnName = "sname",columnType = "varchar(20)") private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + '}'; } }
5.3 读取和处理自定义注解
自定义注解必须配上注解的信息处理流程才有意义。
我们自己定义的注解,只能使用反射的代码读取。所以自定义注解的声明周期必须是RetentionPolicy.RUNTIME。
package com.atguigu.annotation; import java.lang.reflect.Field; public class TestAnnotation { public static void main(String[] args) { Class studentClass = Student.class; Table tableAnnotation = (Table) studentClass.getAnnotation(Table.class); String tableName = ""; if(tableAnnotation != null){ tableName = tableAnnotation.value(); } Field[] declaredFields = studentClass.getDeclaredFields(); String[] columns = new String[declaredFields.length]; int index = 0; for (Field declaredField : declaredFields) { Column column = declaredField.getAnnotation(Column.class); if(column!= null) { columns[index++] = column.columnName(); } } String sql = "select "; for (int i=0; i<index; i++) { sql += columns[i]; if(i<index-1){ sql += ","; } } sql += " from " + tableName; System.out.println("sql = " + sql); } }
6. 体会反射的动态性
体会1:
public class ReflectionTest { //体会反射的动态性:动态的创建给定字符串对应的类的对象 public <T> T getInstance(String className) throws Exception { Class clazz = Class.forName(className); Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); return (T) constructor.newInstance(); } @Test public void test1() throws Exception { String className = "com.atguigu.java1.Person"; Person p1 = getInstance(className); System.out.println(p1); } }
体会2:
public class ReflectionTest { //体会反射的动态性:动态的创建指定字符串对应类的对象,并调用指定的方法 public Object invoke(String className,String methodName) throws Exception { Class clazz = Class.forName(className); Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); //动态的创建指定字符串对应类的对象 Object obj = constructor.newInstance(); Method method = clazz.getDeclaredMethod(methodName); method.setAccessible(true); return method.invoke(obj); } @Test public void test2() throws Exception { String info = (String) invoke("com.atguigu.java1.Person", "show"); System.out.println("返回值为:" + info); } }
体会3:
public class ReflectionTest { @Test public void test1() throws Exception { //1.加载配置文件,并获取指定的fruitName值 Properties pros = new Properties(); InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("config.properties"); pros.load(is); String fruitStr = pros.getProperty("fruitName"); //2.创建指定全类名对应类的实例 Class clazz = Class.forName(fruitStr); Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); Fruit fruit = (Fruit) constructor.newInstance(); //3. 调用相关方法,进行测试 Juicer juicer = new Juicer(); juicer.run(fruit); } } interface Fruit { public void squeeze(); } class Apple implements Fruit { public void squeeze() { System.out.println("榨出一杯苹果汁儿"); } } class Orange implements Fruit { public void squeeze() { System.out.println("榨出一杯桔子汁儿"); } } class Juicer { public void run(Fruit f) { f.squeeze(); } }
其中,配置文件【config.properties】存放在当前Module的src下
com.atguigu.java1.Orange