死磕,Java 反射(Reflection),一文全懂

疯狂创客圈:如果说Java是一个武林,这里的聚集一群武痴, 打磨着九阳神功和独孤九剑,欢迎围观
QQ群链接:
疯狂创客圈QQ群

什么是反射?

在Java 编程时,习惯的方式是通过Class类型,创建对象。

比如,创建一个宠物狗对象的代码为:

Pet pet=new Dog();

如果反过来:已知一个对象,如何知道其Class类型信息呢?

这就要用到反射。

反射的基础

在没有加载到内存之前,每一个类,都作为 .class文件保存在磁盘中,是一个二进制文件。一旦加载入JVM内存,就会在JVM的方法区,创建一个描述java.lang.Class对象,保存这个类的所有信息:名称、属性、方法、构造器等等。
这个java.lang.Class对象,就是反射的基础。下面以宠物狗Dog的类型作为演示类型。Dog类型的代码如下:

package com.crazymakercircle.common.pet;

public class Dog extends Pet {
    private   static int dogNo;  //小狗编号
    public Dog() {
       dogNo++;
        name = "小狗-" + dogNo;
    }
}

反射的第一步,就是取得这个Class对象。

第一种方法:Class.forName
 

Class aClass=Class.forName("com.crazymakercircle.common.pet.Dog");
Logger.debug(aClass);

第二种方法:类型.class
 

aClass=Dog.class;
Logger.debug(aClass);


第三种方法:对象.getclass()

Dog dog = new Dog();
aClass=dog.getClass();
Logger.debug(aClass);


以上三种方法,都是取得同一个Class对象。运行程序,输出的结果为:

class com.crazymakercircle.common.pet.Dog
class com.crazymakercircle.common.pet.Dog
class com.crazymakercircle.common.pet.Dog

三种方法的输出结果,是一模一样的,就看看使用的时候,哪个更加方便。

通过反射,创建对象

通过反射创建对象,需要调用到Class对象的newInstance 方法。 

public static void builderInstance()  throws Exception {

//正常的创建
Pet pet = new Dog();
Logger.debug(pet);

//通过反射创建对象
Pet pet1 = Dog.class.newInstance();
Logger.debug(pet1);

}

输出的结果为:

宠物{名称=小狗-1, 年龄=7}
宠物{名称=小狗-2, 年龄=9}

 通过结果可以看到:通过构造函数创建对象,和通过反射创建对象,结果是一样的。


通过反射, 访问属性

public static void showField()  throws Exception {

//取得属性
Field field = Pet.class.getDeclaredField("name");

Logger.debug(field);
field.setAccessible(true);
Logger.debug(field);

Pet pet =new Dog();
Logger.debug(pet);

//取得对象的属性值
String oldValue= (String) field.get(pet);
Logger.debug("oldvalue:="+oldValue);

//设置对象的属性值
field.set(pet, "未知宠物");
Logger.debug(pet);


}

输出的结果如下:

protected  java.lang.String  com.crazymakercircle.common.pet.Pet.name
protected  java.lang.String  com.crazymakercircle.common.pet.Pet.name
宠物{名称=小狗-3, 年龄=2}
oldvalue:=小狗-3
宠物{名称=未知宠物, 年龄=2}

通过上边的代码可以看出:每一个类的属性,在JVM方法区中,有一个java.lang.reflect.Field 对象进行保存。

通过java.lang.reflect.Class对象,可以获取到 java.lang.reflect.Field 对象。

通过Field属性对象,可以取得对象的属性值,也可以设置对象的属性值。分别调用set、get方法即可。

如果Field属性对象不是公有权限,还可以强行设置为公有权限,通过调用Field对象的setAccessible(true),即可。


通过反射, 调用方法

public static void invokeMethod() throws Exception {

//取得sayHello方法
Method method = Pet.class.getDeclaredMethod("sayHello");
Logger.debug(method);

//设置为公有权限
method.setAccessible(false);
Logger.debug(method);

Pet pet =new Dog();
Logger.debug(pet);

//反射调用 sayhello
method.invoke(pet);

//直接调用 sayhell

pet.sayHello();

}

输出的结果如下:

public com.crazymakercircle.common.pet.Pet com.crazymakercircle.common.pet.Pet.sayHello()
public com.crazymakercircle.common.pet.Pet com.crazymakercircle.common.pet.Pet.sayHello()

宠物{名称=小狗-4, 年龄=6}

sayHello:嗨,大家好!我是小狗-4
sayHello:嗨,大家好!我是小狗-4

通过上边的代码可以看出:每一个类的方法,在JVM方法区中,有一个java.lang.reflect.Method 对象进行保存。
通过java.lang.reflect.Class对象,可以获取到 java.lang.reflect.Method 对象。
通过Method 属性对象的invoke方法, 可以直接调用object对象 的对应的Method 方法,与通过object对象直接调用Method 方法,结果是一样的。

上文中,直接调用pet.sayHello() 和反射调用 method.invoke(pet) 的结果,是完全一致。

如果Method 方法对象不是公有权限,还可以强行设置为公有权限,通过调用Method 对象的setAccessible(true),即可。这个与Field 类似。

通过反射,调用基类的方法

public static void invokeParentMethod() throws Exception {

Method method = Cat.class.getMethod("sayHello");

Pet pet =new Cat();
Logger.debug(pet);

method.invoke(pet);
pet.sayHello();

}

输出的结果如下:
 

宠物{名称=小猫-1, 年龄=15}
sayHello:嗨,大家好!我是小猫-1
sayHello:嗨,大家好!我是小猫-1

Class对象的getDeclaredMethod ,只能返回在当前类中定义的方法,不能返回在基类中定义的方法。

使用Class对象的getMethod 方法,可以返回在任何基类中定义的方法。

 

通过反射,取得注解标记

public static void checkAnno() throws Exception {


Field field = Pet.class.getDeclaredField("age");

AgeRange annotation=field.getAnnotation(AgeRange.class);

Pet pet =new Dog();
Logger.debug(pet);

if(pet.getAge()>annotation.max()){

    Logger.debug("age is too big:="+pet.getAge());

}  else  if(pet.getAge()<annotation.min()){

    Logger.debug("age is too small:="+pet.getAge());

}else {

    Logger.debug("age is valid:="+pet.getAge());
}

}

输出的结果如下:


宠物{名称=小狗-5, 年龄=18}

age is too big:=18

 

 通过 field.getAnnotation(AgeRange.class) 的方式,可以去取得定义在属性前面的注解标记:

 @AgeRange(max = 15,min = 2)

 protected int age;

 这个注解包含了两个方法:max,min如下:

 



@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.FIELD)

public @interface AgeRange {

     int min();

     int max();

}

取得标记对象后,可以直接访问标记的方法,去取得配置的值。 根据配置值进行相对应的业务逻辑的校验:

 

  if(pet.getAge()>annotation.max()){

            Logger.debug("age is too big:="+pet.getAge());

   }  else  if(pet.getAge()<annotation.min()){

 
            Logger.debug("age is too small:="+pet.getAge());

  }else {
 
            Logger.debug("age is valid:="+pet.getAge());

  }

理解反射,使用反射,对于java编程来说,是一个非常重要的基础。

这一小节,只是简单的提到了Java注解。关于Java注解的深入的介绍,另一篇文章源码在疯狂创客圈QQ群下载。

 

死磕,Java注解(Annotation )的秒懂之道

 

 

猜你喜欢

转载自blog.csdn.net/crazymakercircle/article/details/82765304
今日推荐