在实际的Android开发中,很少接触发射。最近使用MVVM重构MapTool app,view和viewModel通过泛型实现动态绑定并在base类中完成一些共有的初始化逻辑,由于base类在编译期并不确定泛型,所以无法按照常规的new对象的逻辑来获取viewModel对象,只能通过反射在运行时创建并初始化。
后来搜索java反射相关文档,看到了一句话“反射是框架的灵魂”,深有同感。看过很多的源码,包括LiveData,ViewModel 这些搭建MVVM的开源库,都不乏发射的影子。自己在实践过程中,又遇到了反射帮忙解决的问题。所以决定对反射做一个系统的总结。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
注意到,这里提到的一个关键点"动态获取",相较静态可以降低耦合性,所以极为适合搭建框架。
我们知道每一个.java文件,经过编译之后,都会生成一个或者多个.class文件。每一个对象对应一个.class文件,.class文件被JVM加载到内存中,会在堆区自动创建一个对应的class对象,这个对象就是java反射的基础。(这里我们先不展开对类加载过程的详解)
要想解剖一个类,必须先要获取到该类的字节码文件对象,而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象。
Class对象在java api中有专门的定义,java反射其实本质上是调用Class对象相关方法实现的。
获取Class对象有三种方式,
前两种能够拿到类对象没必要用反射,反射主要是使用第三种方式,拿到了类对象,你就可以为所欲为了。
反射基本上有三种常用场景:
static class Cat {
private String name;
private int age;
private boolean sex;
Cat(String name) {
this.name = name;
System.out.println("Default 构造方法, name = " + name);
}
public Cat() {
System.out.println("public 构造方法");
}
public Cat(int age) {
this.age = age;
System.out.println("public 构造方法, age = " + age);
}
public Cat(String name, int age) {
this.name = name;
this.age = age;
System.out.println("public 构造方法, name = " + name + ", age = " + age);
}
protected Cat(boolean sex) {
this.sex = sex;
System.out.println("protected 构造方法, sex = " + sex);
}
private Cat(String name, boolean sex) {
this.name = name;
this.sex = sex;
System.out.println("public 构造方法, name = " + name + ", sex = " + sex);
}
public void setName(String name) {
this.name = name;
System.out.println("公有方法:setName this.name = " + name);
}
private void showName() {
System.out.println("私有方法:showName this.name = " + name);
}
protected void setAge(int age) {
this.age = age;
System.out.println("保护方法:setAge this.age = " + age);
}
void showAge() {
System.out.println("default方法:showAge this.age = " + age);
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
根据类名获取构造方法,并创建对象,主要使用以下6个api:
如下代码和最终打印结果:
public class Main {
public static void main(String[] args) {
// write your code here
try {
System.out.println("获取类对象");
Class<?> clz = Class.forName("com.company.Main$Cat");//注意内部类用$连接
System.out.println("获取所有 public 构造方法");
Constructor<?>[] pubConstructors = clz.getConstructors();
for (Constructor<?> c : pubConstructors) {
System.out.println(c);
}
System.out.println("获取所有 构造方法(public,private,default,protected)");
Constructor<?>[] allConstructors = clz.getDeclaredConstructors();
for (Constructor<?> c : allConstructors) {
System.out.println(c);
}
System.out.println("获取 public 无参和带int参数构造方法,并实例化,打印toString");
try {
Constructor<?> noParamC = clz.getConstructor(null);//等价于不填参数
System.out.println("无参Constructor = " + noParamC);
try {
Object obj = noParamC.newInstance();
Cat cat = (Cat) obj;
System.out.println("打印无参 cat = " + cat.toString());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
try {
Constructor<?> paramIntC = clz.getConstructor(int.class);//不能使用Integer
System.out.println("int参数Constructor = " + paramIntC);
try {
Object obj = paramIntC.newInstance(100);
Cat cat = (Cat) obj;
System.out.println("打印int参数 cat = " + cat.toString());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
System.out.println("获取 private 构造方法,并实例化,打印toString");
try {
Constructor<?> privC = clz.getDeclaredConstructor(String.class, boolean.class);
try {
privC.setAccessible(true);
Object obj = privC.newInstance("xiaomi", false);
Cat cat = (Cat) obj;
System.out.println("打印String, boolean 参数 private cat = " + cat.toString());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出结果如下:
获取类对象
获取所有 public 构造方法
public com.company.Main$Cat(java.lang.String,int)
public com.company.Main$Cat()
public com.company.Main$Cat(int)
获取所有 构造方法(public,private,default,protected)
private com.company.Main$Cat(java.lang.String,boolean)
protected com.company.Main$Cat(boolean)
public com.company.Main$Cat(java.lang.String,int)
com.company.Main$Cat(java.lang.String)
public com.company.Main$Cat()
public com.company.Main$Cat(int)
获取 public 无参和带int参数构造方法,并实例化,打印toString
无参Constructor = public com.company.Main$Cat()
public 构造方法
打印无参 cat = Cat{name='null', age=0, sex=false}
int参数Constructor = public com.company.Main$Cat(int)
public 构造方法, age = 100
打印int参数 cat = Cat{name='null', age=100, sex=false}
获取 private 构造方法,并实例化,打印toString
public 构造方法, name = xiaomi, sex = false
打印String, boolean 参数 private cat = Cat{name='xiaomi', age=0, sex=false}
通过以上测试,有几点需要注意:
获取成员变量主要用到以下几个api:
测试代码和打印结果如下:
public class Main {
public static void main(String[] args) {
// write your code here
try {
System.out.println("获取类对象");
Class<?> clz = Class.forName("com.company.Main$Cat");//注意内部类用$连接
System.out.println("获取所有 pubic 字段");
Field[] pubFields = clz.getFields();
for (Field f : pubFields) {
System.out.println(f);
}
System.out.println("获取所有字段, public private protected Default");
Field[] allFields = clz.getDeclaredFields();
for (Field f : allFields) {
System.out.println(f);
}
System.out.println("获取某一public 字段,并使用");
try {
Field pubF = clz.getField("name");
Object obj = clz.getConstructor().newInstance();
pubF.set(obj, "小咪咪");
System.out.println(obj.toString());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
System.out.println("获取某一 private 字段,并使用");
try {
Field privF = clz.getDeclaredField("sex");
Object obj = clz.getConstructor().newInstance();
privF.setAccessible(true);
privF.set(obj, true);
System.out.println(obj.toString());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
打印结果如下:
获取类对象
获取所有 pubic 字段
public java.lang.String com.company.Main$Cat.name
获取所有字段, public private protected Default
public java.lang.String com.company.Main$Cat.name
int com.company.Main$Cat.age
private boolean com.company.Main$Cat.sex
获取某一public 字段,并使用
public 构造方法
Cat{name='小咪咪', age=0, sex=false}
获取某一 private 字段,并使用
public 构造方法
Cat{name='null', age=0, sex=true}
获取方法主要使用以下api:
注意:这里getDeclaredMethod之所以没有获取父类的private方法,是因为子类看不到,这是符合逻辑的。
测试代码和输出结果如下:
public class Main {
public static void main(String[] args) {
// write your code here
try {
System.out.println("获取类对象");
Class<?> clz = Class.forName("com.company.Main$Cat");//注意内部类用$连接
System.out.println("获取所有 public 方法");
Method[] pubMs = clz.getMethods();//包含从父类继承的所有public方法
for (Method m : pubMs) {
System.out.println(m);
}
System.out.println("获取所有 方法, private public protected default");
Method[] allMs = clz.getDeclaredMethods();//只包含本类内声明的方法
for (Method m : allMs) {
System.out.println(m);
}
System.out.println("获取public setName 方法,并调用");
try {
Method setName = clz.getMethod("setName", String.class);
System.out.println(setName);
Object obj = clz.getConstructor().newInstance();
setName.invoke(obj, "大咪咪");
System.out.println(obj.toString());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
System.out.println("获取private showName 方法,并调用");
try {
Method showName = clz.getDeclaredMethod("showName");
System.out.println(showName);
Object obj = clz.getConstructor().newInstance();
showName.setAccessible(true);
showName.invoke(obj);
System.out.println(obj.toString());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
System.out.println("获取protected setAge 方法,并调用");
try {
Method setAge = clz.getDeclaredMethod("setAge", int.class);
System.out.println(setAge);
Object obj = clz.getConstructor().newInstance();
setAge.invoke(obj, 123);
System.out.println(obj.toString());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出打印如下:
获取类对象
获取所有 public 方法
public java.lang.String com.company.Main$Cat.toString()
public void com.company.Main$Cat.setName(java.lang.String)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
获取所有 方法, private public protected default
public java.lang.String com.company.Main$Cat.toString()
public void com.company.Main$Cat.setName(java.lang.String)
void com.company.Main$Cat.showAge()
private void com.company.Main$Cat.showName()
protected void com.company.Main$Cat.setAge(int)
获取public setName 方法,并调用
public void com.company.Main$Cat.setName(java.lang.String)
public 构造方法
公有方法:setName this.name = 大咪咪
Cat{name='大咪咪', age=0, sex=false}
获取private showName 方法,并调用
private void com.company.Main$Cat.showName()
public 构造方法
私有方法:showName this.name = null
Cat{name='null', age=0, sex=false}
获取protected setAge 方法,并调用
protected void com.company.Main$Cat.setAge(int)
public 构造方法
保护方法:setAge this.age = 123
Cat{name='null', age=123, sex=false}