概念
反射机制是在运行状态中,对任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射机制通常被用来检测和改变应用程序在java虚拟机中的行为表现。
反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
Java反射框架主要提供以下功能:
1.在运行时判断任意一个对象所属的类;
2.在运行时构造任意一个类的对象;
3.在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
4.在运行时调用任意一个对象的方法;
5.生成动态代理。
反射机制的原理:
Class本质也是一个类,一个对象(万物皆对象),用来代表运行在java虚拟机中的类和接口。当一个类被加载以后,Java虚拟机就会自动产生一个Class对象。通过这个Class对象我们就能获得加载到虚拟机当中这个Class对象对应的方法、成员以及构造方法的声明和定义等信息。
Class对象的获取
反射中Class是没有公开的构造方法的,所以没有办法像创建一个类一样通过new关键字来获得一个Class对象。
有三种方法可以获取Class对象:
1. 通过Object类中的getClass()方法。
这种方式,必须要明确具体的类,并创建对象。麻烦。它不适用与基本类型。
2. 通过.class标识。
任何数据都具备一个静态的属性.class来获取其对应的Class对象。
这种方法相对简单,但是还是要明确用到类中的静态成员,还是不够扩展。
3. 通过Class.forName()方法。
这种方式只要有名称即可,更为方便,扩展性更强。
获取Class文件:
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
getClassObject_3();
}
/*
方式三:
只要通过给定的类的字符串名称就可以获取该类,更为扩展。
可以用Class类中的方法完成。
该方法就是forName
这种方式只要有名称即可,更为方便,扩展性更强。
*/
public static void getClassObject_3() throws ClassNotFoundException {
String className="bean.Person";
Class clazz=Class.forName(className);
System.out.println(clazz);
}
/*
方式二:
2. 任何数据都具备一个静态的属性.class来获取其对应的Class对象。
相对简单,但是还是要明确用到类中的静态成员。还是不够扩展。
*/
public static void getClassObject_2() {
Class clazz=Person.class;
Class clazz1=Person.class;
System.out.println(clazz==clazz1);
}
/*
获取字节码对象的方式
1. Object类中的getClass()方法
这种方式,必须要明确具体的类,并创建对象。麻烦。
*/
public static void getClassObject_1(){
Person p=new Person();
Class clazz=p.getClass();
Person p1=new Person();
Class clazz1=p1.getClass();
System.out.println(clazz==clazz1);
}
}
创建实例
通过反射来生成对象主要有两种方式。
1.当类有空参构造函数时,使用Class对象的newInstance()方法来创建Class对象对应类的实例。
2.当类没有空参构造函数时,先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。
下面用具体代码说明。
首先创建一个Person类:
package bean;
public class Person {
private int age;
private String name;
public Person(String name, int age) {
super();
this.age = age;
this.name = name;
System.out.println("Person param run..."+this.name+":"+this.age);
}
public Person() {
super();
System.out.println("person run");
}
public void show(){
System.out.println(name+"...show..."+age);
}
private void privateMethod(){
System.out.println("method run");
}
public void paramMethod(String str,int num){
System.out.println("paramMethod run..."+str+":"+num);
}
public static void staticMethod(){
System.out.println("static method run...");
}
}
使用反射机制创建对象:
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
creatNewObject_2();
}
public static void creatNewObject_2() throws Exception{
bean.Person p=new bean.Person("小强",39);
/*
当要获取指定名称对应类中的所体现的对象时,
而该对象初始化不使用空参数构造函数时怎么办?
既然是通过指定的构造函数进行对象的初始化,
所以应该先获取到该构造函数。通过字节码文件对象即可完成。
该方法是:getConstructor(ParamterTypes);
*/
String name="bean.Person";
//找寻该名称类文件,并加载进内存,产生Class对象。
Class clazz=Class.forName(name);
//获取到了指定的构造函数对象
Constructor constructor=clazz.getConstructor(String.class,int.class);
//通过该构造器对象的newInstance方法进行对象的初始化
Object obj=constructor.newInstance("小明",42);
}
public static void creatNewObject() throws ClassNotFoundException, InstantiationException, IllegalAccessException{
//早期:new的时候,先根据被new的类的名称找寻该类的字节码文件,并加载进内存,然后创建该字节码文件,接着创建该字节码对应的Person对象。
// bean.Person p=new bean.Person();
//现在
String name="bean.Person";
//找寻该名称类文件,并加载进内存,产生Class对象。
Class clazz=Class.forName(name);
//产生该类对象
Object obj=clazz.newInstance();
}
}
获取字段
获取字段主要是这几个方法:
getFiled: 访问公有的成员变量;
getDeclaredField:所有已声明的成员变量。但不能得到其父类的成员变量;
以及getFileds和getDeclaredFields是以数组的形式存储获取到的字段。
public class ReflectDemo3 {
public static void main(String[] args) throws Exception {
getFieldDemo();
}
/*
获取字节码文件中的字段
*/
public static void getFieldDemo() throws Exception{
Class clazz=Class.forName("bean.Person");
Field field=null;//clazz.getField("age");//只能获取公有的
field=clazz.getDeclaredField("age");//只能获取本类的,但包含私有。
//对私有字段的访问取消权限检查。暴力访问,不建议。
field.setAccessible(true);
Object obj=clazz.newInstance();
field.set(obj, 89);
Object o=field.get(obj);
System.out.println(o);
System.out.println(field);
}
}
获取并使用方法
获取某个Class对象的方法集合,主要有以下几个方法:
1. getDeclaredMethods()方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
2. getMethods()方法返回某个类的所有公用(public)方法,包括其继承类的公用方法。
3. getMethod方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。
当我们从类中获取了一个方法后,我们就可以用invoke()方法来调用这个方法。
public class ReflectDemo4 {
public static void main(String[] args) throws Exception {
getMethodDemo_3();
}
public static void getMethodDemo_3() throws Exception {
Class clazz=Class.forName("bean.Person");
Method method=clazz.getMethod("paramMethod", String.class,int.class);
Object obj=clazz.newInstance();
method.invoke(obj,"alice",23);
}
public static void getMethodDemo_2() throws Exception {
Class clazz=Class.forName("bean.Person");
Method method=clazz.getMethod("show", null);//获取空参数一般方法
// Object obj=clazz.newInstance();
Constructor constructor=clazz.getConstructor(String.class,int.class);
Object obj=constructor.newInstance("小明",23);
method.invoke(obj, null);
}
/*
获取指定Class中的所有公共函数
*/
public static void getMethodDemo() throws Exception {
Class clazz=Class.forName("bean.Person");
Method[] methods=clazz.getMethods();//获取的都是公有方法
methods=clazz.getDeclaredMethods();//只获取本类的所有方法,包括私有
for(Method method:methods){
System.out.println(method);
}
}
}
注意:
由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。
另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
实际应用
应用场景:电脑运行。
现在要向电脑里增加设备,以扩展其功能。
以往的做法是修改代码传递一个新创建的对象。但如果程序已经写好,代码不能修改的情况下要扩展新功能,就必须使用反射机制。
先定义一个接口:
public interface PCI {
public void open();
public void close();
}
让外部设备声卡和网卡分别实现这个接口:
//声卡
public class SoundCard implements PCI{
public void open(){
System.out.println("sound open");
}
public void close(){
System.out.println("sound close");
}
}
//网卡
public class NetCard implements PCI{
public void open(){
System.out.println("net open");
}
public void close(){
System.out.println("net close");
}
}
在主板上将接口作为函数的参数传递进来:
public class Mainboard {
public void run(){
System.out.println("main board run...");
}
public void usePCI(PCI p){//PCI p=new SoundCard();多态
if(p!=null){
p.open();
p.close();
}
}
}
建立一个后缀为.properties的文件,在文件里通过键值对的形式存储要添加的设备的信息:
pci1=reflect.test.SoundCard
pci2=reflect.test.NetCard
这时就可以通过反射机制来添加设备了:
public class ReflectTest {
public static void main(String[] args) throws Exception {
Mainboard mb=new Mainboard();
mb.run();
//每次添加一个设备,都需要修改代码传递一个新创建的对象
// mb.usePCI(new SoundCard());
//不修改代码完成添加设备
// 不用new完成,而是只获取其class文件,在内部实现创建对象的动作
File configFile=new File("pci.properties");
Properties prop=new Properties();
FileInputStream fis=new FileInputStream(configFile);
prop.load(fis);
for(int x=0;x<prop.size();x++){
String pciName=prop.getProperty("pci"+(x+1));
Class clazz=Class.forName(pciName);//用Class去加载这个pci子类
PCI p=(PCI)clazz.newInstance();
mb.usePCI(p);
}
fis.close();
}
}