Java反射机制

反射的定义

Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
用一句话总结就是反射可以实现在运行时可以知道任意一个类的属性和方法。

反射的优点和缺点

反射是java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接。但是反射使用不当会成本很高!
为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念。

  • 动态编译:

动态编译的可执行文件需要附带一个的动态链接库,在执行时,需要调用其对应动态链接库中的命令。
所以,其优点一方面是缩小了执行文件本身的体积,另一方面是加快了编译速度,节省了系统资源。
缺点一是哪怕是很简单的程序,只用到了链接库中的一两条命令,也需要附带一个相对庞大的链接库;二是如果其他计算机上没有安装对应的运行库,则用动态编译的可执行文件就不能运行。


  • 静态编译:

静态编译就是编译器在编译可执行文件的时候,将可执行文件需要调用的对应动态链接库(.so)中的部分提取出来,链接到可执行文件中去,使可执行文件在运行的时候不依赖于动态链接库。所以其优缺点与动态编译的可执行文件正好互补。

优点

可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。

缺点

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

Class类和类类型

想要了解反射首先理解一下Class类,它是反射实现的基础。

类是java.lang.Class类的实例对象,而Class是所有类的类(There is a class named Class)

在我们开发过程中,经常会使用到一个类的class属性(特别是使用log4j时,都会在每个类前添加LoggerFactory.getLogger(ClassName.class);),但是我们并没有定义过这个属性,那它获取的是什么呢?其实,我们创建的每一个类都隐含一个静态成员变量class,通过class静态成员变量获取是Class对象,这就是类类型,顾名思义,就是所有类的类型。也就是描述一个类是什么,都有哪些东西,所以我们可以通过类类型知道一个类的属性和方法,并且可以调用一个类的属性和方法,这就是反射的基础。

获取类类型方法

对于普通的对象,我们一般都会这样创建和表示:

Code code1 = new Code();

上面说了,所有的类都是Class类的对象,那我们可不可以想下面这种方式来表示呢

Class c = new Class();

我们查看Class的源码时,是这样写的:

    /*
     * Private constructor. Only the Java Virtual Machine creates Class objects.
     * This constructor is not used and prevents the default constructor being
     * generated.
     */
    private Class(ClassLoader loader) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
    }

可以看到,其构造器是私有的,只有JVM才能创建Class对象,因此不能通过new来构造class对象。但我们可以通过以下三种方式获取(以Employee为例)

public static void main(String[] args) throws ClassNotFoundException {
    //第一种 调用隐含的静态成员属性class
    Class c1 =  Employee.class;

    //第二种 通过类对象的getClass()成员方法
    Employee employee = new Employee();
    Class c2 = employee.getClass();

    //第三种 使用Class调用forName方法,通过一个类的全类名获得
    Class c3 = Class.forName("com.dolphin.bean.Employee");

    System.out.println(c1.getName());
    System.out.println(c2.getName());
    System.out.println(c3.getName());
}

执行结果:

com.dolphin.bean.Employee
com.dolphin.bean.Employee
com.dolphin.bean.Employee

可以看出,c1、c2、c3都是Class的对象,他们是完全一样的,而且有个学名,叫做Employee的类类型(class type)。

Java反射相关操作
获取成员方法
Method[] methods = c1.getDeclaredMethods();//获取当前类的所有类方法,不包括父类的
Method[] methods2 = c1.getMethods();//获取当前类的所有类方法,包括父类的

完整示例(获取test方法)

//Employee test 方法
public void test(String id) {
    System.out.println("empId="+id);
}


public static void main(String[] args) throws Exception {
    Class c1 =  Employee.class;
    Object o = c1.newInstance();
    Method method = c1.getMethod("test",String.class);
    method.invoke(o, "1asqwq23");
}

执行结果

empId=1asqwq23
获取成员变量
Field[] fields2 = c1.getDeclaredFields();//获取当前类所有成员变量,不包括父类
Field[] fields = c1.getFields();//获取当前类所有成员变量,包括父类

完整示例

public static void main(String[] args) throws Exception {
    Class c1 =  Employee.class;
    Object o = c1.newInstance();
    Field field = c1.getDeclaredField("empName");//因为msg变量是private的,所以不能用getField方法
    field.setAccessible(true);//设置是否允许访问,因为该变量是private的,所以要手动设置允许访问,如果msg是public的就不需要这行了
    field.set(o, "Tom");//给属性赋值
    Object msg = field.get(o);//获取属性值
    System.out.println(msg);//Tom
}
获取构造函数
Constructor[] constructors = c1.getDeclaredConstructors();//获取当前类的所有构造方法,不包括父类
Constructor[] constructors2 = c1.getConstructors();//获取当前类的所有构造方法,包括父类

完整示例

Constructor constructor = c1.getConstructor(String.class,String.class,char.class);
Object emp = constructor.newInstance("123123","Sam",'F');
System.out.println(emp);

执行结果

Employee [empId=123123, empName=Sam, gender=F]
通过反射了解集合泛型的本质

首先下结论

Java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译到了运行期就无效了。

下面通过一个实例来验证

/**
 * 泛型集合本质
 */
public static void GenericEssence() {
    List list1 = new ArrayList(); // 没有泛型 
    List<String> list2 = new ArrayList<String>(); // 有泛型

    /*
     * 1.首先观察正常添加元素方式,在编译器检查泛型,
     * 这个时候如果list2添加int类型会报错
     */
    list2.add("hello");
//      list2.add(20); // 报错!list2有泛型限制,只能添加String,添加int报错
    System.out.println("list2的长度是:" + list2.size()); // 此时list2长度为1

    /*
     * 2.然后通过反射添加元素方式,在运行期动态加载类,首先得到list1和list2
     * 的类类型相同,然后再通过方法反射绕过编译器来调用add方法,看能否插入int
     * 型的元素
     */
    Class c1 = list1.getClass();
    Class c2 = list2.getClass();
    System.out.println(c1 == c2); // 结果:true,说明类类型完全相同

    // 验证:我们可以通过方法的反射来给list2添加元素,这样可以绕过编译检查
    try {
        Method m = c2.getMethod("add", Object.class); // 通过方法反射得到add方法
        m.invoke(list2, 20); // 给list2添加一个int型的,上面显示在编译器是会报错的
        System.out.println("list2的长度是:" + list2.size()); // 结果:2,说明list2长度增加了,并没有泛型检查
    } catch (Exception e) {
        e.printStackTrace();
    }
}

执行结果

list2的长度是:1
true
list2的长度是:2

综上可以看出,在编译器的时候,泛型会限制集合内元素类型保持一致,但是编译器结束进入运行期以后,泛型就不再起作用了,即使是不同类型的元素也可以插入集合。

参考连接:
http://tengj.top/2016/04/28/javareflect/
https://www.zhihu.com/question/24304289

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值