笔记_Java基础_反射基础

175 阅读5分钟

一. 反射概述

1. 为什么要用反射?

Java开发中往往要遵循OCP,简单来说,就是软件实体对扩展开放,而对修改关闭。因此,一部分数据需要放在配置文件中供修改,可以有效减小修改源码和重新编译的次数;也正是因为Java的反射机制,Java代码更容易实现OCP,因此催生了许多Java生态,如Spring,SpringMVC等。

2. 反射是什么?

Java的反射机制允许程序在执行期借助Reflection API取得任何类的内部信息(方法, 字段,构造器等),并能操作对象的属性和方法。

3. Java反射机制原理图

Java反射原理图.png

4. 反射简单case

package com.hspedu.reflection.question;

import com.hspedu.Cat;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * @author 左阳
 * @version v0.1
 */
public class ReflectionQuestion {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //1. 传统方式
        //Cat cat = new Cat();
        //cat.hi();

        //2. 读取配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src/re.properties"));
        String classPath = properties.get("classfullpath").toString();
        String methodName = properties.get("method").toString();
        //(1)若直接通过字符串来创建对象 
        //System.out.println("classPath=" + classPath);
        //System.out.println("method=" + method);
        //行不通:本质是因为需要从配置文件中读取所需要的类

        //3. 采用反射的机制
        //(1). 【Class类】加载一个class类,会返回一个Class这么一个类型的对象
        Class cls = Class.forName(classPath);
        //(2). 【获取对象实例】通过 cls对象,可以创建/得到加载的类
        Object o = cls.newInstance();
        //(3). 【Method类】通过method1可以调用方法
        Method method1 = cls.getMethod(methodName);
        System.out.println("===================");
        method1.invoke(o);//反射中是反过来,即通过
        //(4). 【Field类】
        Field name = cls.getField("name"); //getField不能得到私有的属性,
        System.out.println("Cat类 name字段为:" + name.get(o));
        //(5). 【Construct类】
        Constructor constructors = cls.getConstructor(); // ()中,可以指定构造器参数类型,这里返回一个无参构造器
        System.out.println(constructors); // 若想获取有参构造器,请传入类型的对象,如String.class
    }
}

5. 反射的优缺点

(1)优点:可以动态的创建和使用对象(也即是框架的底层核心),使用灵活。
(2)缺点:使用反射基本是解释执行,对运行速度有影响。

二. Class类

1. Class类要点

(1)Class类对象不是new出来的,而是系统创建的
(2)对于某个类的Class对象,内存中最多只有一份,因为类只加载一次
(3)通过Class对象可以完整的得到一个类的结构
(4)CLass对象是存放在堆里的
(5)类的字节码信息,是放在方法区的

2. Class类的常用方法

package com.hspedu.class_;

import com.hspedu.Car;
import com.hspedu.Cat;

import java.lang.reflect.Field;

/**
 * @author 左阳
 * @version v0.1
 */
@SuppressWarnings({"all"})
public class Class01 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        //1. 定义类的名字,并获取Class对象
        String className = "com.hspedu.Car";
        Class cls = Class.forName(className);
        //2. 获取class对象是哪个类的class对象
        System.out.println(cls);
        //3. 运行类型 -- java.lang.Class
        System.out.println(cls.getClass());
        //4. 获取Class类的类所在包的名字
        System.out.println(cls.getPackage().getName());
        //5. 获取名字
        System.out.println(cls.getName());
        //6. 通过反射创建对象
        Car car = (Car) cls.newInstance();
        //7. 建立字段对象,通过字段对象获取属性,设置属性
        Field name = cls.getField("brand");
        System.out.println(name.get(car)); // 宝马
        name.set(car, "奔驰");
        System.out.println(name.get(car)); // 奔驰
        //8. 获取所有字段
        Field[] fields = cls.getFields();
        for(Field f : fields) {
            System.out.println(f.getName());
        }
    }
}

3. 如何获取Class类

方法应用场景
Class的静态方法Class.forName()多用于配置文件的读取
类名.class多用于参数传递
对象.getClass()可通过创建好的对象来获取
通过类加载器:对象.getClass().getClassLoad().loadClass("类名")可通过创建好的对象来获取
基本数据类型.classNA
包装数据类型.TYPENA

4. 哪些类型具有Class类?

外部类,内部类,接口,数组,枚举,注解,基本数据类型,void,Class类(本身也是外部类)

5. 类的加载过程

反射机制是Java实现动态语言的关键,降低了代码的依赖性。
下面为类的加载过程图:

类的加载过程图.png

三. 反射过程中的其他信息(类的整个数据结构)

方法比较多,使用过程中查看相关API即可
下面举一些经典case

1. 通过反射创建某类的对象

其中,该类有一个无参public构造器,一个有参public构造器,一个有参private构造器

package com.hspedu.reflection.question;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @author 左阳
 * @version v0.1
 */
@SuppressWarnings({"all"})
public class ReflecCreateInstance {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        //1. 先获取到User类的Class对象
        Class<?> userClass = Class.forName("com.hspedu.reflection.question.User");
        //2. 通过public的无参构造器创建实例
        Object o1 = userClass.newInstance();
        System.out.println("o1=" + o1);
        //3. 通过public的有参构造器创建实例
        Constructor<?> constructor = userClass.getConstructor(String.class);
        Object o2 = constructor.newInstance("zuoyang");
        System.out.println(o2);
        //4. 通过非public的有参构造器来创建实例
        Constructor<?> declaredConstructor = userClass.getDeclaredConstructor(int.class, String.class);
        declaredConstructor.setAccessible(true); //暴力破解,反射面前,一切都是纸老虎
        Object o3 = declaredConstructor.newInstance(30, "hspedu");
        System.out.println(o3);
    }
}

class User {
    private int age = 10;
    private String name = "韩顺平教育";

    public User() {}

    public User(String name) {
        this.name = name;
    }

    private User(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + ''' +
                '}';
    }
}
  1. 通过Class对象,只能获取public无参构造器
  2. 通过构造器对象,可以用其他方式获取对象:
    (1)若需要通过有参public方式构建对象,需要获取构造器对象,再传入参数
    (2)若需要通过有参private方式构造对象,还需将构造器通过setAccessible()暴力破解

2. 通过反射访问属性

package com.hspedu.reflection.question;

import java.lang.reflect.Field;

/**
 * @author 左阳
 * @version v0.1
 */
public class ReflecAccessProperty {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        //1. 创建对象
        Class<?> aClass = Class.forName("com.hspedu.reflection.question.Student");
        Object o = aClass.newInstance();
        //2. 获取public field字段,通过getField()方法
        Field age = aClass.getField("age");
        age.set(o, 20);
        System.out.println(o);  // 20 , null

        //3. 获取private字段,并修改
        Field name = aClass.getDeclaredField("name");
        name.setAccessible(true); //暴力破解
        name.set(o , "123");
        System.out.println(o);  // 20 123
        name.set(null, "456");
        System.out.println(o); // 20 , 456
        System.out.println(name.get(o)); // 456
    }
}

class Student {
    public int age;
    private static String name;

    public Student() {

    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                "name=" + name +
                '}';
    }
}
  1. public字段可以通过api,直接获取与修改
  2. private字段对象可以通过getDclaredField获取,并暴力破解后读和修改
  3. 静态字段可以不传入具体Object修改,而写入null