文章目录
前言
刚开始学习Java的时候,我就只是随便看了一点点,根本没有深入了解,反射这一章也是一点没看,因为我觉得可能反射不是那么重要,现在发现反射真的太重要了。很多优秀的开源框架都是通过反射完成的,反射被称为框架设计的灵魂。反射的重要性不言而喻,但是如果刚开始接触反射,完全不了解反射到底是干嘛的。我们也不要着急,反射入门还是比较简单。看了这篇文章,相信你就会对反射有了一个大概的了解。
记录一下学习笔记,希望可以对大家有所帮助。
在Java中,想要在运行时识别对象和类的信息,主要有两种方式:
- RTTI 它假定了我们在编译时已经知道了所有的类型
- 反射 它允许我们在运行时发现和使用类的信息
反射
先看一下网上关于反射的概念:
Java反射就是在运行状态中,
对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意方法和属性;
并且能改变它的属性。而这也是Java被视为动态语言的一个关键性质。
Java反射的功能是在运行时判断任意一个对象所属的类,
在运行时构造任意一个类的对象,
在运行时判断任意一个类所具有的成员变量和方法,
在运行时调用任意一个对象的方法,生成动态代理。
其实这么说还是比较让人懵逼的。我初学的时候,将反射先简单的理解为:反射就是获取类的信息。
学习新技术的时候不要一直纠结在一点上,有的时候遇到这种不好理解的定义先在心中有一个大致的概念,然后继续往前看,有了一定了解之后回头再看就比较容易理解。
一.反射机制
我们每天都在和代码打交道,一份代码从我们在键盘上输入,到运行结束得到结果,中间过程经历了什么,估计大部分人的回答就是:编辑java文件,经过javac编译,然后就直接创建对象了。
这比我还好一点,因为我之前都不知道java文件还要经过javac编译。
我们总是忽略了中间的Class类对象阶段
我们看一下下面这个图
需要了解一下,Source 源代码阶段是在磁盘上进行的,而Class 类对象阶段是在内存中进行的。
将类的组成部分封装为其它对象,这就是反射机制,也就是Class类对象阶段所做的事情,主要有三件事:
- 将类的成员变量封装成Filed对象
- 将类的构造方法封装成Constructor对象
- 将类的成员方法封装成Method对象
反射机制:将类的组成部分封装为其它对象
这么说是不是对反射有了一定的了解了呢?
如果还是迷迷糊糊的状态,我们再来看一下Java反射机制的一个应用,相信你了解了之后,肯定会说:“卧槽,反射这么牛逼?”
我们在编程的时候,几乎一直在接触反射,只是我太菜了一直没有发现。举一个简单的例子:
现在大家使用的编译器都会有自动提示的功能,比如我们定义一个字符串s,操作s的时候发现,编译器会将s的操作方法自动显示出来,使用的时候我们不必一个一个找方法了,非常快捷方便,但是大家有没有想过这些方法是如何得到的呢?
我没有学习反射之前肯定会说:String类中有这些方法,s是String的对象,自然可以操作这些方法。没有毛病啊!
但是我们的关注重点不是这个,而是编译器如何知道有这些方法的存在呢? 这就用到了反射的知识。
我们定义了一个字符串之后,系统会将字符串的字节码文件加载进内存,在内存中,有一个Class类对象,Class类对象将String的所有方法都抽取出来作为Method的对象并放入Method[] 数组中,使用时,将Method[] 数组中的成员抽取出来,显示在列表中即可。这就是一个反射的过程。
反射的好处:
(1) 可以在程序的运行过程中操作这些对象
(2) 可以解耦(降低程序耦合性),提高程序的可扩展性。
二.获取Class对象的三种方式
经过前面的解释,相信你对反射有了一定的了解,
获取Class 对象的三种方式:
- Class.forName(“全类名”) 将字节码文件加载进内存,返回Class对象
- 多用于配置文件,将类名定义在配置文件中,读取类名,加载类
- 比如JDBC
- 类名.class 类名的属性.class 来获取
- 多用于参数传递
- 对象.getClass() getClass()方法在Object中
- 多用于对象的获取字节码的方式
有这样一个Person类:
- 多用于对象的获取字节码的方式
package com.ahmu.yx.reflect;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(){
}
@Override
public String toString() {
return super.toString();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
获取Class对象:
package com.ahmu.yx.reflect;
public class ReflectClass {
public static void main(String[] args) throws Exception {
//1.Class.forName("全类名")
Class c1 = Class.forName("com.ahmu.yx.reflect.Person");
System.out.println(c1);
//类名.class
Class c2 = Person.class;
System.out.println(c2);
//对象.getClass()
Person p = new Person();
Class c3 = p.getClass();
System.out.println(c3);
//比较三个对象
System.out.println("c1 == c2: " + (c1 == c2));
System.out.println("c2 == c3: " + (c2 == c3));
System.out.println("c3 == c1: " + (c3 == c1));
}
}
结论
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class 对象都是同一个
这是一个静态方法,通过类名直接调用
Class.forName(“全类名”)
三种方式对应文件的三种不同状态
三.使用Class对象
前面说过了如何获取Class 对象,那么我们应该如何使用呢?
获取功能
获取成员变量
有这样一个X类,其中有四个成员变量a,b,c,d,分别有着不同的访问修饰权限
class X {
public String a;
protected String b;
String c;
private String d;
}
获取其成员变量的方法:
-
Field[] getFields()
获取所有public修饰的成员变量Class xClass = X.class; //获取所有public修饰的成员变量 Field[] fields = xClass.getFields(); for (Field field : fields) { System.out.println("使用getFields获得的成员变量:" + field); }
-
Field getField(String name)
获取指定名称的 public修饰的成员变量//获取名称为a的public修饰的成员变量 Class xClass = X.class; Field a = xClass.getField("a"); System.out.println("获取名称为a的public修饰的成员变量:" + a);
-
Field[] getDeclaredFields()
获取所有的成员变量,不考虑修饰符//获取所有成员变量,不考虑修饰符 Class xClass = X.class; Field[] declaredFields = xClass.getDeclaredFields(); System.out.