Java 反射指:运行时对任意类都能查出全部属性和方法,对任意对象都能调用任意方法和属性。动态拿到这些信息、动态调用对象方法,就是语言层面的反射机制。
它依赖运行时由类加载器装入的 Class 对象:编译期看不到的信息都记在这份元数据里,程序才能在启动后按需装载、查看、生成对象并改写信息。远程方法调用、容器取出组件,都靠这一套。
一、反射背景:编译时与运行时
先把几个容易混的词分清。编译是把源程序翻成机器能执行的形式;在 Java 里就是把 .java 翻成 .class 字节码。这个过程叫编译时。运行时则是类加载器装入字节码,交给 JVM 解释执行。
编译型语言一次全部翻成二进制再跑,效率高、依赖编译器、跨平台弱;解释型语言翻一句执行一句,效率低、依赖解释器、跨平台强。Java 是既编译又解释。
类加载器负责把编译好的字节码送到校验器做安全检查,通过后再解释执行。运行时动态加载就是:真正用到一个类(有实例、调了静态成员)时才装它。通过 Class 对象在运行时操作类信息,像照镜子——所以叫反射。
面向对象里万事皆对象。类本身也是对象:它是 java.lang.Class 的实例。静态成员、基本类型不是普通对象,但对应的 Class 对象仍然存在。
二、三种拿到 Class 对象的方式
反射的入口永远是 Class。三种写法:
1.调用对象的getClass方法,返回该对象的Class对象; Class c=Object.getClass();
2.Class.forName(“类的名字”); Class c=Class.forName (“…”);
3.Class c=类名.class; Class c=String.class;
第一种走实例的 getClass();第二种 Class.forName 按全名动态装载(最常见);第三种 类名.class 在编译期就绑定字面量。
| 方式 |
写法 |
典型场景 |
| 实例方法 |
obj.getClass() |
手里已有对象 |
| 全名装载 |
Class.forName("pkg.Name") |
配置驱动、工厂、框架 |
| 类字面量 |
String.class |
编译期已知目标类型 |
三、reflect 包里的三类钥匙
java.lang.reflect 提供三把钥匙:
-
-
Constructor:构造器信息,以及创建实例的接口。
-
四、创建对象、调方法、取字段
用 Class 创建实例(无参构造):
Class c= Class.forName("类的全名");
Object o=c.newInstance();
Test1 t = Test1.class.newInstance();
查这个类属于哪个包:
Class c=Test1.class;
Package p;
p=c.getPackage();
按参数类型拿到方法再 invoke。先 newInstance 得到被访问对象,再准备参数类型数组和实参数组:
Class c = Class.forName(“类的全名”);//获得访问类的对象
Object obj=c.newInstance();//获得被访问类的实例对象
Class[] cs=new Class[]{String.class};//设置参数类型
Method ff=c.getMethod(“setName”,cs);//得到参数匹配的方法
Object[] values=new Object[]{“你好”};//设置需要传递的值 ff.invoke(obj,values);//映射
ff=c.getMethod("getName");
Object uuu= ff.invoke(obj);
动态调用带参静态方法时,invoke 的第一个实参可以是 Class 本身(静态方法不绑实例):
Class c = Class.forName("类的全名");
Class[] cs=new Class[]{String.class};
Method m=c.getMethod("setName",cs);
Object[] o=new Object[]{"aaa"};
m.invoke(c,o);
一次拿出全部属性、构造器和函数(含私有、静态):
Field[] ff = c.getDeclaredFields(); //无论静态还实体还私有皆可得到属性
Constructor[] ccc= c.getDeclaredConstructors(); //得到构造
Method[] mm=c.getDeclaredMethods();//无论静态还实体还私有皆可得到函数
按参数类型拿到构造器再 newInstance:
Class c = Class.forName(“类的全名");
Class[] cs=new Class[]{String.class};
Constructor cc= c.getConstructor(cs);
Object[] o=new Object[]{"ssssssssssssss"};
cc.newInstance(o);
getMethods() 含父类成员方法;getDeclaredMethods() / getDeclaredFields() 只看本类声明:
Method[] methods = classObj1.getMethods(); //获取所有成员方法包含 父类成员方法
Method[] methods = calze.getDeclaredMethods(); //获取类里所有成员方法
Field[] fields = claze.getDeclaredFields();//得到所有属性
Constructor[] constructors = claze.getConstructors();//得到所有构造方法
五、反射的优缺点
优点:提高灵活性和扩展性,降低耦合,程序不必提前写死目标类,就能创建和控制任意类的对象。
缺点:(1)性能——反射本质是解释执行,字段和方法访问远慢于直接代码,适合框架,普通业务不建议铺开。(2)可读性——绕过源码直接改结构,内部逻辑变糊,维护成本高,代码也更绕。
六、用反射实现工厂模式
工厂按类的全名 Class.forName 再 newInstance,调用方只依赖接口 Fruit,新增苹果/橘子不必改工厂分支:
package com.scxh.java.factory;
interface Fruit { //水果接口
public void eat() //吃水果
}
class Apple implements Fruit{ //定义苹果
public void eat() {
System.out.println("**吃苹果。");
}
}
class Orange implements Fruit{
public void eat() {
System.out.println("**吃橘子。");
}
}
class Factory{
public static Fruit getInstance(String className){
Fruit fruit = null
try{
fruit = (Fruit) Class.forName(className).newInstance()
}catch(Exception e ){
e.printStackTrace()
}
return fruit
}
}
public class FactoryDemo{
public static void main(String args[]){ //通过工厂类取得接口实例,传入完整的包.类名称
Fruit f = Factory.getInstance("com.scxh.java.factorydemo.Apple")
if(f!=null){ //判断是否取得接口实例
f.eat()
}
}
}
七、反射工具类:打印指定类的成员
下面工具一次性打印指定类的属性、构造方法和成员方法(示例输出见方法注释)。作者标识沿用原文 zhou.sir。
/**
* 反射机制实现: 打印指定类的属性、方法,构造方法
*
*
* @author zhou.sir
*
*/
public class ReflectUtil {
/**
输出结果:
String name
int age
int id
String sex
Student()
Student(String int int String )
String toString()
void playeGame()
void makeFriend()
void learn()
*/
public static void main(String[] args) throws ClassNotFoundException {
showDeclaredParameter(getClaze());
showConstruction(getClaze());
showDeclaredMethods(getClaze());
}
/**
* 获取类对象
* @throws ClassNotFoundException
*/
public static Class<?> getClaze() throws ClassNotFoundException{
Class<?> classObj1 = Student.class;
// Class classObj2 = new Dog().getClass();
// Class classObj3 = Class.forName("com.scxh.java1601.object.ex01.test.Student");
// Log.d(classObj1.getName());
return classObj1;
}
/**
* 显示成员方法头
* @param calze
*/
public static void showDeclaredMethods(Class calze){
// Method[] methods = classObj1.getMethods(); //获取所有成员方法包含 父类成员方法
Method[] methods = calze.getDeclaredMethods(); //获取类里所有成员方法
for (int i = 0, n = methods.length; i < n; i++) {
String methodName = methods[i].getName();
Class returnType = methods[i].getReturnType();
Class[] parameterType = methods[i].getParameterTypes();
Log.v(subString(returnType.getName()) + " " + methodName + "(");
for (Class p : parameterType) {
Log.v(subString(p.getName()) + " ");
}
Log.d(")");
}
}
public static void showDeclaredParameter(Class claze){
Field[] fields = claze.getDeclaredFields();//得到所有属性
for(Field f : fields){
String fieldName = f.getName();
Class parameterType = f.getType();
String ptName = subString(parameterType.getName());
Log.d(ptName+ "\t"+fieldName);
}
}
public static void showConstruction(Class claze){
Constructor[] constructors = claze.getConstructors();//得到所有构造方法
for(Constructor c : constructors){
String constructorName = c.getName();
Class[] parameterType = c.getParameterTypes();
Log.v(subString(constructorName) + "(");
for (Class p : parameterType) {
Log.v(subString(p.getName()) + " ");
}
Log.d(")");
}
}
public static String subString(String str){
String ptName = str;
if(ptName.contains(".")){
int lastIndex = ptName.lastIndexOf(".");
ptName = ptName.substring(lastIndex+1);
}
return ptName;
}
}
一句话总结:反射就是运行时的 Class 对象:用它创建实例、调方法、读字段;灵活,但慢,也更难维护。