什么是反射
(1)Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制.
(2)反射 (Reflection) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查, 也称自审,并能直接操作程序的内部属性.例如,使用它能获得 Java 类中各成员的名称并显示出来.
(3)Java 的这一能力在实际应用中应用得很多,在其它的程序语言中根本就不存在这一特性.例如,Pascal,C 或者 C++ 中就没有办法在程序中获得函数定义相关的信息.
(4)JavaBean 是类反射的实际应用之一,它能让一些工具可视化的操作软件组件.这些工具通过类反射动态的载入并取得 Java 组件 (类) 的属性.后面学习的各种框架,基本上都会有反射的使用.
使用反射的基本步骤
用于反射的类,如 Method,可以在 java.lang.reflect 包中找到.使用这些类的时候必须要遵循三个步骤:
第一步:获得你想操作的类的 java.lang.Class 对象.在运行中的 Java 程序中,用 java.lang.Class 类来描述类和接口等.
第二步:调用诸如 getDeclaredMethods 的方法,取得该类中定义的所有方法的列表.
第三步:使用反射的 API 来操作这些信息.
认识 java.lang.Class 类
在面向对象的世界里,万事万物皆对象.
那么类(java 语言中,静态的成员,普通数据类型除外) 是不是对象呢
类是 (哪个类的对象呢?) 谁的对象呢
There is a class named Class .
定义的每一个类都是对象,类是 java.lang.Class 类的实例对象.
定义一个 Foo 类
package com.demo.reflect;
class Foo{
public void print(){
System.out.println("Foo");
}
}
Foo 类的实例对象表示方式:
Foo f = new Foo();
//f 就可以表示了 Foo 类的一个对象,这个表示很清晰明了.
定义的 Foo 类也是一个对象,是 java.lang.Class 的对象.
查看源码发现,java.lang.Class 类没有公共构造方法.Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个 Class 对象.
那么问题来了:怎么表示这个对象呢?
在 java 语言中,任何一个类都是 Class 的实例对象,这个实例对象有三种表示方式.
第一种表示方式: 类. class 告诉我们任何一个类都有一个隐含的静态成员变量 class
Class c1 = Foo.class;
第二中表达方式 : 通过该类的对象调用 getClass() 方法
Class c2 = foo1.getClass();
第三种表达方式 : 使用 Class 类的中静态 forName('类的全称') 方法获得与字符串对应的 Class 对象
Class c3 = Class.forName("com.demo.reflect.Foo");
在 java 官网中说明:c1 ,c2,c3 表示了 Foo 类的类类型 (class type).
代码测试 c1\c2\c3 是否代表同一个对象?
System.out.println(c1==c2);//true
System.out.println(c1==c3);//true
根据输出结果,表示一个类只可能是 Class 类的一个实例对象.
实际情况下:虚拟机为每种类型管理一个独一无二的 Class 对象.也就是说,每个类(型)都有一个 Class 对象.运行程序时,Java 虚拟机 (JVM) 首先检查是否 所要加载的类对应的 Class 对象是否已经加载.如果没有加载,JVM 就会根据类名查找. class 文件,并将其 Class 对象载入.
小结:万事万物皆对象,类也是对象,是 Class 类的实例对象,这个对象我们称为该类的类类型(不仅仅代表是类或者接口).
备注:基本的 Java 类型(boolean,byte,char,short,int,long,float 和 double)和关键字 void 也都对应一个 Class 对象. 每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象.
细说:Class.forName(String className)
Class.forName() 不仅表示了类的类类型,还代表了动态加载类.
那什么是静态加载和动态加载?
动态加载:程序在运行时调用相应方法,即使其他方法是错误的,程序依旧会执行.通过动态加载可以让程序的可延长性大大提升,对以后的维护和扩展有重要意义.
静态加载:程序在编译时执行.在执行过程中加载所有可能执行到的程序.在这种加载方式下,只要加载中一个方法出错,程序就不能运行.我们一般写程序默认的是静态加载.
通过下面案例进行分析:
public class Test{
public static void main(String[] args) {
// 静态加载.编译时加载,编译时就需要加载全部可能使用到的的类,一个类出错,其他类的功能都不能得到执行
if ("Word".equals(args[0])) {
Word Word = new Word();
System.out.println("Word");
}
if ("Excel".equals(args[0])) {
Excel excel = new Excel();
System.out.println("Excel");
}
}
}
如果你不定义 Word 类和 Excel 类,则会出现编译错误.
若你想程序能运行,必须定义 Word 类和 Excel 类.
假想如下场景,Test 中有 100 个功能,你只想使用 A 功能,如果你使用的是静态加载的机制,你必须定义其余的 99 种功能才能使用 A 功能,如果使用动态加载机制,不仅不用定义 99 中功能,通过实现某种标准(继承某个接口),将大大方便了代码的编写.
创建一个接口:
public interface MyStandard{
public void print();
}
新建一个程序入口:
public static void main(String[] args) {
// 运行时加载,动态加载类
Class c1 = Class.forName(args[1]);
// 通过类类型加载对象.
// 返回的是包含 N 个 class 的接口,以免程序编译错乱
RunBetter rb = (RunBetter)c1.newInstance();
rb.print();
}
实现接口:
class Word implements MyStandard{
public void print(){
System.out.println("Word");
}
}
class Excel implements MyStandard{
public void print(){
System.out.println("A");
}
}
想要使用那个类就传入该类.
得到类的类类型,我们可以干什么呢?
Class 类里面提供了很多解析,操作类的方法,详情可以查看 API 文档.最重要的方法:Class.forName(String className)
得到类的构造方法
/**
* 打印对象的构造函数的信息
* @param obj
*/
public static void printConMessage(Object obj){
Class c = obj.getClass();
/*
* 构造函数也是对象
* java.lang. Constructor中封装了构造函数的信息
* getConstructors获取所有的public的构造函数
* getDeclaredConstructors得到所有的构造函数
*/
//Constructor[] cs = c.getConstructors();
Constructor[] cs = c.getDeclaredConstructors();
for (Constructor constructor : cs) {
System.out.print(constructor.getName()+"(");
//获取构造函数的参数列表--->得到的是参数列表的类类型
Class[] paramTypes = constructor.getParameterTypes();
for (Class class1 : paramTypes) {
System.out.print(class1.getName()+",");
}
System.out.println(")");
}
}
得到类的属性
/**
* 获取成员变量的信息
* @param obj
*/
public static void printFieldMessage(Object obj) {
Class c = obj.getClass();
/*
* 成员变量也是对象
* java.lang.reflect.Field
* Field类封装了关于成员变量的操作
* getFields()方法获取的是所有的public的成员变量的信息
* getDeclaredFields获取的是该类自己声明的成员变量的信息
* field.setAccessible(true); 破除封装,属性强制可见
*/
//Field[] fs = c.getFields();
Field[] fs = c.getDeclaredFields();
for (Field field : fs) {
//得到成员变量的类型的类类型
Class fieldType = field.getType();
String typeName = fieldType.getName();
//得到成员变量的名称
String fieldName = field.getName();
System.out.println(typeName+" "+fieldName);
}
}
得到类的方法
/*
* Method类,方法对象
* 一个成员方法就是一个Method对象
* getMethods()方法获取的是所有的public的函数,包括父类继承而来的
* getDeclaredMethods()获取的是所有该类自己声明的方法,不问访问权限
*/
Method[] ms = c.getMethods();//c.getDeclaredMethods()
for(int i = 0; i < ms.length;i++){
//得到方法的返回值类型的类类型
Class returnType = ms[i].getReturnType();
System.out.print(returnType.getName()+" ");
//得到方法的名称
System.out.print(ms[i].getName()+"(");
//获取参数类型--->得到的是参数列表的类型的类类型
Class[] paramTypes = ms[i].getParameterTypes();
for (Class class1 : paramTypes) {
System.out.print(class1.getName()+",");
}
System.out.println(")");
}
}
反射的基本操作
public class Book {
private String name;
private double price;
public void setPrice(double price) {
this.price = price;
}
public void setName(String name) {
this.name = name;
}
public Book(){
System.out.println("****无参数构造");
}
@Override
public String toString() {
return "Book [name=" + name + ", price=" + price + "]";
}
public Book(String name, double price) {
super();
this.name = name;
this.price = price;
}
}
Book 类
构造方法反射实例化对象
getConstructor(Class[])
返回当前 Class 对象表示的类的指定的公有构造子对象.
getConstructors()
返回当前 Class 对象表示的类的所有公有构造子对象数组.
getDeclaredConstructor(Class[])
返回当前 Class 对象表示的类的指定已说明的一个构造子对象.
getDeclaredConstructors()
返回当前 Class 对象表示的类的所有已说明的构造子对象数组.
@Test
public void testBookConstructor() throws Exception{
Class<?> cs = Class.forName("com.jcy.po.Book");
//调用类的无参构造器
Object obj = cs.newInstance();
Book b = (Book) obj;
b.setName("无参构造器");
System.out.println(b);
//调用类的有参构造器
Constructor constructor =cs.getConstructor( new Class [] { String.class,double.class});
//也可以 --> cs.getConstructor( String.class,double.class);
Book b2 = (Book)constructor.newInstance("有参构造器",100.00);
System.out.println(b2);
//输出结果:
//Book [name=无参构造器, price=0.0]
//Book [name=有参构造器, price=100.0]
}
反射操作属性
getDeclaredField(String)
返回当前 Class 对象表示的类或接口的指定已说明的一个域对象.
getDeclaredFields()
返回当前 Class 对象表示的类或接口的所有已说明的域对象数组.
getField(String)
返回当前 Class 对象表示的类或接口的指定的公有成员域对象.
getFields()
返回当前 Class 对象表示的类或接口的所有可访问的公有域对象数组.
@Test
public void testBookField() throws Exception{
Book book = new Book("反射书",10.00);
Class cs = book.getClass();
System.out.println(book);
Field [] fields = cs.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
Field field = cs.getDeclaredField("name");
//此处报错java.lang.IllegalAccessException
//私有属性不可以直接访问
//field.set("name", "强行赋值");
field.setAccessible(true);//设置该属性为可使用
field.set(book, "强行赋值");//不报错
System.out.println(book);
}
//输出结果:
Book [name=反射书, price=10.0]
name
price
Book [name=强行赋值, price=10.0]
方法反射的调用
getMethod(String, Class[])
返回当前 Class 对象表示的类或接口的指定的公有成员方法对象.
getMethods()
返回当前 Class 对象表示的类或接口的所有公有成员方法对象数组,包括已声明的和从父类继承的方法.
getDeclaredMethod(String, Class[])
返回当前 Class 对象表示的类或接口的指定已说明的一个方法对象.
getDeclaredMethods()
返回 Class 对象表示的类或接口的所有已说明的方法数组.
@Test
public void testBookMethod() throws Exception{
//获得类中的方法
//getMethod获取的是public的方法,包括继承的方法
//getDelcaredMethod自己声明的方法
//getMethod 根据方法名和方法的参数类类型得到对应方法
Class<?> cs = Class.forName("com.jcy.po.Book");
Book b = (Book) cs.newInstance();
//方法的反射操作
//m.invoke(b,"格林童话");方法的反射操作是用m对象来进行方法调用
//和b.setName(格林童话);调用的效果完全相同
//方法如果没有返回值返回null,有返回值返回具体的返回值
Method m =cs.getMethod("setName", String.class);
System.out.println(b);
m.invoke(b,"格林童话");
System.out.println(b);
Method[] ms = cs.getMethods();
for (Method method : ms) {
System.out.println(method.getName());
}
//输出结果:
/*
* Book [name=null, price=0.0]
Book [name=格林童话, price=0.0]
toString
setName
setPrice
wait
wait
wait
equals
hashCode
getClass
notify
notifyAll
*
*/
}
类反射调用图
来源: http://www.bubuko.com/infodetail-2475229.html