探索JAVA系列(一)探秘Java反射(Reflect)

312 阅读6分钟

前言

反射是java开发进阶的一项重要内容,在我们使用的框架如spring中的aop中就有其最佳实践的案例,我们有必要熟悉其Api,并且深刻理解它的作用,今天就让我们一起来看看java的反射-reflect吧。

正篇

一:java反射加载类的三种方式

首先创建测试类:
接口类

package com.lly.springtest1.reflect;

/**
 * @ClassName IPerson
 * @Description TODO
 * @Author lly
 * @Date 2019/2/21 2:42 PM
 * @Version 1.0
 **/
public interface IPerson {
    void sayHello();
}

实现类

package com.lly.springtest1.reflect;

/**
 * @ClassName ChineseEntity
 * @Description TODO
 * @Author lly
 * @Date 2019/2/20 3:24 PM
 * @Version 1.0
 **/
public class ChineseEntity implements IPerson{
    private String name;
    private int age;
    private String phone;
    public String addres;
    public ChineseEntity() {
    }
    public ChineseEntity(String name, int age, String phone) {
        this.name = name;
        this.age = age;
        this.phone = phone;
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
    @Override
    public String toString() {
        return "ChineseEntity{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", phone='" + phone + '\'' +
                '}';
    }
    public String getAddres() {
        return addres;
    }
    public void setAddres(String addres) {
        this.addres = addres;
    }
    private void getTestName(){
    }
    @Override
    public void sayHello() {
        System.out.println("Chinese say hello");
    }
    public void test(String word) {
        System.out.println("testWord:"+word);
    }
}

package com.lly.springtest1.reflect;

/**
 * @ClassName ChineseEntity
 * @Description TODO
 * @Author lly
 * @Date 2019/2/20 3:24 PM
 * @Version 1.0
 **/
public class AmericanEntity implements IPerson{

    private String name;
    private int age;
    private String phone;
    public String addres;
    public AmericanEntity() {
    }

    public AmericanEntity(String name, int age, String phone) {
        this.name = name;
        this.age = age;
        this.phone = phone;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "ChineseEntity{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", phone='" + phone + '\'' +
                '}';
    }

    public String getAddres() {
        return addres;
    }

    public void setAddres(String addres) {
        this.addres = addres;
    }

    private void getTestName(){
    }

    @Override
    public void sayHello() {
        System.out.println("American say hello");
    }
}

1:Class.forName()

public static void typeOne() {
        try {
            log.info("通过类的全路径获取类");
//            动态加载类,在需要的时候才加载所需要的类
            Class<?> pClass = Class.forName("com.lly.springtest1.reflect.ChineseEntity");
            log.info("获取该类所有的属性");
            Arrays.asList(pClass.getDeclaredFields()).stream().forEach(e -> System.out.println(e.getName()));
            log.info("获取该类所有的所有公开属性");
            Arrays.asList(pClass.getFields()).stream().forEach(e -> System.out.println(e.getName()));
            log.info("获取类的实例");
            ChineseEntity person = (ChineseEntity) pClass.newInstance();
            person.sayHello();
            person.setAge(10);
            log.info("打印类的一个属性");
            System.out.println("年龄:" + person.getAge());
            log.info("返回某个类的所有公用(public)方法包括其继承类的公用方法,当然也包括它所实现接口的方法");
            Arrays.asList(pClass.getMethods()).stream().forEach(e -> {
                System.out.print(e.getName() + "(");
                Arrays.asList(e.getParameterTypes()).stream().forEach(e1 -> System.out.print(e1.getName() + ","));
                System.out.println(")");
                return;
            });
            log.info("类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。当然也包括它所实现接口的方法");
            Arrays.asList(pClass.getDeclaredMethods()).stream().forEach(e -> System.out.println(e.getName()));
            log.info("获取指定的方法");
            Method test = pClass.getDeclaredMethod("test", String.class);
            //方法有返回值,返回实际的值.没有返回值返回null
            Object person1 = test.invoke(person, "person");
            System.out.println(person1);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

2:Object.class

    public static void typeTwo() {
//       静态加载需要的类
        Class<ChineseEntity> pClass = ChineseEntity.class;
        Arrays.asList(pClass.getDeclaredFields()).stream().forEach(e -> System.out.println(e.getName()));
    }

3:new Object()

    public static void typeThree() {
//        new 对象是静态加载类,在编译时刻就需要加载所有需要可能的类
        ChineseEntity chineseEntity = new ChineseEntity();
        Class<? extends ChineseEntity> aClass = chineseEntity.getClass();
        Arrays.asList(aClass.getDeclaredFields()).stream().forEach(e -> System.out.println(e.getName()));

    }

运行结果

15:33:58.553 [main] INFO com.lly.springtest1.reflect.ReflectTest - 通过类的全路径获取类
15:33:58.561 [main] INFO com.lly.springtest1.reflect.ReflectTest - 获取该类所有的属性
name
age
phone
addres
15:33:58.687 [main] INFO com.lly.springtest1.reflect.ReflectTest - 获取该类所有的所有公开属性
addres
15:33:58.688 [main] INFO com.lly.springtest1.reflect.ReflectTest - 获取类的实例
Chinese say hello
15:33:58.688 [main] INFO com.lly.springtest1.reflect.ReflectTest - 打印类的一个属性
年龄:10
15:33:58.688 [main] INFO com.lly.springtest1.reflect.ReflectTest - 返回某个类的所有公用(public)方法包括其继承类的公用方法,当然也包括它所实现接口的方法
toString()
getName()
setName(java.lang.String,)
test(java.lang.String,)
sayHello()
setAge(int,)
getAge()
setPhone(java.lang.String,)
getAddres()
getPhone()
setAddres(java.lang.String,)
wait(long,int,)
wait(long,)
wait()
equals(java.lang.Object,)
hashCode()
getClass()
notify()
notifyAll()
15:33:58.691 [main] INFO com.lly.springtest1.reflect.ReflectTest - 类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。当然也包括它所实现接口的方法
toString
getName
setName
test
sayHello
setAge
getAge
setPhone
getAddres
getPhone
setAddres
getTestName
15:33:58.691 [main] INFO com.lly.springtest1.reflect.ReflectTest - 获取指定的方法
testWord:person
null

Process finished with exit code 0

二:java反射的实际应用

了解过设计模式的同学肯定知晓工厂模式,该模式是帮助我们获取一个类的实例,那么其中实现的原理是什么呢,没错,就是java的反射,通过反射获取到具体类的实例,上面我们已经定义了2个实体类中国人和美国人,并且实现了IPerson接口,这样我们在获取其中一个国家人的实例的时候,通常我们会用

ChineseEntity chineseEntity = new ChineseEntity();

这种写法,但是如果我们以后要获取到美国人或者其他国家的人恩,是不是就需要修改代码了,那么我们就用了接口的有点,实现统一的标准,方便扩展,再利用工厂类来获取我们指定的实例就大大优化了我们打代码,下面我们来实现以下
工厂类

package com.lly.springtest1.reflect;

/**
 * @ClassName PersonFactory
 * @Description persion工厂类
 * @Author lly
 * @Date 2019/2/22 2:44 PM
 * @Version 1.0
 **/
public class PersonFactory {

    public static IPerson getiPersonInstance(String className) {
        IPerson iPerson = null;
        try {
            iPerson = (IPerson) Class.forName(className).newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return iPerson;
    }
}

测试类

package com.lly.springtest1.reflect;

/**
 * @ClassName ReflectAndFactoryTest
 * @Description TODO
 * @Author lly
 * @Date 2019/2/22 2:50 PM
 * @Version 1.0
 **/
public class ReflectAndFactoryTest {
    public static void main(String[] args) {
        IPerson chinese = PersonFactory.getiPersonInstance("com.lly.springtest1.reflect.ChineseEntity");
        chinese.sayHello();
        IPerson american = PersonFactory.getiPersonInstance("com.lly.springtest1.reflect.AmericanEntity");
        american.sayHello();
    }
}

结果

利用反射动态生成我们需要的实例对象,这里有一点需要注意的使我们必须要知道类的全名,我们可以将全路径类名和简单类名做一个k-v的映射配置在java配置类中,这样我们只需要修改配置类就可以了

三:关于setAccessible()方法

首先我们来看看jdk中的解释:
将此对象的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。并不是代表反射类的属性能否访问的开关,而是是否检查语言访问,我们来做个试验

 Method test = pClass.getDeclaredMethod("test", String.class);
            //方法有返回值,返回实际的值.没有返回值返回null
            long stime = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                test.invoke(person, "person");
            }
            long etime = System.currentTimeMillis();
            log.info("时间:{}", etime - stime);

            test.setAccessible(true);
            long stime1 = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                test.invoke(person, "person");
            }
            long etime1 = System.currentTimeMillis();
            log.info("时间:{}", etime1 - stime1);

大家觉得结果是什么呢,都执行10000次哪个更快呢,结果是

15:47:49.967 [main] INFO com.lly.springtest1.reflect.ReflectTest - 时间:101
15:47:50.038 [main] INFO com.lly.springtest1.reflect.ReflectTest - 时间:68

事实胜于雄辩,原来是由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的。

总结

反射的三种方式中第一种使我们经常使用的,例如我们在springAop,jdbc底层加载数据库驱动包等等,其他2种由于我们已经知道了所学要的类,不存在动态加载,所以基本上不使用。