反射的基本概念与运用

参考博客:

https://www.cnblogs.com/ysocean/p/6516248.html

https://www.sczyh30.com/posts/Java/java-reflection-1/

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

反射的核心是 JVM 在运行时才动态加载类或调用方法 / 访问属性,它不需要事先(写代码时或编译期)知道运行对象是谁;

1、Java 反射主要提供以下功能:**

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用 private 方法);
  • 在运行时调用任意一个对象的方法。

2、反射的主要用途

很多人都认为反射在实际的 Java 开发应用中并不广泛,其实不然。当我们在使用 IDE(如 Eclipse,IDEA)时,当我们输入一个对象或类并想调用它的属性或方法时,一按点号,编译器就会自动列出它的属性或方法,这里就会用到反射。

反射最重要的用途就是开发各种通用框架,各框架大量使用了动态代理,而动态代理的实现依赖于反射技术.

3、基本运用

以下我会全部使用 clazz 表示某个类的 Class 实例

(1)、获取 Class 对象

  • // Class 类的 forName 静态方法
    Class.forName("xxx");
    
  • // 直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高
    Class clazz = Person.class;
    
  • // 通过对象的 getClass() 方法,通常用在方法参数
    Person p = new Person();
    Class clazz = p.getClass();
    

(2)、判断是否为某个类的实例

  • 通常使用 instanceof 关键字判断是否为某个类的实例;
  • 也可以使用 Class 对象的 isInstance() 方法判断是否为某个类的实例。
public native boolean isInstance(Object obj);

(3)、创建实例

  1. 使用 Class 对象的 newInstance() 方法创建 Class 对象对应的实例;(调用无参构造函数)

    Class clazz = Person.class;
    Person person = clazz.newInstance();
    
  2. 通过 Class 对象获取指定的 Constructor 对象(带参数的构造函数),在调用 Constructor 对象的 newInstance() 方法创建实例

    Class clazz = Person.class;
    // 获取 Person 类带一个 String 类型的参数的构造器
    Constructor con = clazz.getConstructor(String.class);
    // 根据构造器创建实例
    Person person = con.newInstance("xiaojian");
    

(4)、获取构造器

// 通过 Class 类的实例获取 Constructor,参数为指定构造器的参数的类型的Class对象
Class clazz = Person.class;
Constructor c = clazz.getConstructor(String.class);

// 再由构造器使用 newInstance()方法 创建实例对象
Person person =  c.newInstance("xiaojian");

(5)、获取方法

  • getDeclaredMethods 方法返回接口或接口声明的所有方法,包括公共、保护、默认和私有方法,但不包括继承方法

    public Method[] getDeclaredMethods() throws SecurityException
    
  • getMethods 方法返回某个类的所有公用 (public) 方法,包括其继承类的公用方法

    public Method[] getMethods() throws SecurityException
    
  • getDeclaredMethod 方法可返回所有方法 (不包括继承方法), 其中第一个参数为方法名称,后面的参数为方法的参数对应的 Class 的对象。并且,如果获取的是私有方法(并且不是当前类的,是包里其他的类的私有方法),使用 invoke() 方法调用之前,必须给 Method 对象 setAccessible(true) 来设置或取消访问检查,以达到访问私有对象的目的。Field 也一样。

    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
    // 如
    Method method = clazz.getMethod("study",String.class,String.class);
    method.setAccessible(true);
    method.invoke(clazz.newInstance(),"xiaojian","abc");
    
  • getMethod 方法返回某个特定非私有的方法(包括其继承类的公用方法),其中第一个参数为方法名称,后面的参数为方法的参数对应的 Class 的对象

    public Method getMethod(String name, Class<?>... parameterTypes)
    // 如
    clazz.getMethod("study",String.class,String.class);
    

(6)、调用方法

从类中获取方法后,使用 invoke() 方法调用这个方法

public class ReflectTest {
	public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 使用带参数的构造器,创建实例对象
        Class clazz = Person.class;
        Constructor constructor = clazz.getConstructor(String.class,int.class);
        Object object = constructor.newInstance("小贱",38);
System.out.println(object);
       
System.out.println("====== 方法调用 ======");
        // 调用没有参数的公共 eat 方法
        Method m1 = clazz.getMethod("eat");
        // invoke 的参数是方法的类的实例对象,不是 Class 对象
        m1.invoke(object);
System.out.println("mm");
        // 调用父类 Object 的方法
        Method m = clazz.getMethod("hashCode");
        Object o = m.invoke(object);
        System.out.println(o);
System.out.println("mm");

        // 调用带一个参数的公共 eat 方法
        Method m2 = clazz.getMethod("eat",String.class);
        m2.invoke(object,"米饭");
        
        // 调用带三个参数的私有 eat 方法
        Method m3 = clazz.getDeclaredMethod("eat",String.class,String.class,int.class);
        // 不是当前 main 方法的类 ReflectTest,调用其他类的私有方法,必须 `setAccessible(true)`
        m3.setAccessible(true);
System.out.println(m3.toGenericString());
        Object result = m3.invoke(object,"牛肉面","可乐",3);
System.out.println(result);	
    }
}

// 其他的类
class Person{
    private String name;
    private int age;

    public Person() {
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public void eat(){
        System.out.println("吃");
    }

    public void eat(String food){
        System.out.println("吃" + food);
    }
    // 私有方法
    private String eat(String food,String drink,int count){
        return count + "份" + food + "," + count + "份" + drink + ".";
    }

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

(7)、获取成员变量

  • getFields 访问所有公有的成员变量,包括父类的

    public Field[] getFields() throws SecurityException;
    
  • getDeclaredFields 所有已声明的成员变量,包括私有 (private修饰的) 变量,不包括父类的

    public Field[] getDeclaredFields() throws SecurityException;
    
  • getField 访问指定的 成员变量**(非私有的、父类的)**;

    public Field getField(String name) throws NoSuchFieldException,SecurityException
    
    // 获取成员变量值
    Object object = clazz.newInstance();
    Field field = clazz.getField("xxx");
    System.out.println(field.get(object));
    // 重新设置成员变量值为 "abc"
    field.set(object,"abc");
    System.out.println(field.get(object));
    
  • getDeclaredField 访问指定的成员变量**(所有的、不包括父类的)**,访问或修改该成员变量前要 setAccessible(true)

    public Field getDeclaredField(String name) throws NoSuchFieldException,SecurityException
        
    // 获取成员变量值
    Object object = clazz.newInstance();
    Field field = clazz.getField("xxx");
    field.setAccessible(true);
    System.out.println(field.get(object));
    // 重新设置成员变量值为 "abc"
    field.set(object,"abc");
    System.out.println(field.get(object));
    
    public class ReflectTest_Field {
    
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
            Class clazz = Class.forName("com.xiaojian.basics.basics_test.Son");
            Object object = clazz.newInstance();
    
    System.out.println("====== 获取公有的成员变量,包括父类的 ======");
            // 获取公有的成员变量,包括父类的
            Field[] fields1 = clazz.getFields();
            for(Field field : fields1){
                System.out.println(field.getName());
            }
    System.out.println("====== 获取所有的成员变量,包括私有的,不包括父类的 ======");
            // 获取所有的成员变量,不包括父类的
            Field[] fields2 = clazz.getDeclaredFields();
            for(Field field : fields2){
                System.out.println(field.getName());
            }
    
    System.out.println("====== 获取成员变量,公共的 ======");
            Field field1 = clazz.getField("son_pub");
            System.out.println(field1.get(object));
            field1.set(object,"重新set son_pub");
            System.out.println(field1.get(object));
    
    System.out.println("====== 获取成员变量,父类的 ======");
            Field field2 = clazz.getField("father_pub");
            System.out.println(field2.get(object));
            field1.set(object,"重新set father_pub");
            System.out.println(field1.get(object));
    
    System.out.println("====== 获取成员变量,私有的 ======");
            Field field3 = clazz.getDeclaredField("son_pri");
            // 不止是修改前,访问该成员变量前,都要设置 setAccessible
            field3.setAccessible(true);
            System.out.println(field3.get(object));
            field1.set(object,"重新set son_pri");
            System.out.println(field1.get(object));
        }
    
    }
    
    class Son extends Father{
        public String son_pub = "son pub";
        private String son_pri = "son pri";
    }
    
    class Father{
        public String father_pub;
        private String father_pri;
    
    }
    

4、反射的优缺点

尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。

  • 优点
    • 可扩展性:应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类。
  • 缺点
    • 性能开销:反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多
    • 安全问题:由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值