java回忆录—带你破封装瞧个遍(反射)

今天来看看我们经常能用到的一个知识点,但又不怎么会用的知识点——反射。那我们在什么时候会用到这个知识点呢,以前我们在学习SSH框架的时候都在和反射打交道,还记得我们学习SSH框架的时候一直需要配置文件(当然注解就更简单了),这就是频繁的用到了反射。现在很多开源框架都用到反射机制。

还有就是设计模式中的动态代理模式,我们需要在运行的时候才能确定要代理的对象是啥,要代理干什么,这就需要用到反射,在运行期才能够确定下来。

当然还有一些应用就要是在安卓中,可能为了安全起见,有些API方法是被隐藏起来了(有@hide标记的),按正常流程是访问不到的,这时我们也需要用到反射来访问到这个方法。

一、什么是反射机制

简单来说就是,反射机制指的是程序在运行时能够获取自身的信息。

在java中,只要给定类的名字, 那么就可以通过反射机制来获得类的所有信息。

二、反射机制的优点与缺点

静态编译:在编译时确定类型,绑定对象,即通过。

动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。

优点:

可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中,它的灵活性就表现的十分明显。

比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了。

当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。

采用静态的话,需要把整个程序重新编译一次才可以实现功能 的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。

缺点:

对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

三、利用反射机制能获得什么信息

一句话,类中有什么信息,它就可以获得什么信息,不过前提是得知道类的名字,要不就没有后文了,首先得根据传入的类的全名来创建Class对象。

获得构造器的方法

Constructor getConstructor(Class[] params)//根据指定参数获得public构造器

Constructor[] getConstructors()//获得public的所有构造器

Constructor getDeclaredConstructor(Class[] params)//根据指定参数获得public和非public的构造器

Constructor[] getDeclaredConstructors()//获得所有构造器 

获得类方法的方法

Method getMethod(String name, Class[] params),根据方法名,参数类型获得public方法

Method[] getMethods()//获得所有的public方法

Method getDeclaredMethod(String name, Class[] params)//根据方法名和参数类型,获得public和非public的方法

Method[] getDeclaredMethods()//获得所有的方法 

获得类中属性的方法

Field getField(String name)//根据变量名得到相应的public成员变量

Field[] getFields()//获得类中所有public的成员变量

Field getDeclaredField(String name)//根据方法名获得public和非public成员变量

Field[] getDeclaredFields()//获得类中所有的成员变量 

常用的就这些,知道这些,其他的都好办…… 

首先能调用这些方法,先得获得 Class 对象,那么获取 Class 对象的方式有三种:

①Object 的 getClass()方法

②数据类型的静态属性 class

③Class 中的静态方法

public static void forName(String className)

那么一般使用哪种方式来创建呢?

有两种可能,如果是自己玩,那么可以任选一种,第二种比较方便。

如果是开发,会选择第三种,为什么呢?因为第三种是一个字符串,而不是一个具体的类名,这样的话我们可以把这样的字符串配置在配置文件中去了。

我个人始终认为案例驱动是最好的,要不然只看理论的话,看了也不懂,不过建议大家在看完文章之后,在回过头去看看理论,会有更好的理解。

这里新建一个 Student 类,在整个案例中,我们都用到这个 Student 类来讲解

Student.java

public class Student {

    private String name;
    int age;
    public String address;

    public Student() {

    }

    private Student(String name) {
        this.name = name;
    }

    Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private Student(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
    public void show() {
        System.out.println("show");
    }
    public void method(String s) {
        System.out.println("method:"+s);
    }
    private String getString(String s,int i) {
        return "getString:"+s+i;
    }
    private void funcation() {
        System.out.println("funcation");
    }
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + ", address="
                + address + "]";
    }

}

简单分析一下这个类,这个Student类有三个成员变量 name 、age、address ,权限修饰符分别为 private 、default、public;有四个构造器,无参、一参、两参、三参,权限修饰符分别为 public 、private 、defalut、private;还有四个成员方法,在参数、返回值、权限修饰符各有不同;当然还重写了一个 toString()方法。

案例一:获得 Class 类的对象(三种方式)

public class StudentTestDemo1 {

    public static void main(String[] args) throws Exception{
        //第一种方法
        Student s = new  Student();
        Class c1 = s.getClass();
        //第二种方法
        Class c2 = Student.class;
        //第三种方式
        Class c3 = Class.forName("com.briup.reflect.Student");
        System.out.println(c1 == c2);//true
        System.out.println(c1 == c3);//true
    }
}

结果是 c1 、c2、c3 为同一个对象,这里请记住一句话:所有的类都是 Class 类的对象。即类也是一个对象,它是Class的对象,它的类型称为类类型。

案例二:通过反射去获取构造方法并使用

这里就获取无参的和三参的,因为权限修饰符为 public 和 private ,这两种会了其他的构造方法也就同理了。

public class StudentTestDemo2 {

    public static void main(String[] args) throws Exception{
        //获取Class对象,字符串为包名+类名
        Class c = Class.forName("com.briup.reflect.Student");

        //获取无参构造方法,参数为空,因为无参构造方法没有参数
        Constructor constructor = c.getConstructor();
        //根据构造方法创建对象,参数为空,因为是无参构造方法
        Object obj = constructor.newInstance();
        System.out.println(obj);

        //获取三参的构造方法,参数是个可变参数,表示构造方法的参数的类型的类类型
        Constructor con = c.getDeclaredConstructor(String.class,int.class,String.class);
        //设置取消访问检查,因为权限修饰符为private
        con.setAccessible(true);
        //创建对象,参数为构造方法的实参
        Object object = con.newInstance("周杰伦",30,"台湾");
        System.out.println(object);
    }
}

结果:

java回忆录—带你破封装瞧个遍(反射)_第1张图片

从结果我们可以看出,我们获取无参构造方法和三参构造方法并创建出了对象。不过在使用三参的构造方法创建对象之前添加这句代码:con.setAccessible(true);

那么这句代码是表示什么意思呢?

在注释中我们提到这是取消访问检查(我这里把它叫做暴力访问,因为它不管别人的权限修饰符),因为它是 private 的,如果我们不加这句代码去使用的话报异常:java.lang.IllegalAccessException(非法访问异常)

将此对象的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。

让我们来看看这个方法是在哪呢?是在Constructor中吗?

答:并不是,在 AccessibleObject 类中,AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。

案例三:通过反射获取成员变量并使用

public class StudentTestDemo4 {
    public static void main(String[] args) throws Exception{
        Class c = Class.forName("com.briup.reflect.Student");

        //获取指定的成员变量(name属性)
        Field nameField = c.getDeclaredField("name");
        //获取指定的成员变量(address属性)
        Field addressField = c.getField("address");

        //通过无参构造函数创建对象
        Constructor con = c.getConstructor();
        Object obj = con.newInstance();

        //在使用之前要取消访问检查,因为name的权限修饰符为private
        nameField.setAccessible(true);
        //给obj对象的nameField即name成员变量赋值为TF
        nameField.set(obj, "TF");

        //给obj对象的addressField即address成员变量赋值为中国
        addressField.set(obj, "中国");
        System.out.println(obj);
    }
}

结果:

java回忆录—带你破封装瞧个遍(反射)_第2张图片

同理,成员变量和构造方法一样,使用的时候(比如赋值)如果是 private 修饰的话,需要取消访问检查。不然会报异常。

nameField.set(obj, “TF”);这句话的含义为 调用 obj 对象的set() 方法给 nameFiled 成员变量赋值为 TF。

案例四:通过反射获取成员方法并使用

public class StudentTestDemo5 {

    public static void main(String[] args) throws Exception {

        Class c = Class.forName("com.briup.reflect.Student");
        //获取指定参数的成员方法
        //第一个参数表示方法名,第二个参数表示形参的类类型
        Method method = c.getDeclaredMethod("getString",String.class,int.class);

        // 通过无参构造方法创建对象
        Constructor con = c.getConstructor();
        Object obj = con.newInstance();
        //取消访问检查
        method.setAccessible(true);
        //调用obj对象的method方法,后面跟着的实参,返回值为方法的返回值
        Object object = method.invoke(obj,"zhou",15);

        System.out.println(object);
    }
}

结果:

java回忆录—带你破封装瞧个遍(反射)_第3张图片

同理,使用方法之前也需要取消访问检查,因为它的权限修饰符为 private,不然会报异常。

Object object = method.invoke(obj,”zhou”,15); 这句代码表示 调用 obj 对象的method 方法,并传入“zhou”、15这两个实参,object 为方法的返回值。

温馨提示:

①我们每次获取构造函数、成员变量、成员方法的时候要根据它的权限来选择相应的方法。其实我们需要获取的时候只要调用相应的 getDeclaredXXX()即可。因为
getDeclaredXXX()方法获取的是所有的构造函数/成员变量/成员方法(包括public 和 非public 的)

②大家可以看到每次使用构造函数、成员变量、成员方法之前要判断是否要添加取消访问检查,其实我们不用管他们的权限修饰符,每次要使用这些之前都加上取消访问检查不就行了嘛。 是的。

反射的基本方法的使用就介绍到这,当然它不止上面这些方法,我相信,学会上面的方法的使用,你去学习它的其他方法的使用应该是同理的。

总的来说,java反射机制是一个很好用的东西,用它可以解决很多死的东西,因为反射机制的灵活行很大。

前面我们提到了运用反射+配置文件可以非常方便。现在就来模拟一下这中场景:

需求:通过配置文件运行运行类中的方法

前提:

    需要有配置文件配合使用
    用class.txt代替使用
    并且你知道有两个键
        className
        methodName

有三个简单的类:BasketBall、FootBall、PingPang

BasketBall.java

public class BasketBall {

    public void method() {
        System.out.println("篮球:    科比");
    }
}

FootBall.java

public class FootBall {

    public void method() {
        System.out.println("足球:    C罗");
    }
}

PingPang.java

public class PingPang {

    public void method() {
        System.out.println("乒乓:     张继科");
    }
}

如果现在现在需要调用BasketBall的方法,那么我们会创建BasketBall的对象,然后再去调用它里面的方法;然后现在需求又改成需要调用FootBall的方法,那么我们需要创建FootBall的对象,再去调用它里面的方法;再然后需求又变成需要调用BasketBall的方法,那么我们又写回去吗?。。。。。。这样周而复始,是不是觉得很繁琐,而且每次都得改动源码,你觉得在客户手中有源码吗?没有,只有.class文件。这样改的话每次都得重新编译,再交到客户手中。

所以我们想到了运用反射,因为反射是在运行中动态获取信息。

配置文件用class.txt代替,内容为:

className=com.briup.reflect.BasketBall

methodName=method
public class TestDemo {

    public static void main(String[] args) throws Exception {
        Properties prop = new Properties();
        FileReader fr = new FileReader("class.txt"); //将配置文件加载进来
        prop.load(fr);

        //根据键来获取值
        String className = prop.getProperty("className");
        String methodName = prop.getProperty("methodName");

        //创建Class对象
        Class c = Class.forName(className);

        //获取无参构造方法
        Constructor constructor = c.getDeclaredConstructor();
        //不管三七二十一,暴力访问
        constructor.setAccessible(true);
        Object obj = constructor.newInstance();

        //获取成员方法
        Method method = c.getDeclaredMethod(methodName);
        //不管三七二十一,暴力访问
        method.setAccessible(true);
        method.invoke(obj);
    }
}

这样的话,你需求变了的话,你只需要改一下配置文件的相应的值。重新运行下即可。

如:现在需要调用FootBall的method方法。只需将配置文件改成这样:

className=com.briup.reflect.FootBall
methodName=method

如果又变成需要调用PingPang的method方法。只需将配置文件改成这样:

className=com.briup.reflect.PingPang
methodName=method

这样是不是很简单呢?以前封装就是为了不让访问,现在有了反射,还是可以访问了。赶紧试试吧,小伙伴们。

你可能感兴趣的:(java回忆录)