发表于: 2020-06-26 23:39:51

1 1696


今天完成的事情:


讲完了java小课堂


关于反射


反射:框架设计的灵魂



反射机制:将类的各个组成部分封装为其他对象,这就是反射机制。


反射的好处:

  • 1.可以在程序运行过程中,操作这些对象。
  • 2.可以解耦,提高程序的可扩展性。


反射的坏处:

1. 影响性能,反射是一种解释操作,用于字段和方法接入时要慢于直接代码

2.暴力反破坏封装,不安全




  • (1)Source源代码阶段:*.java被编译成*.class字节码文件。

  • (2)Class类对象阶段:*.class字节码文件被类加载器加载进内存,并将其封装成Class对象(用于在内存中描述字节码文件),Class对象将原字节码文件中的成员变量抽取出来封装成数组Field[],将原字节码文件中的构造函数抽取出来封装成数组Construction[],在将成员方法封装成Method[]。当然Class类内不止这三个,还封装了很多,我们常用的就这三个。

  • (3)RunTime运行时阶段:创建对象的过程new



获取Class对象的方式


获取Class对象的三种方式对应着java代码在计算机中的三个阶段


(1)【Source源代码阶段】 Class.forName("全类名"):将字节码文件加载进内存,返回Class对象 * 多用于配置文件,将类名定义在配置文件中。读取文件,加载类。
(2)【Class类对象阶段】 类名.class:通过类名的属性class获取 * 多用于参数的传递
(3)【Runtime运行时阶段】对象.getClass():getClass()方法是定义在Objec类中的方法 * 多用于对象的获取字节码的方式

结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个。





class类对象的主要方法


最常用的


(1)获取成员变量们
Field[] getFields() :获取所有public修饰的成员变量

Field getField(String name) 获取指定名称的 public修饰的成员变量


Field[] getDeclaredFields() 暴力反射 获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name) 暴力反射 获取单个成员变量,不考虑修饰符
(2)获取构造方法们
Constructor<?>[] getConstructors() :获取所有public修饰的构造方法

Constructor<T> getConstructor(<?>... parameterTypes) 获取指定名称的public修饰的构造方法


Constructor<?>[] getDeclaredConstructors() 暴力反射 获取所有的构造方法,不考虑修饰符
Constructor<T> getDeclaredConstructor(<?>... parameterTypes) 暴力反射 获取单个构造方法,不考虑修饰符
(3)获取成员方法们:
Method[] getMethods() :获取所有public修饰的方法

Method getMethod(String name, <?>... parameterTypes) 获取指定名称的public修饰的方法


Method[] getDeclaredMethods() 暴力反射 获取所有的成员方法,不考虑修饰符
Method getDeclaredMethod(String name, <?>... parameterTypes) 暴力反射 获取单个成员方法,不考虑修饰符




代码演示1:成员变量


获取一个类对象     并且对获取到的类对象里的成员变量

进行get set


//0、获取PersonClass对象
       Class bookClass = Book.class;

       System.out.println("bookClass对象为:"+bookClass);
       System.out.println("=============================");

//=================================获取变量=========================================

//1Field[] 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控制反转的本质。



反射就理解到这里,只是简单掌握了用法,明白反射能做什么。

后面再继续研究深入


明天计划的事情:


继续做任务


返回列表 返回列表
评论

    分享到