java内功修炼基础篇之---反射

引言:

说来实在惭愧,工作四年,学的东西比较杂,以至于聊起天时,连很多Java的一些核心知识都说不清道不明,于是决心将Java的一些知识进行巩固一下

如果有想在Java这条路上走的更远的朋友,建议看看这篇文章,可能对你会有一点小小的帮助  -- Java成神之路

今天要来讲讲的就是Java的一个常用知识点 -- 反射

反射在Java这门语言中是非常核心的一个概念,我们学习Java不可避免地就要学习反射

这是一个非常需要掌握的知识点,在很多Java的框架中都使用到了反射机制

例如在spring中,IOC其实就是利用了反射机制去拿到类并实例化最后放到容器中的过程


Chapter 1:什么是反射

说得简单点,反射就是在Java运行过程中,通过任何一个类,都可以拿到其对应的方法和属性,对于任何一个对象,都可以任意地操作其方法和属性。


Chapter 2:反射可以做什么

反射机制主要提供了以下功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法;
  • 在运行时调用任意一个对象的方法;
  • 生成动态代理。


Chapter 3:实例代码

我知道上面的那么多都没有看(2333)

接下来我们直接上代码直接一点。

我们先定义一个Person类,用来做反射的事例

package cn.reflect.domain;


public class Person {

    private String name;

    public Integer age;

    private String sex;

    public Person() {}

    public Person(String name) {
       setName("小明");
    }

    private Person(String name, String sex) {
        setName(name);
        setSex(sex);
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "name: " + name + ", age: " + age + ", sex: " + sex;
    }

    private String getSomeThing(String message) {
        return "message is : " + message;
    }
}

接下来我们就利用上面的Person来做反射操作

我们定义一个类,在@Before中初始化Person的className字符串  和 person的class

/**
 * 反射案例Demo
 * @author xiangnan 2018/5/22
 */
public class ReflectDemo {

    public String className = "";
    public Class personClass = null;

    @Before
    public void init() throws Exception {
        className = "cn.reflect.domain.Person";
        personClass = Class.forName(className);
    }
我们尝试利用反射去获取类的名字
    /**
     * 获取Class对象的三种方式
     * 1 Object ——> getClass();
     * 2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性  例如:Person.class
     * 3 通过Class类的静态方法:forName(String  className)(常用)
     *
     */
    
    /**
     * 获取类的名字
     */
    @Test
    public void getClassName() {
        String className = personClass.getName();
        System.out.println(className);

    }

    @Test
    public void getClassName2() {
        System.out.println(Person.class.getName());
    }


通过反射去实例化一个对象

/**
     * 实例化一个对象
     */
    @Test
    @SuppressWarnings("all")
    public void getNewInstance() throws Exception {
        System.out.println(personClass.newInstance());
    }


通过反射去拿到一个类的公有/私有构造方法,然后再利用构造方法实例化该类的对象

也可以批量地获取该类所有的构造方法

/*
     * 通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员;
     *
     * 1.获取构造方法:
     *      1).批量的方法:
     *          public Constructor[] getConstructors():所有"公有的"构造方法
                public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)

     *      2).获取单个的方法,并调用:
     *          public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
     *          public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
     *
     *          调用构造方法:
     *          Constructor-->newInstance(Object... initargs)
     */

    /**
     * 得到共有构造方法
     */
    @Test
    @SuppressWarnings("all")
    public void getPublicConstructor() throws Exception {
        Constructor constructor = personClass.getConstructor(String.class);
        Person person = (Person) constructor.newInstance("小白");
        System.out.println(person);
    }

    /**
     * 得到私有构造方法
     */
    @Test
    @SuppressWarnings("all")
    public void getPrivateConstructor() throws Exception {
        Constructor constructor = personClass.getDeclaredConstructor(String.class, String.class);
        constructor.setAccessible(true);
        Person person = (Person) constructor.newInstance("小红", "女");
        System.out.println(person);
    }


通过反射得到一个类的公有/私有成员变量,然后利用这个成员变量给该类的对象赋值(无论公有还是私有都可以赋值)

y

 /*
     * 获取成员变量并调用:
     *
     * 1.批量的
     *      1).Field[] getFields():获取所有的"公有字段"
     *      2).Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;
     * 2.获取单个的:
     *      1).public Field getField(String fieldName):获取某个"公有的"字段;
     *      2).public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
     *
     *   设置字段的值:
     *      Field --> public void set(Object obj,Object value):
     *                  参数说明:
     *                  1.obj:要设置的字段所在的对象;
     *                  2.value:要为字段设置的值;
     *
     */

    /**
     * 得到非私有成员变量
     */
    @Test
    @SuppressWarnings("all")
    public void getNotPrivateField() throws Exception {
        Constructor constructor = personClass.getDeclaredConstructor(String.class, String.class);
        constructor.setAccessible(true);
        Object personObj = constructor.newInstance("小红", "女");

        Field notPrivateFiled = personClass.getField("age");
        notPrivateFiled.set(personObj, 20);
        System.out.println(notPrivateFiled.get(personObj));
        System.out.println(personObj);
    }


    /**
     * 得到私有成员变量
     */
    @Test
    @SuppressWarnings("all")
    public void getPrivateField() throws Exception {
        Constructor constructor = personClass.getConstructor(String.class);
        Object personObj = constructor.newInstance("小明");

        Field privateFiled = personClass.getDeclaredField("sex");
        privateFiled.setAccessible(true);

        privateFiled.set(personObj, "男");
        System.out.println(privateFiled.get(personObj));
        System.out.println(personObj);
    }

通过反射得到一个类的公有/私有方法,然后通过某个对象去调用这个方法

也可以批量获取该类所有的方法

 /*
     * 获取成员方法并调用:
     *
     * 1.批量的:
     *      public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
     *      public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
     * 2.获取单个的:
     *      public Method getMethod(String name,Class... parameterTypes):
     *                  参数:
     *                      name : 方法名;
     *                      Class ... : 形参的Class类型对象
     *      public Method getDeclaredMethod(String name,Class... parameterTypes)
     *
     *   调用方法:
     *      Method --> public Object invoke(Object obj,Object... args):
     *                  参数说明:
     *                  obj : 要调用方法的对象;
     *                  args:调用方式时所传递的实参;
     */

    /**
     * 获取非私有函数
     */
    @Test
    @SuppressWarnings("all")
    public void getNotPrivateMethod() throws Exception {
        Constructor constructor = personClass.getConstructor(String.class);
        Object personObj = constructor.newInstance("小明");

        Method method = personClass.getMethod("toString");
        Object result = method.invoke(personObj);
        System.out.println(result);
    }

    /**
     * 获取私有函数
     */
    @Test
    @SuppressWarnings("all")
    public void getPrivateMethod() throws Exception {
        Constructor constructor = personClass.getConstructor(String.class);
        Object personObj = constructor.newInstance("小明");

        Method method = personClass.getDeclaredMethod("getSomeThing", String.class);
        method.setAccessible(true);
        Object result = method.invoke(personObj, "小明是个20岁的男孩");
        System.out.println(result);
    }

还有一些其他的方法,也PO出来吧

/**
     * 其他方法
     */
    @Test
    public void otherMethod() throws Exception {
        //当前加载这个class文件的那个类加载器对象
        System.out.println(personClass.getClassLoader());
        //获取某个类实现的所有接口
        Class[] interfaces = personClass.getInterfaces();
        for (Class class1 : interfaces) {
            System.out.println(class1);
        }
        //反射当前这个类的直接父类
        System.out.println(personClass.getGenericSuperclass());
        /**
         * getResourceAsStream这个方法可以获取到一个输入流,这个输入流会关联到name所表示的那个文件上。
         */
        //path 不以’/'开头时默认是从此类所在的包下取资源,以’/'开头则是从ClassPath根下获取。其只是通过path构造一个绝对路径,最终还是由ClassLoader获取资源。
        System.out.println(personClass.getResourceAsStream("/log4j.properties"));
        //默认则是从ClassPath根下获取,path不能以’/'开头,最终是由ClassLoader获取资源。
        System.out.println(personClass.getResourceAsStream("/log4j.properties"));


        //判断当前的Class对象表示是否是数组
        System.out.println(personClass.isArray());
        System.out.println(new String[3].getClass().isArray());


        //判断当前的Class对象表示是否是枚举类
        System.out.println(personClass.isEnum());
        System.out.println(Class.forName("cn.reflect.run.ReflectDemo").isEnum());


        //判断当前的Class对象表示是否是接口
        System.out.println(personClass.isInterface());
        System.out.println(Class.forName("cn.reflect.run.ReflectDemo").isInterface());


    }

通过反射和配置文件,去获取类和类的方法  -- spring的IOC的原理

    /**
     * 通過反射和配置文件,去获取类和方法 -- Spring的IOC的原理
     */
    @Test
    @SuppressWarnings("all")
    public void loadProperties() throws Exception {
        Properties properties = new Properties();
        properties.load(new BufferedReader(new InputStreamReader(new FileInputStream("src/cn/reflect/config.properties"))));
        String className = properties.getProperty("className");
        String methodName = properties.getProperty("methodName");

        Class clazz = Class.forName(className);
        Object object = clazz.newInstance();
        System.out.println(clazz.getMethod(methodName).invoke(object));
    }

去获取某个类的泛型类型

注: 如果一个类没有继承某个类,是无法获取其泛型类型的

/**
     * 在父类中获取子类的泛型类型
     * 注意:只有当这个类被继承时,才能获取这个类的泛型。  如果这个类没有被继承,即便是反射,也拿不到当前类的泛型类型
     */
    static abstract class Human {
    	public String getGenericType() {
    		//这里的this代表的是子类,比如下面的child
    		Type type = this.getClass().getGenericSuperclass();
    		//得到Human
    		ParameterizedType parameterizedType = (ParameterizedType) type;
    		//得到 因为泛型可能是多个,所以是得到的是数组
    		Type[] typeArray = parameterizedType.getActualTypeArguments();
    		//得到String
    		Class clazz = (Class) typeArray[0];
    		return clazz.getName();
    	}
    }
    
    static class Child extends Human {}
    
    static class Adult extends Human {}
    
    @Test
    public void getGenericTypeTest() throws Exception {
    	Child child = new Child();
    	//最后打印出java.lang.String
    	System.out.println(child.getGenericType());
    	
    	
    	//获取当前类相同目录下所有子类/实现类   注:内部类也会生成单独的class文件
    	for(Class clazz : ExtendsClassUtils.getAllAssignedClass(Human.class)){
    
            System.out.println(clazz.getName());
    	}
    }

最后结果得到Child和Adult

工具类,去获取某个类/接口 相同包下 的 所有实现/继承 了它们的类

当然你也可以拓展,扫描所有包

这个功能比较吊(23333)

package cn.reflect.utils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class ExtendsClassUtils {
	/**
	 * 获取同一路径下所有子类或接口实现类
	 * 
	 * @param intf
	 * @return
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	public static List> getAllAssignedClass(Class cls) throws IOException,
	ClassNotFoundException {
		List> classes = new ArrayList>();
		for (Class c : getClasses(cls)) {
			if (cls.isAssignableFrom(c) && !cls.equals(c)) {
				classes.add(c);
			}
		}
		return classes;
	}

	/**
	 * 取得当前类路径下的所有类
	 * 
	 * @param cls
	 * @return
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	public static List> getClasses(Class cls) throws IOException,
	ClassNotFoundException {
		String pk = cls.getPackage().getName();
		String path = pk.replace('.', '/');
		ClassLoader classloader = Thread.currentThread().getContextClassLoader();
		URL url = classloader.getResource(path);
		return getClasses(new File(url.getFile()), pk);
	}


	/**
	 * 迭代查找类
	 * 
	 * @param dir
	 * @param pk
	 * @return
	 * @throws ClassNotFoundException
	 */
	private static List> getClasses(File dir, String pk) throws ClassNotFoundException {
		List> classes = new ArrayList>();
		if (!dir.exists()) {
			return classes;
		}
		for (File f : dir.listFiles()) {
			if (f.isDirectory()) {
				classes.addAll(getClasses(f, pk + "." + f.getName()));
			}
			String name = f.getName();
			if (name.endsWith(".class")) {
				classes.add(Class.forName(pk + "." + name.substring(0, name.length() - 6)));
			}
		}
		return classes;
	}
}

最后是利用Modifier(java.lang.reflect)这个类的静态方法去判断某个字段/方法/类,是否被某个关键字修饰

    /**
     * 判断字段是否被final修饰
     * 类似方法和类也可以这样使用: method.getModifiers() / clazz.getModifiers()
     */
    @Test
    public void isFinalTest() throws Exception {
    	Class clazz = Class.forName("cn.reflect.run.ReflectDemo");
    	Field field = clazz.getField("packageName");
    	System.out.println(Modifier.isFinal(field.getModifiers()));
    }

除了判断final之外,Modifier还可以判断很多其他的类型,功能比较强大

java内功修炼基础篇之---反射_第1张图片


除了以上的用法外,class、method、field还可以使用getAnnotations()来获取当前对应的注解是什么,Spring的注解包扫描就是通过此原理实现


总结:

以上便是反射的大多数用法,基本涵盖了日常的用法

如果还想基础深入学习反射,建议去百度下javassist,这是一个字节码编辑工具,可以动态地生成class文件,或者对已有的class文件进行修改,例如修改某个类的名字,方法,成员变量、还可以让某各类去继承其他类。非常强大的一个工具

感谢您阅读-----Java反射之非常重要系列

谢谢。



你可能感兴趣的:(java)