一、Java的解释机制
Java编写的程序,一次编译,只要装有Java虚拟机JVM的地方就可以在任意平台到处运行。原因在于,java的源代码会被编译成.class文件字节码。
Java提供了各种不同平台上的虚拟机制,
第一步,由Java IDE进行源代码编译,得到相应类的字节码.class文件。
第二步,Java字节码由JVM执行解释给目标计算机。
第三步,目标计算机将结果呈现给我们计算机用户。
因此,Java并不是编译机制,而是解释机制。
二、Java的反射机制
反射机制操作的就是这个.class字节码文件
首先,加载相应类的字节码到内存中。
随后解剖(反射 reflect)出字节码中的构造函数、方法以及变量(字段)
三、代码验证.class字节码文件
1、定义一个类Animal,里面定义一些构造函数,方法,以及变量
package com.testtools.demo.reflect;
public class Animal {
public String name ="Dog";
private int age =30 ;
//默认无参构造函数
public Animal(){
System.out.println("Animal");
}
//带参数的构造函数
public Animal(String name , int age){
System.out.println(name+","+age);
}
//公开 方法 返回类型和参数均有
public String sayName(String name) {
return "Hello," + name;
}
}
2、再定义一个测试类ReflectTest.java
package com.testtools.demo.test;
public class ReflectTest {
public static void main(String args[]) throws Exception {
}
}
3、运行项目
我们发现在bin目录下 \com\testtools\demo\reflect 路径下,生产了一个Animal.class文件
对应内存中:
Animal.class (硬盘) → Animal类对应的字节码
我们借助javap命令查看一下,这个Animal.class里面的内容是什么。
我们会发现,字节码里面包含了类Animal的构造函数、变量以及方法,
但注意:全都是public类型的,我们定义的类的私有变量 private int age =30
哪去了?
当然,既然是类的私有部分,肯定不会暴露在外面的,但是不阻碍我们通过反射获得字节码中的私有成员(本篇只举例说明私有变量(字段field),其他私有类成员同理)。
类Animal在Anima.java中定义,但在Animal.class文件中,Animal类阐述如下:
public class com.testtools.demo.reflect.Animal
四、验证反射机制
将.class文件中的类加载出来,并反射出字节码中对应类的相关内容(构造函数、属性、方法)
package com.testtools.demo.test;
import com.testtools.demo.reflect.Animal;
import java.lang.reflect.Constructor;
public class ReflectTest {
public static void main(String args[]) throws Exception {
System.out.println("begin to test");
//1、加载类 ,指定类的完全限定名:包名+类名
Class reflectAminal = Class.forName("com.testtools.demo.reflect.Animal");
//打印发现值和字节码中的类的名称一样: class com.testtools.demo.reflect.Animal
System.out.println(reflectAminal);
//2、反射类reflectAminal的公开构造函数,且参数为null
System.out.println("调用无参构造函数");
Constructor ctor1= reflectAminal.getConstructor();
//3、构造函数的用途,就是创建类的对象(实例)的
//除了私有构造函数外(单列模式,禁止通过构造函数创建类的实例,保证一个类只有一个实例)
//ctor1.newInstance()默认生成一个Object对象,我们需要转化成我们要的Animal类对象
System.out.println("实例化无参构造函数类对象");
Animal a1 = (Animal)ctor1.newInstance();
//4、证明一下a1确实是Animal的实例,我们通过访问类中的变量来证明
System.out.println("证明一下a1确实是Animal的实例");
System.out.println(a1.name);
}
}
输出结果
begin to test
class com.testtools.demo.reflect.Animal
调用无参构造函数
实例化无参构造函数类对象
Animal
证明一下a1确实是Animal的实例
Dog
五、反射获取类变量和方法
获得类中的变量(字段)和方法,两种方式,一个是getXXX,一个是getDeclaredXXX,
二者是有区别的,下面demo注释的很详细,并且,我们使用反射出的字段和方法,去获取相应实例的字段值和唤起方法(相当于执行某实例的方法),我们看下完整版demo
package com.testtools.demo.test;
import com.testtools.demo.reflect.Animal;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectProTest {
public static void main(String args[]) throws Exception {
System.out.println("A(无参构造函数)--加载类、反射类的构造函数、利用构造函数new一个Animal实例instance--");
//1、加载类 ,指定类的完全限定名:包名+类名
Class reflectAminal = Class.forName("com.testtools.demo.reflect.Animal");
//打印发现值和字节码中的类的名称一样: class com.testtools.demo.reflect.Animal
System.out.println(reflectAminal);
//2a、反射类reflectAminal的公开构造函数,且参数为null
Constructor ctor1= reflectAminal.getConstructor();
//3a、构造函数的用途,就是创建类的对象(实例)的
//除了私有构造函数外(单列模式,禁止通过构造函数创建类的实例,保证一个类只有一个实例)
//ctor1.newInstance()默认生成一个Object对象,我们需要转化成我们要的Animal类对象
System.out.println("实例化无参构造函数类对象");
Animal a1 = (Animal)ctor1.newInstance();
System.out.println("证明一下a1确实是Animal的实例");
//4a、证明一下a1确实是Animal的实例,我们通过访问类中的变量来证明
System.out.println(a1.name);
System.out.println("A(有参构造函数)--加载类、反射类的构造函数、利用构造函数new一个Animal实例instance");
// 2b、 解刨(反射)类c1的公开构造函数,参数为string和int
Constructor ctor2 = reflectAminal.getConstructor(String.class, int.class);
System.out.println("实例化有参构造函数类对象");
Animal a2 = (Animal) ctor2.newInstance("Cat", 20);
System.out.println("B getDeclaredFields()获得本类中的所有的变量字段");
// 5、获得类中的所有的字段 包括public、private和protected,不包括父类中申明的字段
Field[] fields = reflectAminal.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("C getFields()获得本类中的所有公有的变量字段,并获得指定对象的字段值");
// 6、获得类中的所有的公有字段
fields = reflectAminal.getFields();
for (Field field : fields) {
System.out.println(field + ", 字段值 = " + field.get(a1));
// 注意:私有变量值,无法通过field.get(a1)进行获取值
// 通过反射类中的字段name,修改name的值(注意,原值在类中name="Dog")
// 如果,字段名称等于"name",且字段类型为String,我们就修改字段的值,也就是类中变量name的值
if (field.getName() == "name" && field.getType().equals(String.class)) {
String name_new = (String) field.get(a1);// 记得转换一下类型
name_new = "哈士奇";// 重新给name赋值
field.set(a1, name_new);// 设置当前实例a1的name值,使修改后的值生效
}
}
System.out.println("利用反射出的字段,修改字段值,修改后的name = " + a1.name);
System.out.println("D getDeclaredMethods()获取本类中的所有的方法");
// 7、获取本类中所有的方法 包括public、private和protected,不包括父类中申明的方法
Method[] methods = reflectAminal.getDeclaredMethods();
for (Method m : methods) {
System.out.println(m);// 我们在类Animal中只定义了一个public方法,sayName
}
System.out.println("E getMethods()获取本类中的所有的公有方法,包括父类中和实现接口中的所有public方法");
// 8、获取类中所有公有方法,包括父类中的和实现接口中的所有public 方法
methods = reflectAminal.getMethods();
for (Method m : methods) {
System.out.println(m);// 我们在类Animal中只定义了一个public方法,sayName
}
System.out.println("F getMethod(name, parameterTypes)根据方法名称和参数类型获取指定方法,并唤起方法(invoke):指定所属对象a1,并给对应参数赋值");
// 9、唤起Method方法(执行) getMethod:第一个参数是方法名,后面跟方法参数的类
Method sayName = reflectAminal.getMethod("sayName", String.class);
System.out.println(sayName.invoke(a1, "Tom"));
}
}
输出
A(无参构造函数)--加载类、反射类的构造函数、利用构造函数new一个Animal实例instance--
class com.testtools.demo.reflect.Animal
实例化无参构造函数类对象
Animal
证明一下a1确实是Animal的实例
Dog
A(有参构造函数)--加载类、反射类的构造函数、利用构造函数new一个Animal实例instance
实例化有参构造函数类对象
Cat,20
B getDeclaredFields()获得本类中的所有的变量字段
public java.lang.String com.testtools.demo.reflect.Animal.name
private int com.testtools.demo.reflect.Animal.age
C getFields()获得本类中的所有公有的变量字段,并获得指定对象的字段值
public java.lang.String com.testtools.demo.reflect.Animal.name, 字段值 = Dog
利用反射出的字段,修改字段值,修改后的name = 哈士奇
D getDeclaredMethods()获取本类中的所有的方法
public java.lang.String com.testtools.demo.reflect.Animal.sayName(java.lang.String)
E getMethods()获取本类中的所有的公有方法,包括父类中和实现接口中的所有public方法
public java.lang.String com.testtools.demo.reflect.Animal.sayName(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
F getMethod(name, parameterTypes)根据方法名称和参数类型获取指定方法,并唤起方法(invoke):指定所属对象a1,并给对应参数赋值
Hello,Tom
由此我们发现:反射的机制,无非就是先加载对应字节码中的类。然后,根据加载类的信息,一点点的去解析其中的内容,不管是public的还是private的,亦或是本类的还是来自原继承关系或者实现接口中的方法。
Java的反射技术 reflect,均可以将其从字节码中拉回到现实,不仅可以得到字段的名字,我们还可以获得字段的值和修改字段的值,不仅可以得到方法的申明我们还可以拿到方法的定义和唤起方法(执行方法)。
六、为什么要用反射
为什么new一个对象那么简单,非要用反射技术中的newInstance?
为什么,我可以直接对象a1. 变量访问变量,却非要用反射那么费劲的获得name字段呢?
原因是:每个用户new的对象需求不相同,他们只能修改源代码,并重新编译才能生效。这种将new的对象写死在代码里的方法非常不灵活,因此,为了避免这种情况的方法,Java提供了反射机制。
Java反射是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public、static等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。
Reflection可以在运行时加载、探知、使用编译期间完全未知的classes。即Java程序可以加载一个运行时才得知名称的class,获取其完整构造,并生成其对象实体、或对其fields设值、或唤起其methods。
反射(reflection)允许静态语言在运行时(runtime)检查、修改程序的结构与行为。
在静态语言中,使用一个变量时,必须知道它的类型。在Java中,变量的类型信息在编译时都保存到了class文件中,这样在运行时才能保证准确无误;
换句话说,程序在运行时的行为都是固定的。如果想在运行时改变,就需要反射这东西了。
举个例子:
假如你写了一段代码:Object o=new Object(); 并运行起来,
首先JVM会启动,代码会编译成一个.class文件,然后被类加载器加载进jvm的内存中。
类Object加载到方法区中,创建了Object类的class对象到堆中,注意这个不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口。
jvm创建对象前,会先检查类是否加载,寻找类对应的class对象。若加载好,则为你的对象分配内存,初始化也就是代码:new Object()。
上面的流程就是你自己写好的代码扔给jvm去跑,跑完就over了,jvm关闭,你的程序也停止了。
为什么要讲这个呢?因为要理解反射必须知道它在什么场景下使用。
程序对象是自己new的,程序相当于写死了给jvm去跑。假如一个服务器上突然遇到某个请求要用到某个类,但没加载进jvm,是不是要停下来自己写段代码,new一下,再启动一下服务器?
反射是什么呢?当我们的程序在运行时,需要动态的加载一些类,这些类可能之前用不到,所以不用加载到jvm,而是在运行时根据需要才加载。
这样的好处对于服务器来说不言而喻,举个例子我们的项目底层有时是用mysql,有时用oracle,需要动态地根据实际情况加载驱动类,这个时候反射就有用了,假设 com.java.dbtest.myqlConnection,com.java.dbtest.oracleConnection这两个类我们要用,这时候我们的程序就写得比较动态化,通过Class tc = Class.forName("com.java.dbtest.TestConnection");通过类的全类名让jvm在服务器中找到并加载这个类,而如果是oracle则传入的参数就变成另一个了。
这时候就可以看到反射的好处了,这个动态性就体现出java的特性了!
举多个例子,大家如果接触过spring,会发现当你配置各种各样的bean时,是以配置文件的形式配置的,你需要用到哪些bean就配哪些,spring容器就会根据你的需求去动态加载,你的程序就能健壮地运行。
java的反射机制就是增加程序的灵活性,避免将程序写死到代码里, 例如: 实例化一个 person()对象, 不使用反射, new person(); 如果想变成 实例化 其他类, 那么必须修改源代码,并重新编译。 使用反射: class.forName("person").newInstance(); 而且这个类描述可以写到配置文件中,如 **.xml, 这样如果想实例化其他类,只要修改配置文件的"类描述"就可以了,不需要重新修改代码并编译。
方法区存的是类的信息,不是存类对象的,建议看一下JVM内存分配,类加载器加载类是通过方法区上类的信息在堆上创建一个类的Class对象,这个Class对象是唯一的,由JVM保证唯一,之后对这个类的创建都是根据这个Class对象来操作的
你可以理解成类存在方法区中,类的class对象存在于堆中,这个class对象会作为运行时创建该类对象的模版。这个class对象是唯一对应该类的,要区分所谓的实例和class对象。为什么需要class对象,你想下如果一个加载进方法区的类,在jvm运行时是动态加载进来的,没有这个class对象你思考该如何访问一个未知的类并创建对象呢?没错就是这个class作为访问接口。
Java的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用在编译期并不知道的类。这样的特点就是反射。
典型的spring应用将类的描述放在xml配置文件中。
Spring的IOC,即“控制反转”(通过第三方配置文件实现对 对象的控制)。简单说是将我们设计好的对象交给容器控制,而不是直接交给程序内部进行对象的控制。(sping bean的配置)
针对上述的配置,Spring是帮助我们实例化对象,并放到容器中去了。
没错,就是通过反射。
伪代码如下:
//解析 元素的id属性得到该字符串值为"sqlSessionFactory"
String idStr = "sqlSessionFactory";
//解析 元素的class属性得到该字符串值为"org.mybatis.spring.SqlSessionFactoryBean"
String classStr = "org.mybatis.spring.SqlSessionFactoryBean";
//利用反射知识,通过classStr获取Class类对象
Class cls = Class.forName(classStr);
//实例化对象
Object obj = cls.newInstance();
//container表示Spring容器
container.put(idStr, obj);
//当一个类里面需要用另一类的对象时,我们继续下面的操作
//解析 元素的name属性得到该字符串值为“dataSource”
String nameStr = "dataSource";
//解析 元素的ref属性得到该字符串值为“dataSource”
String refStr = "dataSource";
//生成将要调用setter方法名
String setterName = "set" + nameStr.substring(0, 1).toUpperCase()
+ nameStr.substring(1);
//获取spring容器中名为refStr的Bean,该Bean将会作为传入参数
Object paramBean = container.get(refStr);
//获取setter方法的Method类,此处的cls是刚才反射代码得到的Class对象
Method setter = cls.getMethod(setterName, paramBean.getClass());
//调用invoke()方法,此处的obj是刚才反射代码得到的Object对象
setter.invoke(obj, paramBean);
七、反射机制其他知识点
1、反射机制的作用是什么?
反射是为了能够动态地加载一个类,动态地调用一个方法,动态地访问一个属性等动态要求而设计的。它的出发点就在于JVM会为每个类创建一个java.lang.Class类的实例,通过该对象可以获取这个类的信息,然后通过使用java.lang.reflect包下的API以达到各种动态需求。
2、Class类的含义和作用是什么?
每一个Class类的对象就代表了一种被加载进入JVM的类,它代表了该类的一种信息映射。开发者可以通过以下3种途径获取到Class对象。
a Class类的forName()方法的返回值。
b 访问所有类都会拥有的静态的class属性。
c 调用所有对象都会有的getClass()方法。
在Class类中,定义许多关于类信息的方法。例如,getName()、getMethod()、getConstructor()和newInstance()等可以用于反射开发,还有isInstance()和isInterface()等一些关于类的功能方法。
3、如何操作类的成员变量
Field提供有关类或接口的单个静态或实例字段的信息,它通过Class类的getDeclaredField()或getDeclaredFields()方法获取到,再置于java.lang.reflect包下。
Field的方法主要分为两大类,即getXXX和setXXX,她们都需要提供相应的实例对象,setXXX还需要提供需要设置的值。
4、如何操作类的方法(Method)
Method提供关于类或接口中的某个方法(以及如何访问该方法)的信息,包括了静态方法额成员方法(包括抽象方法在内)。
它通过Class类的getMethod()或getMethods()方法获取到,该类定义在java.lang.reflect包下。Method类的最常用的方法是invoke(),正是通过它来完成方法被动态调用的目的。
5、如何利用反射实例化一个类
根据调用构造方法的不同,用反射机制来实例化一个类,可以有两种途径。
如果使用无参数的构造方法,则直接使用Class类的newInstance()方法即可。
若需要使用特定的构造方法创建对象,则需要先获取Contructor实例,再用newIntance()方法创建对象。
6、如何利用反射机制来访问一个类的私有成员
在使用反射机制访问私有成员的时候,他们的可访问性是为false的。需要调用setAccessible(true)方法,把原本不可访问的私有成员变为可以访问以后,才能进行成功的访问或调用。