反射机制

概念

反射机制是在运行状态中,对任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为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();
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值