认识Java的反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。此处对几个特性进行简单的总结(不包括数组的反射等高级特性)。

Class类的使用
Java动态加载类
通过反射机制获取成员及方法
通过invoke调用对象方法
通过反射了解集合泛型的本质

Class类的使用
在Java的世界里,或者说对象的世界里,万事万物皆对象,类也是对象,也是此片文章的中心,类的对象称为类类型(class type),Class对象(java.lang.Class)在Java反射机制里面必不可少,其十分强大。下面看一下其简单的使用,以下是一个demo(通过class获取对象实例,为方便观察,后面所有的实例均对exception进行了回避):
package org.devsong.reflectdemo;

public class ClassDemo {
    public static void main(String[] args) throws Exception {
        Foo foo = new Foo();

        // 获取类类型的方式有三种
        // 方式一(在已知对象实例的情况下)
        Class c = foo.getClass();
        // 方式二
        Class c1 = Foo.class;
        // 方式三
        Class c2 = Class.forName("org.devsong.reflectdemo.Foo");

        // 获取到类类型之后可以通过类类型获得对象实例(前提是该类要有无参的构造方法)
        Foo foo1 = (Foo) c2.newInstance();

        // 一个类的类类型只有一个,所以通过以上三个方式获得的类类型是等价的,所以以下结果为 true
        System.out.println(c == c1 && c == c2);
    }
}

class Foo {
    public Foo() {
        System.out.println("foo constructor...");
    }
}

Java动态加载类
类的加载有动态加载静态加载,通过new关键字实例化类的过程为静态加载,而通过反射机制得到类对象的过程为动态加载。静态加载在编译过程执行,而动态加载在程序执行且需要时才进行加载由于以下示例在IDE里面不好演示,所以均采用命令行来操作。

看下面的一个demo:假设现在需要开发一款绘图app,里面需要提供钢笔、铅笔、水彩笔等各种笔的效果,假设现在只实现了钢笔这一个效果,里面有一个draw方法:新建Pen.java:
public class Pen{
    public Pen(){

    }

    public void draw(){
        System.out.println("pen:draw...
");
    }
}
然后新建测试类:Demo.java
public class Demo {

    public static void main(String[] args) {
        if(args[0].equals("pen")) {
            Pen pen = new Pen();
            pen.draw();
        }else if(args[0].equals("pencil")) {
            Pencil pencil = new Pencil();
            pencil.draw();
        }
    }
}
然后用命令行先编译pen.java: javacPen.java 接着编译Demo.java: javac Demo.java 很显然编译不通过,找不到Pencil类。这就值得我们思考了,假设现在我只想用pen,而不想用pencil, pen已经完整开发结束,但是因为pencil没开发完成,导致我连Pen都用不了。这也暴露了静态加载类的弊端,假设现在需要开发100个功能,已经开发完99个,但是因为还有一个未开发完成,剩下的99个都不能使用,这显然是存在很大问题的。所以动态加载类的作用就出现了。 下面新建一个DrawAble接口:DrawAble.java
interface DrawAble{
    public void draw();
}
然后让所有需要实现的“笔”都实现该接口。最后改造一下Demo存为Demo1.java:
public class Demo1 {
    public static void main(String[] args) throws Exception{
        Class c = Class.forName(args[0]);
        DrawAble da = (DrawAble)c.newInstance();
        da.draw();
    }
}
保存并编译,可以发现编译通过,然后执行: java Demo1 Pen 可以在控制台看到输出了Pen:draw...。 完全没问题,然后执行java Demo1 pencil, 直到这一步才抛出异常:java.lang.ClassNotFoundException。要想拓展更多的笔,只需让新添加的笔实现DrawAble接口即可。这也就是动态加载的好处,在运行程序时才加载需要的类,编译过程完全不影响,再也不会出现前面的一个影响99个的情况。

通过反射机制获取成员及方法
class类用非常多的get方法,可以获取包括成员、方法等共有或私有的属性。使用方法大同小异,可以参看API文档,此处只是简单展示了获取一个类的所有方法,如下:
package org.devsong.reflectdemo;

import java.lang.reflect.Method;

import org.devsong.reflectdemo.util.ClassUtil;

public class ReflectDemo01 {

    public static void main(String[] args) {
        ReflectDemo01.getMethodList(new String("123"));
    }

    public static void getMethodList(Object obj) {
        Class c = obj.getClass();

        Method[] methods = c.getDeclaredMethods();
        for (Method m : methods) {
            System.out.println(m);
        }
    }
}
运行后可以看到控制台输出了String的所有方法。通过IDE我们也可以看到class还用非常多的get方法,可以获取非常多的信息,这里就不一一展示了。 文章正文图片

通过invoke调用对象方法
上面展示了获取方法,那么获取到了方法肯定是有调用的办法的。下面介绍以下invoke方法来执行对象的内部方法。依然是一个简单的例子,定义了一个类Foo1,其中实现了两个方法,下面展示如何通过Java反射机制调用这两个方法:
package org.devsong.reflectdemo;

import java.lang.reflect.Method;

public class ReflectDemo02 {

    public static void main(String[] args) throws Exception {
        Foo1 foo = new Foo1();
        Class c = foo.getClass();
        Method m = c.getMethod("sayHello", String.class);
        // 通过Method的invoke方法执行foo对象的方法
        m.invoke(foo, "jack");

        // 在获取方法和执行方法传参的时候,具有两种方式,由于是可变参数,所以可以传数组,也可以传单独的参数,效果是一致的
        // Method m1 = c.getMethod("say", new Class[] {String.class, String.class});       //通过数组
        Method m1 = c.getMethod("say", String.class, String.class);

        m1.invoke(foo, new String[] { "hello", "world" }); // 通过数组传递参数
        // m1.invoke(foo, "hello", "world"); //分别传递参数
    }
}

class Foo1 {
    public Foo1() {
    }

    public void sayHello(String name) {
        System.out.println("hello " + name);
    }

    public void say(String str1, String str2) {
        System.out.println(str1 + str2);
    }
}
可以明确的是确定的名称及确定的参数可以确定一个唯一的方法,所以getMethod方法第一个参数是我们需要的方法名,后面的不定参数是实际方法中包含的参数类类型,可以利用数组也可以分开依次填入,在获取和执行时都是如此。保存运行可以看到结果如预期,两个方法都被正常调用。


通过反射了解集合泛型的本质
这里借用ArrayList来展示,很明显,实际开发中如果给ArrayList规定了泛型,那么简单调用ArrayList的add方法是不能添加其他类型的对象进去的。而通过Java的反射机制,我们可以做一些正常手段不能操作的东西。例子如下,先给ArrayList指定泛型为String,我们用正常的方式往其中添加一个Integer数据的时候发现是行不通的,那就通过反射机制试试吧:
package org.devsong.reflectdemo;

import java.lang.reflect.Method;
import java.util.ArrayList;

public class ReflectDemo03 {

    public static void main(String[] args) throws Exception {
        ArrayList list = new ArrayList();
        list.add(123);
        list.add("sdsd");

        ArrayList list1 = new ArrayList<>();
        list1.add("hello");
        // list1.add(100); //错误的操作

        // 编译之后集合是去泛型化的,虽然list明确指明存放String,但是以下结果是true
        // java中集合的泛型是防止错误输入的,只在编译阶段有效,绕过编译就无效了
        System.out.println(list.getClass() == list1.getClass());

        // 利用反射机制,可以在list1中存入String之外的其他类型
        Class c = list1.getClass();
        Method m = c.getMethod("add", Object.class);

        m.invoke(list1, 100);
        System.out.println(list1);
    }

}
保存并运行后可以看到100成功添加到了list1中。这说明Java编译之后集合是去泛型化的,java中集合的泛型是防止错误输入的,只在编译阶段有效,绕过编译就无效了,所以我们能够将100添加进去而不抛出异常。
It's
欢迎访问本站,欢迎留言、分享、点赞。愿您阅读愉快!
*转载请注明出处,严禁非法转载。
https://www.devsong.org
QQ留言 邮箱留言
头像
引用:
取消回复
提交
涂鸦
涂鸦
热门