做完了第一版的图书管理系统, 决定这段时间静下心好好看一看书, 一边复习Java基础和Servlet&JSP, 再一边学一学框架, 写读书笔记将学习到的知识点记录下来.
反射是什么?
反射,一种计算机处理方式。是程序可以访问、检测和修改它本身状态或行为的一种能力。
这是百度里给出的概念, 可能大多数人跟我刚开始一样, 不是很理解这句话. 那么我们先从Java文件编译链接后生的可执行文件说起吧.
首先, 大家都明白, 一个.java文件经过编译链接会生成.class文件, 准确的来说, 应该是一个类经过编译会生成一个.class文件. 如果一个类里面除了主类还有其他类, 不管是内部类还是外部类, 每个类都会各自生成一个.class文件. 可能有人通过查看IDEA下classes目录会发现, 一个类经过编译链接后, 其内部类并不会生成独立的.class文件,但是实质上是会生成的, 只是IDEA下并没有显示出有这个.class文件而已, 我们在终端下来测试对比一下.
先来看一段Java代码:
public class test {
public static void main(String[] args){
System.out.println("test");
}
public class test1{
public void main(String[] args){
System.out.println("test1");
}
}
}
class test2{
public static void main(String[] args){
System.out.println("test2");
}
}
看一下它在IDEA下生成的.class文件
可以看到红色的目录下就是编译链接后生成的.class文件只有主类和外部类, 但事实上并非如此, 我们在终端下使用javac来编译一下试试看.
可以清楚地看到, 有三个.class文件, 内部类test1也被编译链接生成了一个.class文件.
分析这些的主要目的是想说, 每一个类都会生成对应的.class文件. 但是.class文件并不能直接运行在操作系统上, 它需要运行于JVM之上, 这也是为什么Java可以跨平台的原因. 那么Java的反射机制到底是什么呢? 简单点来讲, 就是反编译: .class -> .java. 也就是说, 我们可以通过反射机制, 从.class文件里去访问原来的.java文件里面类的属性方法, 甚至去创建新的对象.
在编程语言里, 有动态语言和静态语言之分, 动态语言是指程序在运行时可以改变其结构, 而静态语言不能. Java本身是属于静态语言, 它并不能在运行时改变程序的结构, 但是Java本身提供了反射机制, 这也使得Java具有了一丢丢动态语言的性质. 什么意思呢? 就是反射机制允许Java程序在运行时透过Reflection API取得任何一个已知名称的class的内部信息.
反射的功能和使用
好, 明白了反射是什么, 那我们来看看Java的反射机制都能干哪些事?
- 在运行时构造任意一个类的对象
- 在运行时创建新类对象
- 在运行时调用任一个对象的方法
- 在运行时判断任意一个对象所属的类
- 在运行时判断任意一个类所具有的成员变量和方法
在JDK中, 主要由以下类来实现Java反射机制, 这些类都位于java.lang.reflect包中.
Class类: 代表一个类
Field类: 代表一个类的成员变量(成员变量包括数据成员/方法成员/类成员等, 成员变量也称为类的属性)
Method类: 代表一个类的方法
Constructor类: 代表一个类的构造方法
Array类: 提供了动态创建数组, 以及访问数组的元素的静态方法
Proxy类和InvocationHandler接口: 提供了动态生成代理类以及实例的方法
那么下面我们来看一看如何使用反射.
1. 反射机制获取类
反射机制获取类有三种方法, 如下:
//第一种方式:
Class c1 = Class.forName("Employee");
//第二种方式:
//java中每个类型都有class 属性.
Class c2 = Employee.class;
//第三种方式:
//java语言中任何一个java对象都有getClass 方法
Employee e = new Employee();
Class c3 = e.getClass(); //c3是运行时类 (e的运行时类是Employee)
2. 创建对象
前面使用反射机制获取到了类, 那么现在就用获取到的类来创建对象.
//第一种方式
Class c =Class.forName("Employee");
//第二种方式
//创建此Class 对象所表示的类的一个新实例
Object o = c.newInstance(); //调用了Employee的无参数构造方法.
3. 获取属性
获取属性有获取所有属性和获取指定属性两种, 先来看一下获取所有属性.
//首先先获取整个类(以Integer这个类为例)
Class c = Class.forName("java.lang.Integer");
//然后获取该类的所有Field(属性)
Field[] fs = c.getDeclaredFields();
//定义字符串, 存储属性
StringBuffer sb = new StringBuffer();
//通过追加的方法,将每个属性拼接到此字符串中
//最外边的public定义
sb.append(Modifier.toString(c.getModifiers()) + " class " + c.getSimpleName() +"{\n");
//获取里边的每一个属性
for(Field field:fs){
sb.append("\t");//空格
sb.append(Modifier.toString(field.getModifiers())+" ");//获得属性的修饰符,例如public,static等等
sb.append(field.getType().getSimpleName() + " ");//属性的类型的名字
sb.append(field.getName()+";\n");//属性的名字+回车
}
sb.append("}");
System.out.println(sb);
下面是获取特定的属性.
我们通过传统方式和反射方式来对比看一下如何获取指定属性:
//传统方式:
/*
User u = new User();
u.age = 12; //set
System.out.println(u.age); //get
*/
//反射方式
//获取类
Class c = Class.forName("User");
//获取id属性
Field idF = c.getDeclaredField("id");
//实例化这个类赋给o
Object o = c.newInstance();
//打破封装
idF.setAccessible(true); //使用反射机制可以打破封装性,导致了java对象的属性不安全。
//set
//给o对象的id属性赋值"110"
idF.set(o, "110");
//get
System.out.println(idF.get(o));
4. 获取方法, 构造方法
这里关于获取方法和构造方法, 暂时先给出一张方法表, 具体细节等到下次再详细总结.
方法关键字 | 含义 |
---|---|
getDeclaredMethods() | 获取所有的方法 |
getReturnType() | 获得方法的放回类型 |
getParameterTypes() | 获得方法的传入参数类型 |
getDeclaredMethod(“方法名”,参数类型.class,……) | 获得特定的方法 |
构造方法关键字 | 含义 |
---|---|
getDeclaredConstructors() | 获取所有的构造方法 |
getDeclaredConstructor(参数类型.class,……) | 获取特定的构造方法 |
父类和父接口 | 含义 |
---|---|
getSuperclass() | 获取某类的父类 |
getInterfaces() | 获取某类实现的接口 |