反射(面试必问)
反射
为什么要用反射?
因为Java是静态的强类型语言,在编译阶段就需要确定类型
Java为了实现“动态性“特征,引入了反射机制
变量可以使用Object声明,然后在运行时确定某个对象的运行时类型
或者在运行时动态的”注入“某个类型的对象,动态的创建某个类型的对象
例如:用这个类型的Class对象,然后创建它的实例
例如:JS等是动态的弱类型的语言,在运行时确定变量的类型,根据赋的值确定变量的类型
反射的根源
java.lang.Class
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
//示例代码 @Test public void test() { Class c1 = int.class; Class c2 = void.class; Class c3 = String.class; Class c4 = Comparable.class; Class c5 = ElementType.class; Class c6 = Override.class; Class c7 = int[].class; int[] arr1 = new int[5]; int[] arr2 = new int[10]; System.out.println(arr1.getClass() == arr2.getClass()); System.out.println(int[].class == arr2.getClass()); int[][] arr3 = new int[5][10]; System.out.println(arr1.getClass()); System.out.println(arr3.getClass()); }
四种获取Class对象的方式
(1)如果类型已知: 类型名.class
(2)如果对象存在 对象.getClass()
(3)如果在编译阶段未知,但是运行阶段可以获取它的类型全名称 Class.forName("类型全名称")
(4)如果在编译阶段未知,但是运行阶段可以获取它的类型全名称 类加载对象.loadClass("类型全名称")
相关API
java.lang.Class 方法
(1)获取类型名:getName()
(2)创建实例对象 newInstance()
这个类型必须有无参构造
Class对象.newInstance()
(3)获取包的信息:getPackage()
(4)获取父类
Class getSuperClass() 不带泛型
Type getGenericSuperClass() 可以带泛型
(5)获取父接口
Class[] getInterfaces() 不带泛型
Type[] getGenericInterfaces() 可以带泛型
(6)获取该类型的属性
获取全部可访问的公共的属性 Field[] getFields()
获取全部已声明的属性 Field[] getDeclaredFields()
获取某一个公共的属性 Field getField("属性名")
获取某一个声明过的属性,可能是私有的等
Field getDeclaredField("属性名")
通过属性名就可以唯一确定一个属性
(7)获取该类的构造器
获取全部的公共的构造器
获取全部已声明的构造器
获取某一个公共的构造器
获取某一个已声明的构造器
Constructor getDeclaredConstructor(形参列表的类型Class列表... )
通过构造器的形参列表就可以唯一确定一个构造器
(8)获取该类的方法
获取全部的公共的方法
获取全部已声明的方法
获取某一个公共的方法
获取某一个已声明的方法
Method getDeclaredMethod("方法名", 形参列表的类型Class列表 ....)
通过方法的名称+形参列表才能唯一确定一个方法
(9)获取类上的注解
获取所有的注解/注释 Annotation[] getAnnotations()
获取指定的注解 <A extends Annotation> A getAnnotation(Class<A> annotationClass)
java.lang.reflect
Package
获取包名 getName()
Modifier
Modifier.toString(mod)
Constructor
创建实例对象 newInstance(Object ...)
如果无参,那么就直接“构造器对象.newInstance()”
如果有参:构造器对象.newInstance(给构造器的实参列表)
Field
(1)setAccessible(true)
(2)Object get(实例对象)
Object 属性对象.get(实例对象)
原来是: 实例对象.get属性名();
(3)set(实例对象, 属性的新值)
属性对象.set(实例对象,属性值)
原来是:实例对象.set属性名(属性值)
Method
(1)setAccessible(true) 如果方法不是public才需要
(2)Object invoke(实例对象, 传给被调用方法的实参列表)
Object returnValue = 方法对象.invoke(实例对象,实参列表...)
如果原来的方法对象是没有返回值,即是void,那么returnValue是null
原来:
有返回值 变量 = 实例对象.方法名(实参列表)
无返回值 实例对象.方法名(实参列表);
//示例代码 package com.atguigu.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.nio.charset.Charset; import java.util.Arrays; /* * 有了Class对象后,都可以做什么事?你想干啥干啥 * * 1、获取类的详细信息 * 2、创建实例对象 * 3、获取属性,设置属性 * 4、获取方法,设置方法 * ... */ public class TestReflectAPI { public static void main(String[] args) throws Exception { Object obj = "hello"; Class clazz = obj.getClass(); //1、获取类名 System.out.println("类名:" + clazz.getName()); //2、获取包信息 /* * 所有的包有共同点-->Package */ Package pack = clazz.getPackage(); System.out.println("包名:" + pack.getName()); //3、获取类的修饰符 int mod = clazz.getModifiers(); //每一种修饰符,有一个常量表示 //这个常量在Modifier类型声明 System.out.println(Modifier.toString(mod)); //4、父类 Class superclass = clazz.getSuperclass(); System.out.println("父类:" + superclass); //5、接口 Class[] interfaces = clazz.getInterfaces(); System.out.println("接口们:"); for (Class class1 : interfaces) { System.out.println(class1); } //6、属性:Field /* * 属性共同点: 修饰符 数据类型 属性名 属性对应set值,get值的操作 * 任意类型的一个属性对应Field对象 * * 一切皆对象 */ // Field[] fields = clazz.getFields();//返回公共的属性 /* Field[] fields = clazz.getDeclaredFields(); System.out.println("属性们:"); for (Field field : fields) { System.out.println("属性的类型:"+field.getType()); System.out.println("属性的名称:"+field.getName()); System.out.println("属性的所有信息:"+field); }*/ //单独获取某个属性对象,例如:获取value属性 //假设从配置文件中知晓属性名是value // Field field = clazz.getField("value");//得到公共的 Field field = clazz.getDeclaredField("value");//得到已声明的 System.out.println(field); //设置属性值,获取属性值 //先有对象,才能有属性值 // 获取"hello"对象的value属性值 field.setAccessible(true);//设置可访问 Object object = field.get(obj); char[] v = (char[]) object; System.out.println(Arrays.toString(v)); v[0] = 'w'; v[1] = 'o'; v[2] = 'r'; v[3] = 'l'; v[4] = 'd'; //参数一:哪个对象的field属性,第二个参数:设置为xx新值 // field.set("hello", "world");//因为是final System.out.println(obj); //7、创建对象 创建Class对应的类型的对象 // Object obj = clazz.newInstance(); // System.out.println(obj); //8、构造器 // clazz.getConstructors()//获取所有公共的构造器 // clazz.getDeclaredConstructors();//获取所有该类拥有的构造器 /* * 构造器的共同特点:修饰符 构造器名 形参列表 可以创建对象的操作 */ /* * 构造器可以重载,构造器的名称都一样 * 如何在类中唯一确定一个构造器:靠形参列表(个数和类型) */ // Constructor c = clazz.getDeclaredConstructor();//获取无参构造 // Object newInstance = c.newInstance();//用无参构造创建对象 // System.out.println("对象:"+newInstance); //public String(char value[]) Constructor c = clazz.getDeclaredConstructor(char[].class);//char[]数组类型 //用有参构造创建对象,需要实参列表 char[] params= {'c','h','a','i'}; Object newInstance = c.newInstance(params); System.out.println("对象:"+newInstance); //9、方法 /* * 所有方法共同特点: * 修饰符 返回值类型 方法名(形参列表)抛出的异常列表 * 方法可以被调用 */ // clazz.getMethods()//获取所有公共的方法 // clazz.getDeclaredMethods();//获取所有方法 /* * 方法可以重载,如何在一个类中,唯一确定方法:方法名+形参列表(个数和类型) * * toString() */ Method m = clazz.getDeclaredMethod("toString");//获取无参的方法 System.out.println(m); //调用方法 //参数一:那个实例对象调用m方法,参数二:传给m方法的实参列表 Object returnValue = m.invoke(obj); System.out.println(returnValue); // public byte[] getBytes(Charset charset) Method m2 = clazz.getDeclaredMethod("getBytes", Charset.class); Object returnValue2 = m2.invoke(obj, Charset.forName("GBK")); System.out.println(returnValue2); byte[] data = (byte[]) returnValue2; System.out.println(Arrays.toString(data)); } }
如何获取类上的泛型
步骤
(1)先得到类的Class对象
(2)获取它的父类 Type getGenericSuperClass() 可以带泛型
(3)类型转换
如果是父类是这样的类型 父类名<泛型实参>
ParameterizedType p = (ParameterizedType )type;
(4)获取泛型实参 Type[] getActualTypeArguments()
//示例代码 package com.atguigu.reflect; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Date; public class TestGenericType { public static void main(String[] args) { GenericSub g = new GenericSub(); System.out.println(g.getType1()); System.out.println(g.getType2()); GenericSub2 g2 = new GenericSub2(); System.out.println(g2.getType1()); System.out.println(g2.getType2()); } } //T叫做类型形参 abstract class GenericSuper<T,U>{ private Class<T> type1; private Class<U> type2; public GenericSuper(){ Class clazz = this.getClass();//this是当前对象,在构造器中,就是代表那个正在创建的对象 //Type是包含Class等的所有类型 Type gs = clazz.getGenericSuperclass(); //GenericSuper<String>:参数化的类型 ParameterizedType p = (ParameterizedType) gs; //获取类型实参 Type[] arr = p.getActualTypeArguments(); type1 = (Class<T>) arr[0]; type2 = (Class<U>) arr[1]; } public Class<T> getType1() { return type1; } public Class<U> getType2() { return type2; } } //String是类型实参 class GenericSub extends GenericSuper<String,Integer>{ } class GenericSub2 extends GenericSuper<Date,Double>{ } //核心代码 Class clazz = GenericSub.class; // Class sup = clazz.getSuperclass();//得不到泛型的信息 // System.out.println(sup); //Type是包含Class等的所有类型 Type gs = clazz.getGenericSuperclass(); //GenericSuper<String>:参数化的类型 ParameterizedType p = (ParameterizedType) gs; //获取类型实参 Type[] arr = p.getActualTypeArguments(); System.out.println(arr[0]); System.out.println(arr[1]);
获取注解
获取类上的注解
步骤
(1)先得到类的Class对象
(2)获取指定的注解
<A extends Annotation> A getAnnotation(Class<A> annotationClass)
可以获取到的注解,必须声明周期是RUNTIME
(3)获取注解的配置参数的值
//示例代码 package com.atguigu.reflect; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.junit.Test; @MyAnnoation public class TestAnnotatio { @MyAnnoation(value = "尚硅谷") private String info; //获取类上的注解信息 @Test public void test() { // 1、先得到Class对象 Class clazz = TestAnnotatio.class; // 2、获取类上的注解信息:得到MyAnnoation注解对象 MyAnnoation m = (MyAnnoation) clazz.getAnnotation(MyAnnoation.class); // 3、得到注解的配置参数的值 String value = m.value(); System.out.println(value); } } @Retention(RetentionPolicy.RUNTIME) // 为了在反射阶段可以读取到该注解的信息,生命周期一定要在RUNTIME @interface MyAnnoation { String value() default "atguigu"; }
获取属性上的注解
步骤
(1)先得到类的Class对象
(2)获取属性对象
(3)获取指定的注解
<A extends Annotation> A getAnnotation(Class<A> annotationClass)
可以获取到的注解,必须声明周期是RUNTIME
(4)获取注解的配置参数的值
//示例代码 package com.atguigu.reflect; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Field; import org.junit.Test; public class TestAnnotatio { @MyAnnoation(value = "尚硅谷") private String info; //获取属性上的注解信息 @Test public void test2() throws Exception { // 1、获取Class对象 Class clazz = TestAnnotatio.class; // 2、先获取属性对象 Field field = clazz.getDeclaredField("info"); // 3、得到注解对象 MyAnnoation m = (MyAnnoation) field.getAnnotation(MyAnnoation.class); // 4、得到属性值 System.out.println(m.value()); } } @Retention(RetentionPolicy.RUNTIME) // 为了在反射阶段可以读取到该注解的信息,生命周期一定要在RUNTIME @interface MyAnnoation { String value() default "atguigu"; }
类加载器
类加载的过程(了解)
双亲委托模式/机制
某个类加载器接到加载任务,先把加载任务交给“父”加载器,层层往上,一直到引导类加载器,如果“父”加载器可以加载,那么就由“父”加载器加载,如果不可以,传回它的“子”加载器,“子”加载器尝试加载,如果可以,那么就加载,如果不可以,再往回传,一到回到最初接到任务的那个加载器,如果它可以,也正常加载,如果它也不能加载,报异常:ClassNotFoundException
作用:安全
类加载器的体系结构
1、引导类加载器BootStrap
非Java语言实现的 获取不到它的对象,只能得到null
加载核心类库rt.jar
加载sun.boot.class.path路径下的内容
2、扩展类加载器ExtClassLoader
加载jre/ext目录
java.ext.dirs路径下的内容
3、应用程序类加载器,系统类加载器AppClassLoader
加载用户自定义的类型
加载src目录下的内容(bin)
4、自定义类加载器
类加载器的作用
(1)加载类
(2)加载资源文件
@Test public void test8()throws Exception{ Properties pro = new Properties(); //JavaSE和Web项目 //在web项目中,因为项目部署到tomcat中运行,又因为tomcat用自己的类加载器的 //把配置文件放在了src中,最终代码在WEB-INFO的classes目录 //可以用类加载器加载这个配置文件,但是不是系统类加载器 //this.getClass().getClassLoader()目的是得到tomcat的自定义类加载器对象 pro.load(this.getClass().getClassLoader().getResourceAsStream("3.properties")); System.out.println(pro.getProperty("user3")); } @Test public void test7()throws Exception{ Properties pro = new Properties(); //JavaSE和Web都可以 //把配置文件放在了项目根目录下,在src外面 //不可以用类加载器加载这个配置文件 //可以使用FileInputStream获取 pro.load(new FileInputStream("2.properties")); System.out.println(pro.getProperty("user2")); }
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术