Java基础进阶--反射

什么是反射?

反射是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的实例对象,因为一切的调用都是在建立在有这个实例对象的基础上的。那么,我们如何通过反射来拿到这个实例对象呢?有以下几种方法。

  1. 通过类的newInstance(),这个方法实例化一个Person对象。
Person xiaoming = Person.class.newInstance();
  1. 通过类名,使用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>()
这个错误的意思就是没有找到无参数的构造方法。
那么,如果想要生成实例对象的这个类,没有定义无参数的构造方法,我们可不可以生成实例呢?答案是可以的,需要通过反射拿到类的构造方法,通过构造方法生成一个实例对象,调用这个通过构造方法生成的实例对象的方法,就可以生成你想要类型的实例对象。

  1. 通过反射构造方法生成实例对象。
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();
        }
    }

这里由于这个动态代理,实现了多个接口,这里我们就可以通过反射的方式,拿到这个动态代理类的方法,也就是这个动态代理实现的所有接口的方法。

以上也就是动态代理+反射实现的代理模式。

这里还有一个问题,如果两个接口的方法名一样,参数一样,会发生什么问题呢。这个留给大家课后自己去尝试吧。

上一篇:Java基础进阶–注解.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值