发表于: 2020-06-26 23:39:51
1 1698
今天完成的事情:
讲完了java小课堂
关于反射
反射:框架设计的灵魂
反射机制:将类的各个组成部分封装为其他对象,这就是反射机制。
反射的好处:
- 1.可以在程序运行过程中,操作这些对象。
- 2.可以解耦,提高程序的可扩展性。
反射的坏处:
1. 影响性能,反射是一种解释操作,用于字段和方法接入时要慢于直接代码
2.暴力反破坏封装,不安全
- (1)Source源代码阶段:*.java被编译成*.class字节码文件。
- (2)Class类对象阶段:*.class字节码文件被类加载器加载进内存,并将其封装成Class对象(用于在内存中描述字节码文件),Class对象将原字节码文件中的成员变量抽取出来封装成数组Field[],将原字节码文件中的构造函数抽取出来封装成数组Construction[],在将成员方法封装成Method[]。当然Class类内不止这三个,还封装了很多,我们常用的就这三个。
- (3)RunTime运行时阶段:创建对象的过程new
获取Class对象的方式
结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个。
class类对象的主要方法
最常用的
Field getField(String name) 获取指定名称的 public修饰的成员变量
Constructor<T> getConstructor(类<?>... parameterTypes) 获取指定名称的public修饰的构造方法
Method getMethod(String name, 类<?>... parameterTypes) 获取指定名称的public修饰的方法
代码演示1:成员变量
获取一个类对象 并且对获取到的类对象里的成员变量
进行get set
//0、获取Person的Class对象
Class bookClass = Book.class;
System.out.println("bookClass对象为:"+bookClass);
System.out.println("=============================");
//=================================获取变量=========================================
//1、Field[] getFields()获取所有public修饰的成员变量
Field[] fields = bookClass.getFields();
for(Field field : fields){
System.out.println("所有的成员变量"+field);
}
System.out.println("=============================");
//2.Field getField(String name) 根据名称查找获取public修饰的成语变量名
//公有
Field a = bookClass.getField("nameA");
System.out.println("获取public成员变量nameA成功:" + a);
//私有
try {
Field b = bookClass.getField("introduceC");
System.out.println("获取private成员变量introduceC成功:" + b);
}catch (Exception e){
System.out.println("获取private成员变量introduceC失败");
}
System.out.println("=============================");
Book book = new Book();
System.out.println("sssssssss"+book);
//可以通过Filed类的get(Object)方法获取属性的值
Object value = a.get(book);
System.out.println("Filed类的get方法获取属性"+value);
System.out.println("=============================");
//可以通过Filed类的set(Object,value)方法设置属性的值
a.set(book,"悲惨世界");
System.out.println("set修改后的book对象内容"+book.toString());
System.out.println("=============================");
//再次通过Filed类的get(Object)方法获取属性的值
Object value1 = a.get(book);
System.out.println("再次获取Filed类的get方法获取属性"+value1);
System.out.println("=============================");
结果:
代码演示2:构造方法
获取一个类对象 并利用类对象里的构造方法实例化对象
@Test
public void reflect3() throws Exception {
//生成一个class类的对象
Class personClass = Person.class;
System.out.println("生成的class类的对象为"+personClass);
// System.out.println("带路径的全类名"+personClass.getName());
//获取personClass对象所有的public修饰的构造方法
Constructor[] constructors = personClass.getConstructors();
//循环获取所有的构造方法名称
for(Constructor constructor : constructors){
System.out.println("所有的构造方法为"+constructor);
String cName = constructor.getName();
System.out.println("构造方法名字为"+cName);
}
System.out.println("==========================================");
//获取单个无参构造函数 注意:Person类中必须要有无参的构造函数,不然抛出异常
Constructor constructor1 = personClass.getConstructor();
System.out.println("无参的构造方法为= " + constructor1);
//获取到构造函数后可以用于创建对象 //Constructor类内提供了初始化方法newInstance();
Object person1 = constructor1.newInstance();
System.out.println("利用无参方法创建的对象为= " + person1);
System.out.println("==========================================");
//获取有参的构造函数 //public Person(String name, Integer age) 参数类型顺序要与构造函数内一致,且参数类型为字节码类型
Constructor constructor2 = personClass.getConstructor(String.class,Integer.class);
System.out.println("有参构造方位为 = " + constructor2);
//创建对象 //获取的是有参的构造方法,就必须要给参数赋值
Object person2 = constructor2.newInstance("张三", 23);
System.out.println("利用有参构造方法创建的对象为"+person2);
System.out.println("=========================================");
//对于一般的无参构造函数,我们一般不用先获取无参构造器,再进行初始化。而是直接调用Class类内的newInstance()方法
//(但需要保证内部一定要有无参构造方法)
Object person3 = personClass.newInstance();
System.out.println("直接用类的对象进行实例化创建的对象为 = " + person3);
//我们之前使用的 Class.forName("").newInstance; 其本质上就是调用了类内的无参构造函数来完成实例化的
//我们在使用 Class.forName("").newInstance; 反射创建对象时,一定要保证类内有无参构造函数
运行结果
代码演示3:成员方法
获取一个类对象
然后获取到的类对象里的成员方法 再执行
@Test
public void reflect4() throws Exception {
//获取person类的对象
Class personClass = Person.class;
//======================================反射执行方法============================================
//获取所有的方法列表
//注意:获取到的方法名称不仅仅是我们在Person类内看到的方法
//继承下来的方法也会被获取到(当然前提是public修饰的)
Method[] methods = personClass.getMethods();
for(Method method : methods){
//获取方法名
System.out.println(method);
//method.getName()方法获取的方法名是仅仅就是方法名(不带全类名),且不带有参数列表。
String name = method.getName();
System.out.println(name);
}
//获取指定名称的方法
Method eat_method1 = personClass.getMethod("eat");
System.out.println("获取的方法为"+eat_method1);
//执行方法
Person person = new Person();
//如果方法有返回值类型可以获取到,没有就为null
Object rtValue = eat_method1.invoke(person);
//输出返回值 eat方法没有返回值,故输出null
System.out.println("执行eat方法后的返回值为 = " + rtValue);
System.out.println("--------------------------------------------");
//获取有参的构造函数 有两个参数 第一个方法名 第二个参数列表 ,不同的参数是不同的方法(重载)
Method eat_method2 = personClass.getMethod("eat", String.class);
//执行方法
Object rtValue1 = eat_method2.invoke(person,"烤全羊");
//输出返回值 eat方法有返回值,故输出烤全羊
System.out.println("执行有参有返回值eat方法后的返回值为 = " + rtValue1);
System.out.println("============================================");
获取成员变量(类对象继承的object类的所有成员方法也会被获取)
并执行
扩展思考:
写一个小"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。
先写一个两个类
carFactory.calss
public class CarFactory {
public void car(){
System.out.println("造出了一辆汽车");
}
}
tankFactory.class
public class TankFactory {
public void tank(){
System.out.println("造出了一辆坦克");
}
}
先定义一个配置文件
test.properties
className = com.ptteng.enity.TankFactory
methodName = tank
测试文件
public class BuildTest {
public static void main(String[] args) throws Exception {
//1.加载配置文件
//1.1创建Properties对象
Properties pro = new Properties();
//1.2加载配置文件,转换为一个集合
//1.2.1获取class目录下的配置文件 使用类加载器
ClassLoader classLoader = BuildTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("test.properties");
pro.load(is);
//2.获取配置文件中定义的数据
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
//3.加载该类进内存
Class cls = Class.forName(className);
//4.创建对象(默认构造方法)
Object obj = cls.newInstance();
//5.获取方法对象
Method method = cls.getMethod(methodName);
//6.执行方法
method.invoke(obj);
}
}
执行
该类执行了tankFactory的tank方法
修改配置文件再执行
className = com.ptteng.enity.carFactory
methodName = car
该类执行了carFactory的car方法
我们这样做的好处:
1. 对于框架来说,是人家封装好的,我们拿来直接用就可以了,而不能去修改框架内的代码。但如果我们使用传统的new形式来实例化,那么当类名更改时我们就要修改Java代码,这是很繁琐的。
修改Java代码以后我们还要进行测试,重新编译、发布等等一系列的操作。而如果我们仅仅只是修改配置文件,就来的简单的多,配置文件就是一个实实在在的物理文件。
2. 这种方式能达到解耦的效果,假设我们使用的是new这种形式进行对象的实例化。此时如果在项目的某一个小模块中我们的一个实例类丢失了,那么在编译期间就会报错,以导致整个项目无法启动。
而对于反射创建对象Class.forName("全类名");这种形式,我们在编译期需要的仅仅只是一个字符串(全类名),在编译期不会报错,这样其他的模块就可以正常的运行,而不会因为一个模块的问题导致整个项目崩溃。这就是Spring框架中IOC控制反转的本质。
反射就理解到这里,只是简单掌握了用法,明白反射能做什么。
后面再继续研究深入
明天计划的事情:
继续做任务
评论