【Java进阶篇】——反射机制
一、反射的概念
1.1 反射出现的背景
- Java程序中,所有对象都有两种类型:编译时类型和运行时类型,而很多时候对象的编译时类型和运行时类型不一致
Object obj = new String("hello")
、obj.getClass();
如果某些变量或形参的声明类型是Object类型,但程序却需要调用该对象运行时类型的方法,而不是编译时中的方法,那么如何解决呢?
-
方案一:在编译和运行时都完全知道类型的具体信息,在这种情况下,我们可以直接使用 instanceof 运算符进行判断,再利用强制类型转换符将其转换成运行时类型的变量即可
- instanceof 是用来判断左边的对象是否为它右边类的实例
num instanceof int
- instanceof 是用来判断左边的对象是否为它右边类的实例
-
方案二:编译时根本无法预知该对象和类的真实信息,程序只能依靠运行时信息来发现该对象和类的真实信息,这种情况就必须使用反射机制
1.2 反射概述
-
反射(Reflection):被视为动态语言的关键,反射机制允许程序在运行期间借助于Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
-
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象【一个类只有一个Class对象】
-
这个对象包含完整的类的结构信息
-
在内存加载的过程中,可以参考下面的图
-
1.3 反射机制提供的功能
- 在运行时判断任意一个对象所属类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
1.4 反射相关的API
- java.lang.Class: 代表一个类
- java.lang.reflect.Method: 代表类的方法
- java.lang.reflect.Field: 代表类的成员变量
- java.lang.reflect.Constructor: 代表类的构造器
1.5 反射机制的优缺点
-
优点:
- 提高了Java程序的灵活性和扩展性,降低了耦合度,提高了自适应能力
- 允许程序创建和控制任何类的对象,无需提前硬编码目标类
-
缺点:
- 反射的性能较低
- 反射机制主要应用在堆灵活性和扩展性要求较高的系统框架上
- 反射会模糊程序内部逻辑,可读性较差
- 反射的性能较低
二、理解Class类并获取Class实例
- 要想要深度剖析一个类,首先要获得这个类的Class对象,然后调用相应的API分析
- java.lang.Class、java.lang.reflect.* >> Class对象是反射的根源
2.1 理解Class类
🌔 1、在理论上我们如何理解Class呢?
- 在Object类中定义了以下的方法,此方法将被所有子类继承:
public final Class getClass()
- 我们可以看出这个方法的返回值是一个Class类,这个类是Java反射的源头,可以通过对象反射得到类的名称
- 对于每个类而言,JRR都为其保留一个不变的Class类型的对象,其中包含了特定某个结构的信息- Class 本身也是一个类- Class 对象只能由系统建立对象- 一个加载的类在JVM中只会有一个Class实例- 一个Class对象对应的是 ==一个加载到JVM中的一个.class文件==- 每个类的实例都会记得自己是由哪个Class实例所生成- 通过Class可以完整地得到一个类中的所有被加载的结构- Class类是Reflection的根源,针对任何想要动态加载、运行的类,只能首先获得相应的Class对象
🌔 2、在内存结构上如何理解Class?
说明:上图中字符串常量池在JDK6中存储在方法区;JDK7及以后,存储在堆空间
2.2 获取Class类实例的方法
🌔 1、编译期间已知类型
- 如果知道具体的类,我们可以直接通过该类的 class 属性获取,该方法安全可靠、程序性能最高
Class clazz = String.class;
🌔 2、获取对象的运行时类型
-
已知某个类的实例,调用该实例的getClass()方法获取Class对象
Person p = new Person(); Class clazz = p.getClass();
🌔 3、可以获取编译期间的未知类型
-
已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出 ClassNotFoundException
Class clazz = Class.forName("java.lang.String");
🌔 4、可以使用加载类的方式
-
用系统类加载对象或自定义加载器对象加载指定路径下的类型
ClassLoader c1 = this.getClass().getClassLoader(); Class clazz = c1.loadClass("类的全限定类名");
🌔 5、通过综合的案例演示如何获取Class实例
package com.zwh.shangguigu.reflection_;/*** @author Bonbons* @version 1.0*/
public class GetClassObject {public static void main(String[] args) {//知道编译时类型Class c1 = GetClassObject.class;//获取对象的运行时类型GetClassObject obj = new GetClassObject();Class c2 = obj.getClass();Class c3 = null;Class c4 = null;try{//通过指定的类名去查找、创建Class的实例c3 = Class.forName("com.zwh.shangguigu.reflection_.GetClassObject");//通过类加载器去获得Class的实例c4 = ClassLoader.getSystemClassLoader().loadClass("com.zwh.shangguigu.reflection_.GetClassObject");}catch (ClassNotFoundException e){e.printStackTrace();}//输出四个Class实例System.out.println("c1 = " + c1);System.out.println("c2 = " + c2);System.out.println("c3 = " + c3);System.out.println("c4 = " + c4);//通过Class.class方法获取到的最可靠,所以用它与其他几个比较System.out.println(c1 == c2);System.out.println(c1 == c3);System.out.println(c1 == c4);}
}
2.3 哪些类型可以有Class对象
- Java中的所有类型都可以用Class对象 【这个Class对象就是类实例创建后在JVM中的.class文件】
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;
Class c6 = Override.class;
Class c7 = int.class;
Class c8 = void.class;
Class c9 = Class.class;int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要元素类型与维度一样,就是同一个Class
System.out.println(c10 == c11);
2.4 Class类的常用方法
方法名 | 功能 |
---|---|
static Class forName(String name) | 返回指定类名name的Class对象 |
Object newInstance() | 调用缺省构造函数,返回该Class对象的一个实例 |
getName() | 返回此Class对象所表示的实体名称【类、接口、数组等】 |
Class getSuperClass() | 返回当前Class对象的父类的Class对象 |
Class [] getInterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Class getSuperclass() | 返回表示此Class所表示实体的超类的Class |
Constructor [] getConstructors() | 返回一个包含某个Constructor对象的数组 |
Field[] getDeclaredFields() | 返回Field对象的一个数组 |
Method getMethod(String name, Class …paramTypes) | 返回一个Method对象,此对象的参数类型为paramType |
//test4.Person是test4包下的Person类
String str = "test4.Person";
//获取这个类的Class实例
Class clazz = Class.forName(str);
//根据Class创建对象
Object obj = clazz.newInstance();
//获取特定的一个属性
Field field = clazz.getField("name");
//将obj这个对象的这个name属性设置为 "Peter"
field.set(obj, "Peter");
//获取obj的name属性值
Object name = field.get(obj);
//输出name属性值,看是否设定成功
System.out.println(name);
三、类的加载与ClassLoader的理解
3.1 类的生命周期
- 类在内存中完整的生命周期:加载 >> 使用 >> 卸载
- 加载:装载 >> 链接 >> 初始化三个阶段
3.2 类的加载过程
- 当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、链接初始化三个步骤对该类进行初始化【统称类加载 】
- 我们展开论述一下类加载包含的三个子过程
- 装载(Loading):类加载器将类的class文件读入内存,并为之创建一个 java.lang.Class对象
- 链接(Linking):
- 验证(Verify):确保加载的类信息符合JVM规范
- 准备 (Prepare):正式为类变量(static)分配内存并设置类变量默认初始值阶段,这些内存都将在方法区中进行分配
- 解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
- 初始化(Initization):
- 执行类构造器
<clinit>()
方法的过程- 类构造器
<clinit>()
方法:是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的 - 类构造器是构造类信息的,不是构造该类对象的构造器
- 类构造器
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
- 虚拟机会保证一个类的
<clinit>()
方法,在多线程环境中被正确加锁和同步
- 执行类构造器
3.3 类加载器
🌔 1、类加载器有什么作用呢?
- 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的 java.lang.Class 对象,作为方法区中类数据的访问入口
- 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载一段时间(缓存),JVM的垃圾回收机制可以回收这些Class对象
🌔 2、以JDK8为例介绍类加载器的分类
-
JVM 支持两种类型的类加载器:
- 引导类加载器(Bootstrap ClassLoader)
- 自定义类加载器(User-Defined ClassLoader):将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
-
常见的类加载器结构如图所示:
(1)启动类加载器(引导类加载器,Bootstrap ClassLoader)
- 这个类加载使用 C/C++ 语言实现的,嵌套在JVM内部,获取他的对象往往返回 null
- 它用来加载Java的核心库,用于提供JVM自身需要的类
- JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容
- 并不继承在 java.lang.ClassLoader,没有父加载器
- 出于安全考虑,Bootstrap启动类加载器只加载包名为 java、javax、sun开头的类
- 是加载扩展类和应用程序类加载器的父类加载器
(2)扩展类加载器(Extension ClassLoader)
- Java语言编写,由 sun.misc.Launcher&ExtClassLoader实现
- 继承与ClassLoader类,父加载器是启动类加载器
- 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的 jre/lib/ext 子目录下加载类库
- 如果用户创建的JRE放在此目录下,也会自动由扩展类加载器加载
(3)应用程序类加载器(系统类加载器, AppClassLoader)
- Java语言编写,由 sun.misc.Luncher&AppClassLoader实现
- 继承与ClassLoader类,是扩展类加载器的子类
- 它负责加载环境变量 classpath 或系统属性 java.class.path指定路径下的类库
- 应用程序中的类加载器默认是系统类加载器
- 它是用户自定义类加载器的默认父加载器
- 通过ClassLoader的getSystemClassLoader()方法可以获取到该类加载器
(4)用户自定义类加载器(了解)
- 在Java日常应用程序开发中,类的加载几乎是由上述3中类加载器互相配合执行的。必要情况下我们可以自定义类加载器,来定义类的加载方式
- 可以实现类库的动态加载,加载源可以是本地的JAR包,也可以是网络上的远程资源
- 可以实现应用隔离(隔离不同的组件模块),通常需要继承ClassLoader
🌔 3、如何查看某个类的类加载器对象呢?
获取默认的系统类加载器
ClassLoader classloader = ClassLoader.getSystemClassLoader();
查看某个类时哪个类加载器加载的
ClassLoader classloader = Class.forName("exer2.ClassloaderDemo").getClassLoader();
//如果是根加载器加载的类,得到的时null
ClassLoader classloader1 = Class.forName("java.lang.Object).getClassLoader();
获取某个类加载器的父加载器
ClassLoader parneClassloader = classloader.getParent();
- 通过代码演示如何获取加载器:
package com.zwh.shangguigu.reflection_;/*** @author Bonbons* @version 1.0*/
public class ClassLoaderTest {public static void main(String[] args) {//获取默认系统类加载器ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();System.out.println("默认的系统类加载器: " + systemClassLoader);//String类加载器ClassLoader c1 = String.class.getClassLoader();System.out.println("加载String类的类加载器: " + c1);//通过forName获取Class实例,然后进一步获取其类加载器try{ClassLoader c2 = Class.forName("sun.util.resources.cldr.zh.TimeZoneNames_zh").getClassLoader();System.out.println("sun.util.resources.cldr.zh.TimeZoneNames_zh类的类加载器: " + c2);}catch (ClassNotFoundException e){e.printStackTrace();}//获取当前类的类加载器ClassLoader c3 = ClassLoaderTest.class.getClassLoader();System.out.println("加载当前类的类加载器: " + c3);//当前类加载器的父类的类加载器ClassLoader c4 = c3.getParent();System.out.println("当前类的父类的类加载器: " + c4);//当前类父类的父类ClassLoader c5 = c4.getParent();System.out.println("当前类父类的父类的类加载器: " + c5);}
}
🌔 4、如何使用ClassLoader获取流呢?
- getResourceAsStream(String str):获取类路径下的指定文件的输入流
InputStream in = null; in = this.getClass().getClassLoader().getResourceAsStream("exer2\\test.properties"); System.out.println(in);
通过代码演示类加载器获取流:
package com.zwh.shangguigu.reflection_;import jdk.internal.util.xml.impl.Input;import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;/*** @author Bonbons* @version 1.0*/
public class ClassLoaderGetStreamTest {public static void main(String[] args) throws IOException {//创建保存流的集合Properties pros = new Properties();//方法1:此时默认的相对路径是当前的moduleFileInputStream is = new FileInputStream("info.properties");FileInputStream is2 = new FileInputStream("src//info1.properties");//方法2:使用类的加载器,此时默认的相对路径是当前module的src目录InputStream is3 = ClassLoader.getSystemClassLoader().getResourceAsStream("info1.properties");pros.load(is);pros.load(is2);pros.load(is3);//获取配置文件中的信息String name = pros.getProperty("name");String password = pros.getProperty("password");System.out.println("name = " + name + ", password = " + password);}
}
四、反射的基本应用
4.1 创建运行时类的对象
🌔 1、直接调用Class对象的newInstance() 方法
- 要求:
- 必须有一个无参数的构造器
- 类构造器的访问权限足够
- 步骤:
- 获取该类型的Class对象
- 调用Class对象的newInstance()方法创建对象
🌔 2、获取构造器对象来进行实例化
-
步骤:
- 通过Class类的getDeclaredConstructor(Class parameterTypes) 取得本类的指定形参类型的构造器
- 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数
- 通过Constructor实例化对象
-
如果构造器的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)
package com.atguigu.reflect;import org.junit.Test;import java.lang.reflect.Constructor;public class TestCreateObject {@Testpublic 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);}@Testpublic 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);}@Testpublic 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 获取运行时类的完整结构
- 可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包含泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)
接下来重点介绍各种API和演示:
🌔 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()
🌔 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 {@Testpublic 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);}}//权限修饰符 变量类型 变量名@Testpublic 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();}}
}
🌔 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 {@Testpublic 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,...{}@Testpublic 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、获取其他结构(构造器、父类、接口、包、注解等)
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 {/*获取当前类中的所有的构造器*/@Testpublic void test1(){Class clazz = Person.class;Constructor[] cons = clazz.getDeclaredConstructors();for(Constructor c :cons){System.out.println(c);}}/*获取运行时类的父类*/@Testpublic void test2(){Class clazz = Person.class;Class superclass = clazz.getSuperclass();System.out.println(superclass);//class com.atguigu.java1.Creature}/*获取运行时类的所在的包*/@Testpublic void test3(){Class clazz = Person.class;Package pack = clazz.getPackage();System.out.println(pack);}/*获取运行时类的注解*/@Testpublic void test4(){Class clazz = Person.class;Annotation[] annos = clazz.getAnnotations();for (Annotation anno : annos) {System.out.println(anno);}}/*获取运行时类所实现的接口*/@Testpublic void test5(){Class clazz = Person.class;Class[] interfaces = clazz.getInterfaces();for (Class anInterface : interfaces) {System.out.println(anInterface);}}/*获取运行时类的带泛型的父类*/@Testpublic void test6(){Class clazz = Person.class;Type genericSuperclass = clazz.getGenericSuperclass();System.out.println(genericSuperclass);//com.atguigu.java1.Creature<java.lang.String>}
}
🌔 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>属于ParameterizedTypeParameterizedType 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>{}
🌔 6、获取内部类或外部类信息
public Class<?>[] getClasses():
返回所有公共内部类和内部接口【包括从超类继承的公共类和接口成员以及该声明的公共类和接口成员】public Class<?>[] getDeclaredClasses():
返回Class对象的一个数组,这些对象梵音声明为此Class对象所表示的类的成员的所有类和接口与、包括该类所声明的公共、保护、默认(包)访问及私有类和接口,但不包括继承的类和接口public Class<?> getDeclaringClass()
:如果此 Class 对象所表示的类或接口是一个内部类或内部接口,则返回它的外部类或外部接口,否则返回nullClass<?> getEnclosingClass()
:返回某个内部类的外部类
@Testpublic 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.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(实参…);
(5)设置指定对象obj上此Field的属性内容【就是给指定的对象设置field对应的属性值】
//如果是静态变量,可以将obj替换为null
field.set(obj, “属性值”);
(6)取得指定对象obj上此Field的属性内容
//如果操作静态变量,那么实例对象可以省略,用null表示
Object value = field.get(obj);
接下来用代码演示如何获取类中我们需要的指定结构
package com.zwh.shangguigu.reflection_;import java.lang.reflect.Field;/*** @author Bonbons* @version 1.0*/
public class FieldTest {public static void main(String[] args) throws Exception{//1、先获取到Class类Class clazz = Class.forName("com.zwh.shangguigu.reflection_.Student");//2、获取属性对象Field idField = clazz.getDeclaredField("id");//3、如果id是私有属性,并且在当前类中没有访问权限idField.setAccessible(true);//4、创建实例对象Object stu = clazz.newInstance();//5、获取属性值/** (1)正常情况下我们通过Student的对象获取属性值,调用getXxx()方法即可* (2)现在我们需要创建对应属性的对象,然后属性对象调用get()方法,将在内存中创建的Student对象作为参数传入* */Object value = idField.get(stu);System.out.println("id = " + value);//6、设置属性值/** 以前:学生对象.setId(属性值);* 现在:id属性对象.set(学生对象,值);* */idField.set(stu, 33);value = idField.get(stu);System.out.println("id = " + value);}}//创建一个Student类
class Student{private String name;private int id;public Student(){}public Student(String name, int id) {this.name = name;this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getId() {return id;}public void setId(int id) {this.id = id;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", id=" + id +'}';}
}
🌔 2、补充说明关于 setAccessible方法的使用
- Method、Field、Constructor对象都提供了这个方法
- setAccessible 是启用和禁用访问安全检查的开关
- 参数值为 true 则指示反射的对象在使用时应该取消Java语言访问检查
- 可以提高反射的效率 【特别是需要大量用反射的情况下】
- 可以使得本来无法访问的私有成员也可以访问
- 参数值为 false则指示反射的对象应该实施Java语言的访问检查
🌔 3、调用指定的方法
(1)获取该类型的Class对象
Class clazz = Class.forName("包.类名");
(2)获取方法对象
Method method = clazz.getDeclaredMethod("方法名", 方法的形参类型列表);
(3)创建实例对象
Object obj = class.newInstance();
(4)调用方法
Object result = method.invoke(obj, 方法的实参值列表);
同获取指定的属性值一样:
如果方法的权限修饰符修饰的范围不可见,可以调用
setAccessible(true);
跳过检查
如果方法是静态方法,实例对象可以省略,用 null 代替
package com.zwh.shangguigu.reflection_;import java.lang.reflect.Method;/*** @author Bonbons* @version 1.0*/
public class MethodTest {public static void main(String[] args) throws Exception{//1、获取Student的Class对象Class clazz = Class.forName("com.zwh.shangguigu.reflection_.Student");//2、创建我们需要的方法的对象Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);//3、创建Student的实例Object stu = clazz.newInstance();//4、调用方法Object setNameMethodReturnValue = setNameMethod.invoke(stu, "张三");System.out.println("stu = " + stu);//set方法都是void,所以下面输入的肯定是nullSystem.out.println("setNameMethodReturnValue = " + setNameMethodReturnValue);//5、为了演示调用静态方法,我在Student类中添加一个//接下来需要获取方法的对象,至于Class的对象就用上面的那个了Method printMethod = clazz.getDeclaredMethod("print", String.class);//调用方法,因为是静态方法,所以不用传入Student的实例printMethod.invoke(null, "我想打印一张照片");}
}
🌔 4、案例分析
- 读取user.properties文件中的数据,通过反射完成User类对象的创建及对应方法的调用
我们需要准备对应的实体类
package com.atguigu.bean;public class User {private String name;public User() {}public User(String name) {this.name = name;}public void show(){System.out.println("我是一个焦绿平台的用户");}
}
编写我们的反射文件 ReflectTest
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;public Class ReflectTest{public static void main(String [] args){//1、创建Properties对象Properties pro = new Properties();//2、加载配置文件,转换成一个集合(就是通过类加载器将数据转换成流)ClassLoader classLoader = ClassLoader.getSystemClassLoader();InputStream is = classLoader.getResourceAsStream("user.properties");pro.add(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(iinstance); }
}
五、读取注解信息
- 一个完整的注解应该包含三部分:声明、使用、读取
🌔 1、声明自定义注解
package com.zwh.shangguigu.reflection_;import java.lang.annotation.*;@Inherited
@Target(ElementType.TYPE)
@Rentention(RententionPolicy.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
🌔 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;}@Overridepublic String toString() {return "Student{" +"id=" + id +", name='" + name + '\'' +'}';}
}
🌔 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);}
}
六、体会反射的动态性
🌔 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();}@Testpublic 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);}@Testpublic void test2() throws Exception {String info = (String) invoke("com.atguigu.java1.Person", "show");System.out.println("返回值为:" + info);}
}
🌔 3、案例三
public class ReflectionTest {@Testpublic 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下
相关文章:

【Java进阶篇】——反射机制
一、反射的概念 1.1 反射出现的背景 Java程序中,所有对象都有两种类型:编译时类型和运行时类型,而很多时候对象的编译时类型和运行时类型不一致 Object obj new String("hello")、obj.getClass(); 如果某些变量或形参的声明类型…...

Oracle中含有recover 状态的数据文件环境中,做异机恢复
背景: 我们在一些恢复测试案例中,会经常遇到一些奇怪的问题,其中有的是源端数据文件不规范而导致恢复过程出错,比较常见的错误有: 数据文件名称重复(如:/oradata1/user01.dbf 和 /oradata2/us…...

图像识别模型
一、数据准备 首先要做一些数据准备方面的工作:一是把数据集切分为训练集和验证集, 二是转换为tfrecord 格式。在data_prepare/文件夹中提供了会用到的数据集和代码。首先要将自己的数据集切分为训练集和验证集,训练集用于训练模型…...

[零刻]EQ12 N100 迷你主机:从开箱到安装ESXi+虚拟机
开箱先上图:配置详情:EQ12采用了Intel最新推出的N100系列的处理,超低的功耗,以及出色的CPU性能用来做软路由或者是All in one 相当不错,CPU带有主动散热风扇,在长期运行下散热完全不用担心,性价…...

MongoDB基础
优质博客 IT-BLOG-CN 一、简介 MongoDB是一个强大的分布式文件存储的NoSQL数据库,天然支持高可用、分布式和灵活设计。由C编写,运行稳定,性能高。为WEB应用提供可扩展的高性能数据存储解决方案。主要解决关系型数据库数据量大,并…...

【Linux】Linux基本指令(下)
前言: 紧接上期【Linux】基本指令(上)的学习,今天我们继续学习基本指令操作,深入探讨指令的基本知识。 目录 (一)常用指令 👉more指令 👉less指令(重要&…...

基于uniapp+u-view开发小程序【技术点整理】
一、上传图片 1.实现效果: 2.具体代码: <template><view><view class"imgbox"><view>职业证书</view><!-- 上传图片 --><u-upload :fileList"fileList1" afterRead"afterRead"…...

投稿指南【NO.7】目标检测论文写作模板(初稿)
中文标题(名词性短语,少于20字,尽量不使用外文缩写词)张晓敏1,作者1,2***,作者2**,作者2*(通信作者右上标*)1中国科学院上海光学精密机械研究所空间激光传输与探测技术重…...

【绘图】比Matplotlib更强大:ProPlot
✅作者简介:在读博士,伪程序媛,人工智能领域学习者,深耕机器学习,交叉学科实践者,周更前沿文章解读,提供科研小工具,分享科研经验,欢迎交流!📌个人…...

经典七大比较排序算法 ·上
经典七大比较排序算法 上1 选择排序1.1 算法思想1.2 代码实现1.3 选择排序特性2 冒泡排序2.1 算法思想2.2 代码实现2.3 冒泡排序特性3 堆排序3.1 堆排序特性:4 快速排序4.1 算法思想4.2 代码实现4.3 快速排序特性5 归并排序5.1 算法思想5.2 代码实现5.3 归并排序特性…...

【网络安全工程师】从零基础到进阶,看这一篇就够了
学前感言 1.这是一条需要坚持的道路,如果你只有三分钟的热情那么可以放弃往下看了。 2.多练多想,不要离开了教程什么都不会,最好看完教程自己独立完成技术方面的开发。 3.有问题多google,baidu…我们往往都遇不到好心的大神,谁…...

素描-基础
# 如何练习排线第一次摸板子需要来回的排线,两点然后画一条线贯穿两点画直的去练 练线的定位叫做穿针引线法或者两点一线法 练完竖线练横线 按照这样去练顺畅 直线曲线的画法 直线可以按住shift键 练习勾线稿 把线稿打开降低透明度去勾线尽量一笔的去练不要压…...

Elasticsearch:高级数据类型介绍
在我之前的文章 “Elasticsearch:一些有趣的数据类型”,我已经介绍了一下很有趣的数据类型。在今天的文章中,我再进一步介绍一下高级的数据类型,虽然这里的数据类型可能和之前的一些数据类型有所重复。即便如此,我希望…...

Golang每日一练(leetDay0012)
目录 34. 查找元素首末位置 Find-first-and-last-position-of-element-in-sorted-array 🌟🌟 35. 搜索插入位置 Search Insert Position 🌟 36. 有效的数独 Valid Sudoku 🌟🌟 🌟 每日一练刷题专栏 …...

Web前端:6种基本的前端编程语言
如果你想在前端web开发方面开始职业生涯,学习JavaScript是必须的。它是最受欢迎的编程语言,它功能广泛,功能强大。但JavaScript并不是你唯一需要知道的语言。HTML和CSS对于前端开发至关重要。他们将帮助你开发用户友好的网站和应用程序。什么…...

九【springboot】
Springboot一 Spring Boot是什么二 SpringBoot的特点1.独立运行的spring项目三 配置开发环境四 配置开发环境五 创建 Spring Boot 项目1.在 IntelliJ IDEA 欢迎页面左侧选择 Project ,然后在右侧选择 New Project,如下图2.在新建工程界面左侧,…...

《程序员成长历程的四个阶段》
阶段一:不知道自己不知道(Unconscious incompetence) 大学期间,我和老师做过一些小项目,自认为自己很牛,当时还去过一些公司面试做兼职,但是就是不知道为什么没有回复。那个时期的我,压根不知道自己不知道&…...

【SpringBoot】Spring data JPA的多数据源实现
一、主流的多数据源支持方式 将数据源对象作为参数,传递到调用方法内部,这种方式增加额外的编码。将Repository操作接口分包存放,Spring扫描不同的包,自动注入不同的数据源。这种方式实现简单,也是一种“约定大于配置…...

uni-app基础知识介绍
uni-app的基础知识介绍 1、在第一次将代码运行在微信开发者工具的时候,应该进行如下的配置: (1)将微信开发者工具路径进行配置; [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lbyk5Jw2-16790251840…...

Word2010(详细布局解释)
目录一、界面介绍二、选项卡1、文件选项卡(保存、打开、新建、打印、保存并发送、选项)2、开始选项卡(剪贴板、字体、段落、样式、编辑)3、插入选项卡(页、表格、插图、链接、页眉页脚、文本、符号)4、页面…...

Spring如何实现Quartz的自动配置
Spring如何实现Quartz的自动配置1. 开启Quartz自动配置2. Quartz自动配置的实现过程2.1 核心类图2.2 核心方法3. 任务调度执行3.1 大致流程3.2 调整线程池的大小如果想在应用中使用Quartz任务调度功能,可以通过Spring Boot实现Quartz的自动配置。以下介绍如何开启Qu…...

计算机组成原理——作业四
一. 单选题(共11题,33分) 1. (单选题, 3分)四片74181 ALU和一片74182 CLA器件相配合,具有如下进位传递功能:________。 A. 行波进位B. 组内先行进位,组间行波进位C. 组内先行进位,组间先行进位D. 组内行波进位,组间先行进位 我的答案: C 3…...

2023前端面试题(经典面试题)
经典面试题Vue2.0 和 Vue3.0 有什么区别?vue中计算属性和watch以及methods的区别?单页面应用和多页面应用区别及优缺点?说说 Vue 中 CSS scoped 的原理?谈谈对Vue中双向绑定的理解?为什么vue2和vue3语法不可以混用&…...

【Linux内网穿透】使用SFTP工具快速实现内网穿透
文章目录内网穿透简介1. 查看地址2.局域网测试连接3.创建tcp隧道3.1. 安装cpolar4.远程访问5.固定TCP地址内网穿透简介 是一种通过公网将内网服务暴露出来的技术,可以使得内网服务可以被外网访问。以下是内网穿透的一些应用: 远程控制:通过内…...

SQL语句性能分析
1. 数据库服务器的优化步骤 当我们遇到数据库调优问题的时候,该如何思考呢?这里把思考的流程整理成下面这张图。 整个流程划分成了 观察(Show status) 和 行动(Action) 两个部分。字母 S 的部分代表观察&…...

【K3s】第28篇 详解 k3s-killall.sh 脚本
目录 k3s-killall.sh 脚本 k3s-killall.sh 脚本 为了在升级期间实现高可用性,当 K3s 服务停止时,K3s 容器会继续运行。 要停止所有的 K3s 容器并重置容器的状态,可以使用k3s-killall.sh脚本。 killall 脚本清理容器、K3s 目录和网络组件&a…...

生成时序异常样本-学习记录-未完待续
1.GAN&VAE|时间序列生成及异常注入那些事儿:主要讲了数据增广,用GAN、WGAN、DCGAN、VAE,有给几个代码的github的链接,非常有用 2.时序异常检测综述,写的非常好 3.自编码器原理讲解,后面还附…...

自定义类型的超详细讲解ᵎᵎ了解结构体和位段这一篇文章就够了ᵎ
目录 1.结构体的声明 1.1基础知识 1.2结构体的声明 1.3结构体的特殊声明 1.4结构体的自引用 1.5结构体变量的定义和初始化 1.6结构体内存对齐 那对齐这么浪费空间,为什么要对齐 1.7修改默认对齐数 1.8结构体传参 2.位段 2.1什么是位段 2.2位段的内存分配…...

【五】springboot启动源码 - onRefresh
onRefresh 源码解析 Initialize other special beans in specific context subclasses. 核心是创建一个web服务容器(并未在这个方法启动) createWebServer第182行,获取ServletWebServerFactory的具体实现 getWebServerFactory方法ÿ…...

带你一文透彻学习【PyTorch深度学习实践】分篇——线性回归(训练周期:前馈、反馈、权重更新)
“梦想使你迷醉,距离就成了快乐;追求使你充实,失败和成功都是伴奏;当生命以美的形式证明其价值的时候,幸福是享受,痛苦也是享受。” --------史铁生《好运设计》 🎯作者主页:追光者♂🔥 🌸个人简介:计算机专业硕士研究生💖、2022年CSDN博客之星人工…...