什么是反射?

java的反射就是利用Class对象在运行阶段获取任何类的各种信息,从而可以实例化对象,访问对象的方法和属性的这么一种机制。

什么时候使用反射?

在某种业务场景下,无法在编写源代码时就确定要用哪个类的对象,需要根据用户的行为做出动态地响应。这个时候就可以考虑用反射机制在运行阶段根据用户的输入来判断到底实列化哪个类的对象,并调用该对象的方法等操作。例如:在美团点外卖后付款的界面,用户可以选择多种付款方式(微信、支付宝、银行卡等等)。假如每种支付方式都对应一个类,而在编写源代码的时候我们不能确定使用那种付款方式,为了代码的可扩展性,也不想用分支结构并为每个支付方式的类创建对象。那么,这种情况下就可以考虑用反射机制,用户点击哪个支付方式,程序就在运行阶段创建哪个支付方式类的对象完成支付。

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.junit.Test;

public class ReflectionTest { 
    // 反射之前对Person的操作
    @Test
    public void test1() {
        // 1.创建Person类的对象
        Person p1 = new Person("Tom", 12);
        // 2.通过对象,调用其内部的属性、方法
        p1.age = 10;
        p1.setName("Jack");
        System.out.println(p1.toString());
        p1.show();
        // 在Person类外部,不可以通过Person类的对象调用其内部私有结构
        // 比如:name、showNation()以及私有的构造器
    }
// 反射之后对Person的操作
    @Test
    public void test2() throws Exception {
        
        Class clazz = Person.class;
        // 1.通过反射,创建Person类的对象
        Constructor cons = clazz.getConstructor(String.class, int.class);
        // 1.1通过构造器创建对象(用多态的形式赋给Object)
        Object obj = cons.newInstance("Tom", 12);
        System.out.println(obj.toString());
        Person p = (Person)obj;
        System.out.println(p.toString());
        
        // 2.通过反射,调用对象指定的属性、方法
        // 2.1调用属性
        Field age = clazz.getDeclaredField("age");
        age.set(p, 10);
        System.out.println(p.toString());
        // 2.2调用方法
        Method show = clazz.getDeclaredMethod("show");
        show.invoke(p);
        
        System.out.println("************");
        // 3通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性
        // 3.1调用私有的构造器
        Constructor cons1 = clazz.getDeclaredConstructor(String.class);
        cons1.setAccessible(true);
        Person p1 = (Person)cons1.newInstance("Jerry");
        System.out.println(p1.toString());
        // 3.2调用私有的属性
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);
        name.set(p1, "Jack");
        System.out.println(p1.toString());
        // 3.3调用私有的方法
     // 带参且有返回值
        Method showNation = clazz.getDeclaredMethod("showNation", String.class);
        showNation.setAccessible(true);
        //showNation.invoke(p1, "中国");// 相当于p1.showNation("中国");
        String nation = (String)showNation.invoke(p1, "中国");// 相当于String nation = p1.showNation("中国");
        System.out.println(nation);
        // 不带参
        Method show1 = clazz.getDeclaredMethod("show1");
        show1.setAccessible(true);
        show1.invoke(p1);
    }
}

class Person {
    // 属性
    private String name;
    public int age;
    
    // 构造器
    public Person() {}

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

    // get,set方法
    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 void show() {
        System.out.println("我是一个人");
    }
    private void show1() {
        System.out.println("我是一个人");
    }
    
    private String showNation(String nation) {
        System.out.println("我的国籍是:" + nation);
        return nation;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}

 

反射是否破坏了面向对象的封装性?

反射并没有破坏面向对象的封装性。因为通过反射机制获取到的带权限修饰符的方法和属性都依然遵照权限修饰符限定的访问方式。除非调用setAccessible(true)方法来抑制java访问权限检查,从而达到可以随意访问的效果。但是利用setAccessible(true)是一种暴力方式,不安全。开发人员一般不会用这种方式损害自己代码的封装性。

 扩展:

// 获取Class实例的四种方式(前三种需要掌握)
    @Test
    public void test3() throws ClassNotFoundException {
        // 方式一:调用运行时类的属性:.class
        Class clazz1 = Person.class;
        System.out.println(clazz1);
        // 方式二:通过运行时类的对象,调用getClass()
        Person p = new Person();
        Class clazz2 = p.getClass();
        System.out.println(clazz2);
        // 方式三:调用Class静态方法:forName(String classPath)
        Class clazz3 = Class.forName("java.lang.String");
        System.out.println(clazz3);
        clazz3 = Class.forName("java.util.Date");
        System.out.println(clazz3);
        
        // 方式四:使用类的加载器:ClassLoader(了解)
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("java.lang.String");
        System.out.println(clazz4);
    }