反射

JAVA反射机制在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法(包含private修饰的);对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

在反射面前, 一切都是赤裸裸的.

反射相关的主要API

java.lang.Class : 代表一个类
java.lang.reflect.Method : 代表类中方法
java.lang.reflect.Field : 代表类中的成员变量
java.lang.reflect.Constructor : 代表类中的构造方法
    
    
Student类  ->  字节码对象
name属性  -> Field对象
eat()方法  -> Method对象
构造方法  -> Constructor对象

类加载的过程

在这里插入图片描述

  1. java源代码经过javac.exe命令之后, 会生成一个或多个字节码文件(Xxx.class)
  2. 使用java.exe命令对字节码文件进行运行, 这个过程中会将Xxx.class加载进内存.(类的加载)
  3. 此时在内存中就出现了一个Xxx.class, 这个Xxx.class其实就是Class类的对象, 也是反射需要操作的内容.

类加载的时机(什么时候用, 什么时候加载)

  1. 创建对象
  2. 使用类方法(静态方法)
  3. 使用类变量(静态变量)
  4. 使用反射创建对象
  5. 使用它的子类
  6. 使用java.exe命令执行

获取字节码对象的3种方式

在这里插入图片描述

  • 源文件阶段
  • 字节码文件阶段
  • 创建对象

扩展: ClassLoader

引导类加载器(Bootstap Classloader) : 用C++编写, 是JVM自带的类加载器, 负责Java平台的核心类库.

扩展类加载器(Extension Classloader) (jdk1.7以及以前)

扩展类加载器(PlatformClassLoader)(jdk1.8开始) : 负责jre/lib/ext目录下的jar包内容的加载

系统类加载器(System Classloader) : 负责java.exe运行时

/**
 *
 * 获取字节码对象的三种方式
 */
public class demo02 {
    public static void main(String[] args) throws Exception{

        // Class.forName()
        Class aClass1 = Class.forName("com.itheima._02reflect.demo02.Person");
        // 类名.class
        Class aClass2 = Person.class;
        // 对象名.getClass()
        Person p = new Person();
        Class aClass3 = p.getClass();

        Person p2 = new Person("高圆圆", 30);
        Class aClass4 = p2.getClass();

        System.out.println(aClass1 == aClass2); // true
        System.out.println(aClass2 == aClass3); // true
        System.out.println(aClass4 == aClass3); // true
        System.out.println(aClass1.hashCode()); // 434091818
        System.out.println(aClass2.hashCode()); // 434091818
        System.out.println(aClass3.hashCode()); // 434091818
        System.out.println(aClass4.hashCode()); // 434091818


    }
}

动态获取
在这里插入图片描述

构造方法

(1) 获取空参构造并创建对象
  • newInstance()
/**
 * 使用反射, 获取空参构造创建对象
 *
 *  public T newInstance()
 *
 */
public class Demo03 {
    public static void main(String[] args) throws Exception{

        // 获取字节码对象
        Class aClass1 = Class.forName("com.itheima._02reflect.demo02.Person");

        // 创建对象
        Person p = (Person) aClass1.newInstance();

        p.setName("贾静雯");
        p.setAge(20);

        System.out.println(p);
    }
}
(3) 获取有参构造并创建对象
// 通过字节码对象, 获取构造方法的对象(Constructor)
public Constructor<?>[] getConstructors() : 获取所有构造方法的对象(Constructor)
    
public Constructor<T> getConstructor(Class<?>... parameterTypes)
    Class : 参数中要传入Class类型的参数
    Class ... : 可变参数
    parameterTypes : 参数类型
        
    参数中需要传入的是Class类型的多个参数, 用来描述构造方法的参数
        
    有很多构造方法, 我们来区分到底使用哪个构造, 需要通过参数列表来区分
    
    构造方法()  =>   getConstructor()
    构造方法(String name)  =>   getConstructor(String.class)
    构造方法(String name, int age) =>  getConstructor(String.class, int.class)
        
        



  • getConstructors()
        // 获取字节码对象
        Class aClass = Class.forName("com.itheima._02reflect.demo02.Person");

        // 获取所有构造方法
        Constructor[] constructors = aClass.getConstructors();

        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
  • 打印效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2EEINslR-1607768139405)(img/image-20200713105151733.png)]

获取指定的构造方法

 		// 获取字节码对象
        Class aClass = Class.forName("com.itheima._02reflect.demo02.Person");

        // 获取指定构造方法
        Constructor c = aClass.getConstructor(String.class, int.class);
        System.out.println(c);

使用构造方法, 创建对象

// 原始创建对象:  new Person("张三", 23)
// Person() => 构造方法
// "张三", 23 => 参数


// 刚才已经能够获取到Constructor构造方法, 还剩下传参

public T newInstance(Object... initargs)
    
    
// Constructor : c
c.newInstance()  =>  new 类名()
c.newInstance("张三") => new 类名("张三")
c.newInstance("张三", 23) => new 类名("张三", 23)
  • 代码演示
/**
 * 获取指定构造方法
 */
public class Demo03_3 {
    public static void main(String[] args) throws Exception {

        // 获取字节码对象
        Class aClass = Class.forName("com.itheima._02reflect.demo02.Person");

        // 获取指定构造方法
        Constructor c = aClass.getConstructor(String.class, int.class);

        // 调用newInstance方法, 传入参数并创建对象
        Person p = (Person) c.newInstance("张三", 23);

        System.out.println(p);


        // 获取空参构造
        Constructor c2 = aClass.getConstructor();
        // 创建对象
        Person p2 = (Person) c2.newInstance();
        System.out.println(p2);

    }
}

5. 成员变量

(1) 获取成员变量并修改

获取成员变量

// 成员变量: Field类的对象

// 获取所有成员变量
public Field[] getFields()
public Field[] getDeclaredFields() : 暴力获取到所有成员变量(包括私有的)

// 获取指定的成员变量
// 根据变量的名字, 获取唯一的成员变量
public Field getField(String name)
    
getField("name") -> name成员变量

修改成员变量的值

public void set(Object obj,		// 对象
                Object value)	// 值

获取非私有成员变量并修改

        // 获取字节码对象
        Class aClass = Class.forName("com.itheima._02reflect.demo02.Person");

        // 获取构造方法
        Constructor c = aClass.getConstructor();
        // 创建对象
        Person p = (Person) c.newInstance();

        // 获取指定成员变量 (Field)
        Field gender = aClass.getField("gender");

        // 传统方式的修改: p.gender = "男";
        gender.set(p, "男"); // 修改p中的gender属性, 改成"男"

        System.out.println(p);
(2) 暴力反射获取私有成员变量并修改

获取私有成员变量并修改(暴力反射)

// public Field getDeclaredField(String name) : 获取私有成员变量

// public void setAccessible(boolean flag) : 去除私有权限 -> true

        // 获取字节码对象
        Class aClass = Class.forName("com.itheima._02reflect.demo02.Person");
        // 获取无参构造
        Constructor c = aClass.getConstructor();
        // 使用无参构造, 创建对象
        Person p = (Person) c.newInstance();

        // 获取私有成员变量
        Field name = aClass.getDeclaredField("name");
        // 去除私有权限
        name.setAccessible(true);

        // 修改成员变量值
        /*
         set(Object obj,    // 修改的对象
            Object value)   // 修改到的值
         */
        name.set(p, "张三丰");

        System.out.println(p);

6. 成员方法(Method)

反射调用方法
在这里插入图片描述
无参时
在这里插入图片描述

私有方法,无参
在这里插入图片描述

(1) 获取所有成员方法
public Method[] getMethods() : 获取所有成员方法(有权限的)
    会获取到父类中非私有方法
public Method[] getDeclaredMethods() : 获取所有成员方法(包括私有)
    只会获取本类中所有成员方法
(2) 获取指定成员方法并调用(无参)
// 获取成员方法
public Method getMethod(String name,
                        Class<?>... parameterTypes)
name : 方法名
Class<?>... parameterTypes : 指定参数列表的类型
    
eat()  =>  getMethod("eat")
eat(int n) => getMethod("eat", int.class)
sleep() => getMethod("sleep")
fun(int a, String b, double c) => getMethod("fun", int.class, String.class, double.class)
    
    
// 调用成员方法
public Object invoke(Object obj,
                     Object... args)
obj: 要调用方法的对象
args : 调用方法需要传入的参数
  • 代码演示
        // 获取指定的成员方法(getName())
        Method getName = aClass.getMethod("getName");
        // 调用方法
        System.out.println(getName.invoke(p));
(3) 获取指定成员方法并调用(有参)
        // 获取指定的成员方法 (setName(String name))
        Method setName = aClass.getMethod("setName", String.class);
        // 调用方法
        setName.invoke(p, "张三丰");
        System.out.println(p);
(4) 暴力反射获取成员方法并调用
        // 获取字节码对象
        Class aClass = Class.forName("com.itheima._02reflect.demo02.Person");
        // 获取无参构造
        Constructor c = aClass.getConstructor();
        // 使用无参构造, 创建对象
        Person p = (Person) c.newInstance();

        // 获取私有成员方法
        Method eat = aClass.getDeclaredMethod("eat");
        // 去除私有全新啊
        eat.setAccessible(true);
        // 调用
        eat.invoke(p);

7. 使用反射完成榨汁机案例

  • 水果类
/**
 * 水果类
 */
public abstract class Fruit {
    public abstract void juice();
}
  • 水果的子类
public class Apple extends Fruit {
    @Override
    public void juice() {
        System.out.println("榨出了一杯苹果汁儿~");
    }
}
// ======================================================================
public class Orange extends Fruit {
    @Override
    public void juice() {
        System.out.println("榨出了一杯橙子汁儿~");
    }
}
// ==========================================================================
public class Watermelon extends Fruit {
    @Override
    public void juice() {
        System.out.println("榨出了一杯西瓜汁儿~");
    }
}
  • 测试类
public class FruitTest {
    @Test
    public void test01() throws Exception{
        // 创建Properties对象
        Properties p = new Properties();
        // 创建字节输入流, 关联配置文件
        // FileInputStream fis = new FileInputStream("day15-code\\fruit.properties");
        // 现阶段, 我们会把配置文件直接放到src下
        InputStream is = FruitTest.class.getClassLoader().getResourceAsStream("fruit.properties");
        // 加载配置文件中的信息
        p.load(is);

        // 获取classNam对应的值
        String className = p.getProperty("className");
        // System.out.println(className);

        // 获取到字节码对象
        Class aClass = Class.forName(className); // 全限定类名需要通过配置文件获取

        // 获取无参构造
        Constructor c = aClass.getConstructor();
        // 创建对象
        Fruit f = (Fruit) c.newInstance();
        juicer(f);
    }

    /**
     * 榨汁机
     * @param fruit 传入的水果
     */
    public void juicer(Fruit fruit) {
        fruit.juice();
    }
}
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐