前言
很多讲反射的文章,总是喜欢引用概念,摘抄一段百度反射的定义,说实话,这种概念对没什么基础的同学来说,压根整不明白,像天书,或者有的仅仅告诉你反射的语法,说反射是框架的灵魂,但却不举例看看反射能做什么;
对我来说,学了一门技术,一定得明白它对于我们代码中,该怎么样去用,所以,今天这篇介绍反射的文章,偏向于应用,开头先介绍一下语法,怎么去用反射,最后出了三道题,看看如何去用反射去解决问题,加深大家对反射的印象
目录
前言
一、反射的概念
二、获取Class类
1、新建一个User类
2、获取Class类的语法
三、获取构造方法,并用构造方法创建实例
四、通过反射获取成员变量
1、获得共有成员变量
2、获得私有成员变量的值
五、通过反射获取类的方法
1、获得公有方法并调用
2、获得私有方法并调用
五、来做几道反射的编程题吧
1、通过反射去越过泛型检查
2、通过配置文件去运行某个类的某个方法
3、通过参数,动态去获取对象的字段
总结
一、反射的概念
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
哈哈,听了这个概念是不是很懵逼,你可以简单的理解为在程序运行中,你想获得一个类有哪些属性哪些方法,但你有没有这个类的实现,我们可以通过他的Class类(每一个类在虚拟机中都有一个对应的Class类),我们可以通过解剖这个Class类去反向获得我们需要的类;Class类对象会将一个类的方法、变量、接口、类名、类修饰符等信息告诉运行的程序。
二、获取Class类
1、新建一个User类
public class User {
public String name;
private Integer age;
public User() {
System.out.println("调用无参构造");
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public void say(){
System.out.println("Hello " + this.getName());
}
public void say(String name){
System.out.println("Hello " +name);
}
private void see(){
System.out.println("See" + age);
}
private void see(Integer age){
System.out.println("See" + 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 "User{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
1、新建一个User类
public class User {
public String name;
private Integer age;
public User() {
System.out.println("调用无参构造");
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public void say(){
System.out.println("Hello " + this.getName());
}
public void say(String name){
System.out.println("Hello " +name);
}
private void see(){
System.out.println("See" + age);
}
private void see(Integer age){
System.out.println("See" + 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 "User{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
2、获取Class类的语法
获取Class对象的方式有3中,比较常用的是第三中,通过类名去获取,像JDBC加载驱动也是用的这一种
public class GetClazz {
public static void main(String[] args) {
try {
//通过路径获取
Class> clazz1 = Class.forName("com.company.pojo.User");
System.out.println(clazz1);
//通过类获取
Class clazz2 = User.class;
System.out.println(clazz2);
//通过示例获取
User user = new User();
Class extends User> clazz3 = user.getClass();
System.out.println(clazz3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
三、获取构造方法,并用构造方法创建实例
Java将类的构造方法封装成了Constructor对象,我们可以通过反射去得到这个对象,并且通过构造方法创建类的实例
public class ConstructorTest {
public static void main(String[] args) throws Exception{
Class> clazz = Class.forName("com.company.pojo.User");
//无参构造
Constructor> constructor = clazz.getConstructor();
User user = (User) constructor.newInstance();
System.out.println("通过无参构造方法生成:"+user);
//有参构造
Constructor> constructor1 = clazz.getConstructor(String.class, Integer.class);
User user1 = (User) constructor1.newInstance("tyshawn", 23);
System.out.println("通过有参构造方法生成:"+user1);
}
}
四、通过反射获取成员变量
类的成员变量被封装成Field类,我们得到类的成员变量的封装后,可以通过Field对象去修改一个实例的某个成员变量的值
1、获得共有成员变量
public class PublicFieldTest {
public static void main(String[] args) throws Exception{
Class> clazz = Class.forName("com.company.pojo.User");
User user = (User) clazz.newInstance();
System.out.println("获得对象:"+user);
//获取共有成员变量
Field name = clazz.getField("name");
//可以通过成员变量方法给对应的对象赋值
name.set(user, "tyshaw");
System.out.println("通过反射去改变反射生成的对象属性:"+user);
}
}
2、获得私有成员变量的值
public class PrivateFieldTest {
public static void main(String[] args) throws Exception{
Class> clazz = Class.forName("com.company.pojo.User");
User user = (User) clazz.newInstance();
//获取共有成员变量
Field age = clazz.getDeclaredField("age");
//去除私有权限,如果没有这句话,调用set会报错
age.setAccessible(true);
//赋值
age.set(user, 23);
System.out.println(user);
}
}
五、通过反射获取类的方法
类的方法被封装成了一个Method对象,我们可以通过反射得到这个对象,得到这个对象后,就能通过反射去调用这个类的方法
1、获得公有方法并调用
public class PublicMethodTest {
public static void main(String[] args) throws Exception{
Class> clazz = Class.forName("com.company.pojo.User");
Constructor> constructor = clazz.getConstructor(String.class, Integer.class);
User user = (User) constructor.newInstance("zhangsan", 23);
//获取无参成员方法
Method say = clazz.getMethod("say");
say.invoke(user);
//获取有参成员方法
Method say1 = clazz.getMethod("say", String.class);
say1.invoke(user, "Tom"); //Hello Tom
}
}
2、获得私有方法并调用
public class PrivateMethodTest {
public static void main(String[] args) throws Exception{
Class> clazz = Class.forName("com.company.pojo.User");
Constructor> constructor = clazz.getConstructor(String.class, Integer.class);
User user = (User) constructor.newInstance("zhangsan", 23);
//获取无参成员方法
Method method = clazz.getDeclaredMethod("see");
//去除私有权限
method.setAccessible(true);
method.invoke(user); // Hello zhangsan
//获取有参成员方法
Method method1 = clazz.getDeclaredMethod("see", Integer.class);
//去除私有权限
method1.setAccessible(true);
method1.invoke(user, 11); //Hello Tom
}
}
public class PrivateMethodTest {
public static void main(String[] args) throws Exception{
Class> clazz = Class.forName("com.company.pojo.User");
Constructor> constructor = clazz.getConstructor(String.class, Integer.class);
User user = (User) constructor.newInstance("zhangsan", 23);
//获取无参成员方法
Method method = clazz.getDeclaredMethod("see");
//去除私有权限
method.setAccessible(true);
method.invoke(user); // Hello zhangsan
//获取有参成员方法
Method method1 = clazz.getDeclaredMethod("see", Integer.class);
//去除私有权限
method1.setAccessible(true);
method1.invoke(user, 11); //Hello Tom
}
}
五、来做几道反射的编程题吧
学了反射的语法和一些概念,那就来尝试一下看看通过反射能解决什么问题吧
正所谓,光说不练,假把式
1、通过反射去越过泛型检查
如果我有一个ArrayList集合,泛型设置为Integer类型,但我想在保留泛型的情况下去在集合中新增一个String字符串,该怎么去实现?
答案:
public class Solution_01 {
public static void main(String[] args) throws Exception {
String s = "hello world";
ArrayList list = new ArrayList<>();
list.add(10);
Class extends ArrayList> aClass = list.getClass();
//拿到集合的add方法
Method add = aClass.getMethod("add",Object.class);
add.invoke(list,s);
System.out.println(list);
}
}
看上面的代码,正常情况,list直接调用add方法会检查泛型,但通过反射去获取它的Method方法封装类,在去调用,就能越过泛型检查,往里面添加一个String类型;
2、通过配置文件去运行某个类的某个方法
想象一个场景,在学校,如果来的是学生,那就学习,如果是老师,那就教书,如果是食堂阿姨,那就打饭,学校里有很多人有各自的身份,每个身份有各自需要做的事情(方法),而且后续还会有新的角色;
把这个场景抽象为代码,那就是
if(学生){
调用学习方法
}else if(老师){
教书
}else if(教授){
科研
}else if(保安){
保卫学校
}。。。。
这样有什么弊端,每个、新增一个角色,就得去新增或删除一个角色,就得去改一次代码这样非常不灵活;如何用反射去解决呢?
我们可以新建一个配置文件config.txt
className=com.company.train.bean.Student methodName=study
新建执行方法
public class Solution_02 {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
FileReader fileReader = new FileReader("D:\IDEAProjects\reflection-demo\src\com\company\train\config\class.txt");
properties.load(fileReader);
fileReader.close();
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
Class> aClass = Class.forName(className);
Constructor> constructor = aClass.getConstructor();
Object o = constructor.newInstance();
Method method = aClass.getMethod(methodName); //study
method.invoke(o);
}
}
如果用反射,我们可以把需要的身份直接在配置文件配置,需要调用那个方法,直接就在配置文件配置,如果有新的角色,也只需要新增实体类,然后只需要去该配置文件的类和方法,而不用去动代码,解耦合
3、通过参数,动态去获取对象的字段
有一个实体类,里面有X个成员变量(这里因为篇幅只写了三个),成员变量的命名是FXX,你需要根据上下文,获得序号为X的某个成员变量值,该怎么去获取?比如输入为2,输出F2
public class Entity {
private Integer F1;
private Integer F2;
private Integer F3;
public Integer getF1() {
return F1;
}
public void setF1(Integer f1) {
F1 = f1;
}
public Integer getF2() {
return F2;
}
public void setF2(Integer f2) {
F2 = f2;
}
public Integer getF3() {
return F3;
}
public void setF3(Integer f3) {
F3 = f3;
}
}
如果没有学过反射,可以这样写:
int fun(Entity entity,int i){
if (i==1){
return entity.getF1();
}
if (i==2){
return entity.getF2();
}
if (i==3){
return entity.getF3();
}
return -1;
}
这样写有什么坏处,这里只有3个变量,那就得有三个判断,那如果有100个变量,1000个变量呢,如果还是这样写,那对代码结构简直是一种灾难!
我们可以用反射去解决,只需要几行代码,你可以通过反射去动态获取需要调用的方法
int reflection(Entity entity,int i) throws Exception{
Class extends Entity> aClass = entity.getClass();
Method method = aClass.getMethod("F0" + i);
Integer invoke = (Integer)method.invoke(entity);
return invoke;
}
总结
怎么理解反射,反射就是程序在运行时,通过Class类,获取类的名称、package信息、所有属性、方法、注解、类型、类加载器,这些方法都是封装在其各自的对象中,比如Filed,Method,获得了这些对象,我们可以通过这些组件,比如构造方法类去创建实例,Method实例去执行类的方法,成员变量实例去赋值,;
反射可以动态的去获取不同的类和执行不同的方法,它的优点就是很灵活,代码简洁,可以提高代码的复用率,外部调用方便;它的缺点是性能消耗更大,而且用反射会模糊程序内部逻辑,更复杂。



