Java反射机制
关于反射,我也是听过很多遍,我也去学习了一段时间,但是一直模模糊糊的,平时像我是做前端的,更没有机会遇见。现在有空闲时间了,就找了很多的文章看了,梳理了下,好歹是弄清楚了点。
介绍:
我看了很多的介绍,第一句话就是告诉你:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
这句话虽然简单,但是像我这种实践少的,理解起来就特别费劲,没有具体的场景,感觉只能算是对自己猜测的一种认可。
为了更好理解反射机制,我又回头来补充了一下。想一下这样一个场景:
场景一:
有时候你获得了一个类的名字,想创建这个类,但是这时候你发现它是一个String字符,那么怎么用这个String字符去创建这个类呢?通常情况下我们会这样写:
...
String className = "Cat";
...
if("Cat".equals(className)){
Cat cat = new Cat("xxx");
return cat;
}
这样写没错,但是我们假设有很多不同的类要生成(Dog类、Fish类…),这时候我们得不断加else if
,每次都得来代码中添加几行代码。
但是,java反射机制给了我们很好的一个解决方法,那就是反射
,我们就可以直接通过String生成对应的类。
...
String className = "xxx.xxx.Cat"; // 要求是类的全路径(放在哪个包下)
...
Object obj = Class.forName(className).newInstance(); // 运行类型为className值的类型,这里为Cat
// 上面的Class.forName(xxx)就是加载xxx类,返回一个Class类(也是一个类)
// newInstance()就是创建实例
// 所以上面的就相当于 new className(); -> new Cat();
这样的话,我们用一句话就解决了上面的问题,无论是哪个对象的生成,都可以通过上面的代码实现,就没必要反复去添加else if了。
场景二:
这里其实有一个很明显的例子:就是你使用编程软件的时候用到的东西,当你输入一个类的时候,你输入.
这个符号的时候,他会把这个类的方法给你显示出来。这样来看,“运行状态”中指的就是你的编程软件运行,然后他通过反射手段,把你类的所有属性和方法拿到,在把相应的部分显示给你。(通过getMethods()返回类所有的方法,getFields()返回所有的字段(也就是属性))。
通过反射,加载类(Class clazz = Class.forName(xxx))之后,它会识别出你类中的方法,属性(内部实现我们不用关心,我们只用知道xxx类的所有信息都保存在clazz里边了),我们这时候就可以知道类中的所有方法和属性,我们可以用clazz对象去创建实例,或者是调用其中的方法(就像上面用String实例创建对象一样,我们仍可以用String实例来调用方法)。
简单的例子:
例子:
项目结构:
properties文件(只是用于读取className使用的)
className=com.myClass.Cat
Cat类:
package com.myClass;
public class Cat {
public String name = "小猫";
private int age = 0;
public void eat(){
System.out.println("小猫吃鱼...");
}
public void play(){
System.out.println("小猫玩耍...");
}
public void sleep(){
System.out.println("小猫睡觉...");
}
}
reflection类
package com.myClass.reflection;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Properties;
public class Reflection {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Properties properties = new Properties();
properties.load(new FileInputStream("src\\properties")); // 加载配置文件,就相当于加载文件,然后放到一个配置类中去
String classFullPath = properties.get("className").toString();// 获取配置类中的信息
// 反射的使用
Class clazz = Class.forName(classFullPath);
Field[] fields = clazz.getFields(); //获取所有的属性
// 打印输出
System.out.println("属性有:");
for(Field f : fields){
System.out.println(f.getName());
}
Method[] methods = clazz.getMethods();// 获取所有方法
// 打印输出
System.out.println("方法有:");
for(Method m : methods){
System.out.println(m.getName());
}
}
}
运行截图:
这里多出来的方法继承自基类而来。
两种方式比较:
我们一般创建对象都是都是在未运行时
new className();然后再去调用相应的方法。而反射可以做到在未运行时不去创建对象,而在运行的时候
去完成这个对象的创建,再去使用相应的方法。下面比较下两种情况:
未使用反射:
Cat cat = new Cat();
cat.eat();
使用反射:
Properties properties = new Properties();
properties.load(new FileInputStream("src\\properties")); // 加载配置文件
// 获取配置文件信息
String classFullPath = properties.get("className").toString(); // 获取类名->com.myClass.Cat
String methodName = properties.get("method").toString(); // 获取调用的方法->eat
// 下面两步相当于new Cat();
Class clazz = Class.forName(classFullPath);
Object obj = clazz.newInstance();
// 下面两步相当于cat.eat();
Method method = clazz.getMethod(methodName);
method.invoke(obj);
从上面就可以看见反射的好处,假设我需要改变调用的方法,第一种非反射方式只能改源码,而反射方式能通过修改配置文件就能做到。Reflection类中不需要出现依赖的对象,达到了解耦的效果。
其次:
未用反射时,我们只能在未运行时就创建好对象,等待调用,这样代码就写死了,就死死绑定了我们写的几个类,这就是高耦合
使用反射时,我们要使用的对象并没有出现在Reflection类中,单纯这样看,他们之间没有任何关系(无依赖),这样的话,代码的耦合度就很小,那么代码就会更加灵活。