什么是反射?
反射是Java提供的SDK,它可以通过一系列方法,拿到类的实例,和类的各种属性。通过反射的方法,可以在不用new生成实例的情况下,进行各种类相关的操作。
通过反射可以拿到什么?
反射,操作的是一个类,也就是一个Class,我们通过这个Class生成类的实例,也可以通过这个Class拿到类定义的方法 属性 等。
通过反射生成类的实例
这里,我建立一个Person类,通过这个Person类,进一步讲解反射能拿到哪些东西。
public class Person {
private int id;
private int age;
private int sex;
private String name;
public Person(){}
public Person(int id) {
this(id, 18);
}
public Person(int id, int age) {
this(id, age, 1);
}
public Person(int id, int age, int sex) {
this(id, age, sex, "XiaoLi");
}
public Person(int id, int age, int sex, String name) {
this.id = id;
this.age = age;
this.sex = sex;
this.name = name;
}
}
首先,我们会考虑,拿到这个Person类以后,应该要生成一个Person的实例对象,因为一切的调用都是在建立在有这个实例对象的基础上的。那么,我们如何通过反射来拿到这个实例对象呢?有以下几种方法。
- 通过类的newInstance(),这个方法实例化一个Person对象。
Person xiaoming = Person.class.newInstance();
- 通过类名,使用Class.forName(“类的全名”),来生成我们的对象。
Person xiaoli = (Person) Class.forName("com.example.enjoy.reflect.Person").newInstance();
这里需要注意的是,我们Person类定义了一个无参数的构造函数,如果想要通过这种newInstance()方法来生成一个实例对象,那么这个类,就必须要有一个无参数的构造函数,如果没有定义无参数的构造函数,这里就会报这样的错误:
Caused by: java.lang.NoSuchMethodException: com.example.enjoy.reflect.Person.<init>()
这个错误的意思就是没有找到无参数的构造方法。
那么,如果想要生成实例对象的这个类,没有定义无参数的构造方法,我们可不可以生成实例呢?答案是可以的,需要通过反射拿到类的构造方法,通过构造方法生成一个实例对象,调用这个通过构造方法生成的实例对象的方法,就可以生成你想要类型的实例对象。
- 通过反射构造方法生成实例对象。
Constructor<Person> constructor = Person.class.getConstructor(int.class);
Person xiaohong = constructor.newInstance(1);
这里getConstructor(),参数是可变参数,也就是说根据你想要获得的构造函数的参数类型和个数,传值即可。比如,这里我想要通过4个参数的构造方法,生成一个Person对象,那就可以这样传值。
Constructor<Person> constructor1 = Person.class.getConstructor(int.class, int.class, int.class, String.class);
Person xiaolong = constructor1.newInstance(1, 22, 0, "Xiao Long");
获得类的成员变量
这里,我们看到,Person这个类定义了4个成员变量,id,age,sex,name。由于我们没有生成get set方法,这里除了构造函数定义了这4个参数的值,我们还可不可以在使用的过程中修改他们呢?在正常的使用中,是没有办法的,因为由于成员变量的属性是私有的,我们没办法通过实例对象点出这几个变量,没办法修改。但是通过反射获取这几个成员变量,我们还是可以做到修改的。
Constructor<Person> constructor1 = Person.class.getConstructor(int.class, int.class, int.class, String.class);
Person xiaolong = constructor1.newInstance(2, 22, 0, "Xiao Long");
System.out.println(xiaolong);
Field age = Person.class.getDeclaredField("age");
age.setAccessible(true);
age.set(xiaolong, 24);
System.out.println(xiaolong);
这里,我们在创建好了xiaolong这个实例对象以后,发现age这里录入错误了,那么我们就通过反射的方法获得age这个变量的引用,通过反射的sdk改变age的值。
这里我们使用的是getDeclaredField,还有一个方法是getField,这两个方法的区别是什么呢?接下来,我们再定义一个Student类,然后继承自Person类,我们看一看这两个方法有什么区别。
public class Student extends Person {
private int cls;
private int grade;
}
这里我们定义了两个变量,cls(班级),和grade。然后建立一个Student对象。
Constructor<Student> constructor2 = Student.class.getConstructor(int.class, int.class);
Student xiaoming = constructor2.newInstance(1, 1);
这里我们调用这两个方法,打印出来。
for (Field field : Student.class.getFields()) {
System.out.println("getFields=" + field.getName());
}
for (Field field : Student.class.getDeclaredFields()) {
System.out.println("getDeclaredFields=" + field.getName());
}
结果发现:
getDeclaredFields=cls
getDeclaredFields=grade
getFields并没有拿到任何东西,getDeclaredFields拿到了我们定义的cls和grade。
我们修改一下Person类中name成员的限定符,将private改成protect,再运行一下看看。
结果发现和上面的一样,我们再次将protect改成public,再运行一次。
getFields=name
getDeclaredFields=cls
getDeclaredFields=grade
结果发现,拿到了name。但是getDeclaredFields还是没有拿到name。这里我们明白了这两个方法的作用。
getFields方法拿到的只能是public的,可以是父类的public成员。
getDeclaredFields方法拿到的是本类的任何成员。
最近在做的一个项目,就是利用反射改变了已经由xml绑定好了数据的Spinner的数据源。
/**
* set adapter source of charge spinner by reflect method
*/
private fun initChargeSpinner(){
viewModel.chargeList.setAll(chargeList)
val adapter = charge_spinner.adapter
val declaredField = adapter.javaClass.getDeclaredField("mObjects")
declaredField.isAccessible = true
declaredField.set(adapter, viewModel.chargeList)
}
由于项目需要,这个Spinner的数据需要我们动态修改,这里由于SpinnerAdapter或是ArrayAdapter都没有提供给我们任何修改数据的方法,所以这里查看源码后找到,实际上数据是放在mObjects这个变量里的,那么,我们就通过反射拿到这个变量的引用。从而将数据绑定到我们的ObservableArrayList上,这样就达到了项目的要求。
获得类的方法
这里,我们在Person类里定义一个speak方法。
private void speak() {
System.out.println("I am " + name + " .My age is " + age + " .I am a " + (sex == 0 ? "man" : "women"));
}
由于方法是私有的,所以不能通过实例对象来直接调用,但是还是可以通过反射的方法来调用。
Method speak = xiaolong.getClass().getDeclaredMethod("speak");
speak.setAccessible(true);
speak.invoke(xiaolong);
这里通过getDeclaredMethod这个方法,传入方法名,获得了方法的实例Method,通过方法的invoke方法就可以进行调用了。这里的invoke()参数,需要传入你想要调用的实例对象,还有方法的参数。
与Field一样,这里同时存在getDeclaredMethod和getMethod,作用是一样的,这里就不再进行讨论了。
反射与注解
我们定义一个注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Yong {
}
然后在Student这个类上使用他,表明这个类是Yong的这样一个属性。
@Yong
public class Student extends Person {
private int cls;
private int grade;
public Student(int cls, int grade) {
this.cls = cls;
this.grade = grade;
}
@Override
public String toString() {
return "Student{" + cls +
"年" + grade +
"班}";
}
}
然后我们这样使用
List<Person> personList = new ArrayList<>();
personList.add(new Teacher());
personList.add(xiaoming);
personList.add(xiaoli);
personList.add(xiaohong);
personList.add(xiaolong);
for (Person person : personList) {
for (Annotation annotation : person.getClass().getDeclaredAnnotations()) {
if (annotation instanceof Yong) {
System.out.println("yong person !!! " + person);
}
}
}
这里,通过循环遍历person集合,拿到person的具体类型,通过getDeclaredAnnotations拿到所有注解的集合,然后再经过一次循环遍历,通过instanceof方法来判断,类的上边有没有使用这个注解。那么,我们还有没有其他办法,可以判断Person的子类有没有定义Yong这个注解呢,看下面这段应用:
List<Person> personList = new ArrayList<>();
personList.add(new Teacher());
personList.add(xiaoming);
personList.add(xiaoli);
personList.add(xiaohong);
personList.add(xiaolong);
for (Person person : personList) {
if (person.getClass().isAnnotationPresent(Yong.class)) {
System.out.println("yong person !!! " + person);
}
}
这里改成使用isAnnotationPresent这个方法,就可以判断类的上边有没有这个注解。
当然,这里只是反射拿到注解的一种简单应用,实际上我们有时候不知道这个注解的具体类型,但还是可以通过反射去做一些事情的。
反射与泛型
这里我们定义几个类:
public class Bag<T> {
}
public class Ball {
public void kick(){
System.out.println("roll roll roll");
}
}
public class Book {
public void read(){
System.out.println("knowledge is power");
}
}
public class Student<T> {
private Bag<T> bag;
private Type type;
public Student() {
Type genericSuperclass = getClass().getGenericSuperclass();
System.out.println(genericSuperclass);
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
type = actualTypeArguments[0];
}
public Type getType() {
return type;
}
}
这里,我们定义一个书包类,一个球类,一个书类,最后学生类这里我们使用一个泛型。
首先,在构造方法里通过getGenericSuperclass拿到这个类还有类的泛型信息,然后将其转成ParameterizedType,这个类的意思就是含有具体的泛型的类型信息。然后通过getActualTypeArguments这个方法,拿到具体的泛型的类型信息,这里返回一个数组,因为泛型可以是多个。
然后我们调用这个Student类。
public static void main(String[] args) throws Exception {
Student<Book> student = new Student<Book>(){};
Type type = student.getType();
Class<?> cls = (Class<?>) type;
if (cls == Book.class) {
Book book = Book.class.newInstance();
book.read();
} else if (cls == Ball.class) {
Ball ball = Ball.class.newInstance();
ball.kick();
}
}
这里通过匿名内部类的方式创建了一个Student的子类。因为只有通过这种方式才可以拿到泛型的具体类型信息。
将我们的Student反编译成字节码可以看到:
// class version 51.0 (51)
// access flags 0x30
// signature Lcom/example/enjoy/enjoy/reflect/beans/Student<Lcom/example/enjoy/enjoy/reflect/beans/Book;>;
// declaration: com/example/enjoy/enjoy/reflect/EnjoyReflect$1 extends com.example.enjoy.enjoy.reflect.beans.Student<com.example.enjoy.enjoy.reflect.beans.Book>
final class com/example/enjoy/enjoy/reflect/EnjoyReflect$1 extends com/example/enjoy/enjoy/reflect/beans/Student {
// compiled from: EnjoyReflect.java
OUTERCLASS com/example/enjoy/enjoy/reflect/EnjoyReflect main ([Ljava/lang/String;)V
// access flags 0x8
static INNERCLASS com/example/enjoy/enjoy/reflect/EnjoyReflect$1 null null
// access flags 0x0
<init>()V
L0
LINENUMBER 11 L0
ALOAD 0
INVOKESPECIAL com/example/enjoy/enjoy/reflect/beans/Student.<init> ()V
RETURN
L1
LOCALVARIABLE this Lcom/example/enjoy/enjoy/reflect/EnjoyReflect$1; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
}
这里生成的匿名内部类,相当于是把Student这个类的具体类型信息给具象化了。这里的signature表示,在字节码中记录了泛型的类型信息。
而如果不适用匿名内部类,只使用简单的对象的话,由于类型的擦除,被擦除成了Object了,所以我们是拿不到任何泛型的类型信息的。
反射与动态代理
讲到动态代理,首先了解一下什么是代理。代理是一种Java的设计模式,通过执行代理类中的方法,来实现具体想要执行类中的方法。
静态代理
还是通过代码来简单了解一下:
//房屋管理所
public interface HouseManger {
void buy();
void sell();
}
//中介商
public class HouseProxy implements HouseManger {
private XiaoLi xiaoLi;
public HouseProxy() {
xiaoLi = new XiaoLi();
}
@Override
public void buy() {
System.out.println("give me $10,000 for service");
xiaoLi.buy();
System.out.println("thank you");
}
@Override
public void sell() {
xiaoLi.sell();
System.out.println("give me $50,000 for service");
}
}
//小莉(房子的持有者)
public class XiaoLi implements HouseManger {
@Override
public void buy() {
System.out.println("I want to sell my house");
}
@Override
public void sell() {
System.out.println("");
}
}
public static void main(String[] args) {
HouseProxy houseProxy = new HouseProxy();//只有中介商的联系方式
houseProxy.buy();
}
这里中介商就充当了代理模式中的代理角色。由于这里我只有中介商的联系方式,所以我只能跟他买房子。但是中介商有各种房源,他可以找到真正卖房子的这个人。那么这里定义了一个房屋管理所这个接口,这个接口定义了两个方法:买房子和卖房子。中介和小莉同时实现了这个接口,但是真正持有房子的只有小莉这个对象。这种设计模式就叫做代理模式。可以避免和真正执行这个方法的对象进行接触。
代理模式的好处就是可以监控方法的运行,比如在这里,我在执行买房子的前后分别加入了一些打印语句。
动态代理
静态代理有没有什么缺陷呢,或者说他的限制在哪里呢。
这里举一个例子,小莉名下除了房子还有汽车,这时候小莉要卖掉自己的汽车。
由于这个中介商只能代理房屋的买卖,没法代理汽车的买卖,所以我还要再建一个可以买卖汽车的代理。
这样,如果使用静态代理,一个代理是没办法代理多个接口的,这就是静态代理的缺陷。
所以,我们引入了动态代理的概念,一个可以代理所有的接口代理类。
下面通过代码来创建一个既可以代理房屋买卖,又可以代理汽车买卖的中介商:
// 汽车管理所
public interface CarManager {
void buyCar();
void sellCar();
}
// 小莉除了房子还有汽车可以卖
public class XiaoLi implements HouseManger, CarManager {
@Override
public void buy() {
System.out.println("I want to sell my house");
}
@Override
public void sell() {
System.out.println("sell my house");
}
@Override
public void buyCar() {
System.out.println("I want to sell my car");
}
@Override
public void sellCar() {
}
}
// 建立一个计算接口
public interface Calculate {
int add(int a, int b);
}
// 小明实现了计算接口,但是他比较糊涂,总是算错
public class XiaoMing implements Calculate {
@Override
public int add(int a, int b) {
return a + b + 1;
}
}
// 建立一个万能的代理
public class AllProxy {
private Object proxy;
private XiaoLi xiaoLi;
private XiaoMing xiaoMing;
public AllProxy() {
xiaoLi = new XiaoLi();
proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{CarManager.class, HouseManger.class}, new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
if (method.getName().equals("buy")) {
xiaoLi.buy();
}
if (method.getName().equals("buyCar")) {
xiaoLi.buyCar();
}
if (method.getName().equals("add")) {
return xiaoMing.add((int) objects[0], (int) objects[1]);
}
return null;
}
});
}
public Object getProxy() {
return proxy;
}
}
动态代理是通过sdk的Proxy类的newProxyInstance方法创建的,创建出来的对象我们用一个Object类型来接收。
方法需要传入三个参数
ClassLoader(类加载器) : 获得当前类的ClassLoader即可。
Class[]:这里传入的是一个Class类的集合,这个就是你要实现的接口的class类,这里只能传入接口,不可以传入Class类。
InvocationHandler:这里就是动态方法的关键之所在,这里我们需要新建一个匿名内部类实现他的方法invoke。
稍后来讲invoke的三个参数。这里重点讲一下invoke方法的调用机制。
在使用生成的这个Object类型的动态代理对象时,调用任何一个方法,都会回调这个invoke方法,也就是说我们实际在处理方法的调用的时候,是在这个方法里进行各种运算的。invoke有三个参数
Object o:就是我们生成的这个动态代理对象。
Method method:这个是我们调用了哪个方法。
Object[] objects:调用动态代理对象的方法所传入的参数。
返回值:就是调用这个方法的时候,生成的返回值,如果没有返回值就传空。
这里调用了小明做加法,所以返回了经过xiaoming.add()这个方法获得的值。
这里我们写一下调用这个动态代理的方法:
public static void main(String[] args) throws Exception {
Object proxy = new AllProxy().getProxy();
try {
Method buy = proxy.getClass().getMethod("buy");
buy.invoke(proxy);
Method buyCar = proxy.getClass().getMethod("buyCar");
buyCar.invoke(proxy);
Method add = proxy.getClass().getMethod("add", int.class, int.class);
System.out.println("add = " + (int) (add.invoke(proxy, 3, 2)));
} catch (Exception e) {
e.printStackTrace();
}
}
这里由于这个动态代理,实现了多个接口,这里我们就可以通过反射的方式,拿到这个动态代理类的方法,也就是这个动态代理实现的所有接口的方法。
以上也就是动态代理+反射实现的代理模式。
这里还有一个问题,如果两个接口的方法名一样,参数一样,会发生什么问题呢。这个留给大家课后自己去尝试吧。