反射
反射
一定要自动动手实践一遍,才能有更深的一些认识。看完这些打算斗胆去看看框架试试,心想应该没有那么难了叭?
参考链接🔗:https://blog.csdn.net/sinat_38259539/article/details/71799078?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-9.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-9.nonecase
反射是框架设计的灵魂
使用前提:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码)
反射的概述
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任何一个对象,都能够调用它的任意一个方法和属性。这种动态获取信息以及动态调用对象的方法的功能称为Java语言的反射机制。
反射就是把Java类中的各种成分映射成一个个的Java对象。一个类有:成员变量、方法、构造方法、包等信息。
使用
先写一个实体类,如HomeHouse(住宿类)
package com.snl.domain;
public class HomeHouse {
private Integer id;
private String name;
private Boolean sale;
}
一、获取Class对象的三种方式
- Object ---> getClass(); ——因为所有类都继承Object类,返回一个对象的运行时类
- 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
- (常用)通过Class类的静态方法:Class.forName(String className)
Demo:
package com.snl.controller;
import com.snl.domain.HomeHouse;
public class TestController {
/**
* 获取Class对象
*
* 注意:在运行期间,一个类,只有一个Class对象产生。
* 常用第三种:一个字符串可以传入也可写在配置文件中等多种方法。
* 第一种:对象都有了还要反射干什么。
* 第二种:需要导入类的包,依赖太强,不导包就抛编译错误。
*
* @param args
*/
public static void main(String[] args) {
// 方式1:Object ---> getClass();
// new 产生一个HomeHouse对象,一个Class对象
HomeHouse homeHouse = new HomeHouse();
// 获取Class对象
Class<? extends HomeHouse> houseClass1 = homeHouse.getClass();
// 输出className:com.snl.domain.HomeHouse
System.out.println(houseClass1.getName());
// 方式2:任何数据类型都有一个“静态”的class属性
Class<HomeHouse> houseClass2 = HomeHouse.class;
// 在运行期间,一个类只有一个Class对象产生
// 输出:true
System.out.println(houseClass1 == houseClass2);
// 方式3:通过Class类的静态方法:Class.forName(String className)
try {
Class<?> houseClass3 = Class.forName("com.snl.domain.HomeHouse");
// 输出:true
System.out.println(houseClass2 == houseClass3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
二、通过反射获取构造方法并使用
通过Class对象可以获取类中的:构造方法、成员变量、成员方法,并访问成员。
获取构造方法:
-
批量的方法
// 所有"公有的"构造方法 public Constructor[] getConstructors(); // 获取所有的构造方法(包括私有、受保护、默认、公有) public Constructor[] getDeclaredConstructors();
-
获取单个的方法
// 获取单个的"公有的"构造方法: public Constructor getConstructor(Class... parameterTypes); // 获取"某个构造方法"可以是私有的,或受保护、默认、公有; public Constructor getDeclaredConstructor(Class... parameterTypes);
-
调用构造方法
Constructor-->newInstance(Object... initargs); 创建一个构造方法的声明类的新实例对象,并为之调用。
Demo:
package com.snl.controller;
import com.snl.domain.HomeHouse;
import java.lang.reflect.Constructor;
public class TestController {
public static void main(String[] args) throws Exception {
// 加载Class对象
Class<?> clazz = Class.forName("com.snl.domain.HomeHouse");
System.out.println("--获取所有公有构造方法--");
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
System.out.println("--所有构造方法(包括:私有、受保护、默认、公有)--");
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : declaredConstructors) {
System.out.println(constructor);
}
System.out.println("--获取公有、无参的构造方法--");
// 因为是无参构造方法,所以类型是一个null,不写也可以(注意:这里需要的是一个参数的类型)
Constructor<?> constructor = clazz.getConstructor(null);
System.out.println(constructor);
//调用构造方法
System.out.println("--调用构造方法--");
Object obj = constructor.newInstance();
System.out.println(obj);
HomeHouse homeHouse = (HomeHouse) obj;
System.out.println(homeHouse);
System.out.println("--获取私有构造方法,并调用--");
constructor = clazz.getDeclaredConstructor(Boolean.class);
System.out.println(constructor);
// 暴力访问,忽略掉访问修饰符
constructor.setAccessible(true);
Object o = constructor.newInstance(true);
System.out.println(o);
}
}
HomeHouse类
package com.snl.domain;
public class HomeHouse {
// 无参构造方法
public HomeHouse() {
System.out.println("调用了公有、无参构造方法执行了");
}
// 多个参数构造方法
public HomeHouse(Integer id, String name) {
this.id = id;
this.name = name;
System.out.println("调用了公有、有2参构造方法执行了");
System.out.println("id" + id + "姓名:" + name);
}
// 一个参数构造方法
public HomeHouse(String name) {
this.name = name;
System.out.println("调用了公有、有1参构造方法执行了");
System.out.println("姓名:" + name);
}
// 受保护的构造方法
protected HomeHouse(Integer id) {
this.id = id;
System.out.println("调用了受保护、有1参构造方法执行了");
System.out.println("id:" + id);
}
// 私有构造方法
private HomeHouse(Boolean sale) {
this.sale = sale;
System.out.println("调用了私有、有1参构造方法执行了");
System.out.println("sale:" + sale);
}
}
三、获取成员变量并调用
- 批量的
// 获取所有的“公有字段”
Field[] getFields();
// 获取所有字段,包括:私有、受保护、默认
Filed[] getDeclaredFields();
-
获取单个的
// 获取某个"公有的"字段 public Field getField(String fieldName); // 获取某个字段(可以是私有的) public Field getDeclaredField(String fieldName);
-
设置字段的值
参数说明:
obj:要设置的字段所在的对象
value:值
Field --> public void set(Object obj,Object value);
Demo:
package com.snl.controller;
import com.snl.domain.HomeHouse;
import java.lang.reflect.Field;
public class TestController {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.snl.domain.HomeHouse");
System.out.println("--获取所有公有的字段--");
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("--获取所有的字段--");
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field);
}
System.out.println("--获取私有字段name--");
Field name = clazz.getDeclaredField("name");
System.out.println(name);
Object o = clazz.getConstructor().newInstance();
// 暴力反射,解除私有限定
name.setAccessible(true);
name.set(o, "豪宅");
// 验证
HomeHouse homeHouse = (HomeHouse) o;
System.out.println("验证名称:" + homeHouse.getName());
}
}
四、获取成员方法并调用
-
批量的
// 获取所有"公有方法"(包含了父类的方法也包含Object类) public Method[] getMethods(); // 获取所有的成员方法,包括私有的(不包括继承的) public Method[] getDeclaredMethods();
-
获取单个的
// 参数: // name : 方法名; // Class ... : 形参的Class类型对象(注意:是类型) public Method getMethod(String name,Class<?>... parameterTypes); public Method getDeclaredMethod(String name,Class<?>... parameterTypes);
-
调用方法
// 参数说明: // obj:要调用方法的对象; // args:调用方式时所传递的实参 Method --> public Object invoke(Object obj,Object... args);
Demo:
package com.snl.controller;
import com.snl.domain.HomeHouse;
import java.lang.reflect.Field;
public class TestController {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.snl.domain.HomeHouse");
System.out.println("--获取所有公有方法--");
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("--获取所有方法,包括私有的--");
methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("--获取公有的show1()的方法--");
Method m = clazz.getMethod("show1", String.class);
System.out.println(m);
// 实例化一个HomeHouse对象
Object o = clazz.getConstructor().newInstance();
m.invoke(o, "方块");
System.out.println("--获取私有的show4方法--");
m = clazz.getDeclaredMethod("show4", int.class);
System.out.println(m);
m.setAccessible(true);
Object result = m.invoke(o, 20);
System.out.println("返回值:" + result);
}
HomeHouse类
package com.snl.domain;
public class HomeHouse {
// 成员方法
public void show1(String s) {
System.out.println("调用了 公有的、String参数的show1():s = " + s);
}
protected void show2() {
System.out.println("调用了 受保护的、无参数的show2()");
}
void show3() {
System.out.println("调用了 默认的、无参数的show3()");
}
private String show4(int i) {
System.out.println("调用了 私有的,并且有返回值的,int参数的show4:i = " + i);
return String.valueOf(i);
}
}
五、反射main方法
Demo:
package com.snl.controller;
import java.lang.reflect.Method;
public class TestController {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.snl.domain.HomeHouse");
// 获取main方法
// 第一个参数:方法名称
// 第二个参数:方法形参的类型
Method main = clazz.getMethod("main", String[].class);
// 调用main方法
// 第一个参数:对象类型,因为方法是static静态的,可传null
// 第二个参数:String数组,jdk1.4时是数组,jdk1.5后是可变参数,这里拆的时候将 new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。
// main.invoke(null, (Object)new String[]{"a", "b", "c"});
main.invoke(null, new Object[]{new String[]{"a", "b", "c"}});
}
HomeHouse
package com.snl.domain;
public class HomeHouse {
public static void main(String[] args) {
System.out.println("HomeHouse的main方法执行了");
}
}
六、通过反射运行配置文件内容
利用反射和配置文件,可以使应用程序更新时,对源码无需进行任何修改。只需将新类发送给客户端,并修改配置文件即可。
Demo:
package com.snl.controller;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;
public class TestController {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName(getValue("className"));
Method method = clazz.getMethod(getValue("methodName"));
method.invoke(clazz.getConstructor().newInstance());
}
/**
* 接收key,在配置文件中获取相应的value
*
* @param key
* @return
* @throws IOException
*/
public static String getValue(String key) throws IOException {
Properties properties = new Properties();
FileReader fileReader = new FileReader("D:\\snl\\learnspace\\mytest\\src\\main\\java\\com\\snl\\config\\pro.txt");
properties.load(fileReader);
fileReader.close();
return properties.getProperty(key);
}
配置文件以txt为例(pro.txt):
className = com.snl.domain.HomeHouse
methodName = show
HomeHouse
package com.snl.domain;
public class HomeHouse {
public void show(){
System.out.println("is show()");
}
}
通过反射越过泛型检查
泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的。
package com.snl.controller;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class TestController {
// 通过反射越过泛型检查
// 例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值
public static void main(String[] args) throws Exception{
ArrayList<String> stringList = new ArrayList<String>();
stringList.add("aaa");
stringList.add("bbb");
Class listClass = stringList.getClass();
Method m = listClass.getMethod("add", Object.class);
m.invoke(stringList,100);
for (Object s : stringList) {
System.out.println(s);
}
}
}