反射 总结

反射

1. 概述

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

​ 使用的前提是:首先需要获取该类的字节码文件对象。必须首先得到代表的字节码的Class,Class类用于表示.class文件(字节码)

2. 创建对象的三种方式

2.1 方式一源代码阶段

Class<T> Class.forName(String packageAndClassName);
	表示返回的一个Class对象,表示名为className的类

	Class 类型提供的静态成员方法,根据用户提供的完整包名.类名,获取对应数据类型的Class 对象。
	异常:ClassNotFoundException 指定的类型未找到异常
        
  第一种方式:使用在源代码阶段
  1. 如果对应的类型已经在 JVM 中进行加载过,直接获取对应的 Class对象
  2. 如果对应类型尚未加载 JVM 中,加载对应的类型,获取对应的Class对象

2.2. 方式二加载阶段

Class<T> 类名.class;

通过java中任意的类名,获取其属性内容,得到对应的class对象
    
使用在加载的阶段:

2.3 方式三运行阶段

Class<? extend T> 对象.getClass();
    
    java 中的任意一个类兑现,都可以通过getClass方法获取对应的Class类型的对象。该方法是Object类型提供给Java中所有类型的使用方法。
    
    可以使用在运行的阶段

代码演示

/*
 万恶之源
*/
//方式一
Class<?> cls = Class.forName("cn.reflect.Person");
// 方式二
Person person = new Person();
Class<? extends Person> acl = person.getClass();
// 方式三
Class<Person> personClass = Person.class;

3. 反射操作实例化对象(调用对象是Class)

3.1 Consturctor 构造方法类型

.3.1.1 概述

针对于Class对象,构造方法的名称是类名,不具备唯一性,在实际调用构造反方,区分构造方法的不同是通过,参数列表、类型、个数、参数顺序来区分。

3.1.2 通过Class对象获取对应的构造方法对象
Constructor[] getConstructors();
	获取当前Class对应数组类型中的所有的非私有化的构造方法对象数组。

Constructor getConstructor(Class ... parameterTypes);
	表示的是获取当前指定的参数的顺序,个数获取对应的Constructor构造方法对象,并且是非私有化构造方法。

Constructor[] getDeclaredConstructors();
	暴力反射,获取当前Class对应数据类型的所有构造方法对象数组,包括私有化构造方法。
        
Constructor getDeclaredConstructor(Class...parameterTypes);
	暴力反射,根据用户指定的参数类型,顺序,个数获取对应的Constructor构造方法对象,可以获取私有化构造方法。
  • Class: 约束数据类型,当前方法所需的参数类型
    例如:
    这里需要int类型 int.class
    这里需要String类型 String.class
    之类需要Perosn类型 Person.class

  • 异常:
    NoSuchMethodException

  • … : 不定长参数
    构造方法需要的参数类型是很多的,有可能无参数,有可能有参数。… 不定长参数
    类约束使用,增强代码的普适性
    例如:
    这里无参数 () or (null)
    参数类型int类型 (int.class)
    参数类型int, String类型 (int.class, String.class)

  • initArgumentTypes:
    参数名 初始化参数类型复数

代码演示

获取非私有化的构造方法对象数组

 /**
  * 获取当前Class对应的数据类型的所有【非私有化】构造方法
  */
Constructor<?>[] constructors = cls.getConstructors();
for (Constructor<?> constructor : constructors) {
    System.out.println(constructor);
}

运行的效果:
public cn.reflect.Person(int,java.lang.String)
public cn.reflect.Person(int)
public cn.reflect.Person()

获取当前所有构造方法对象数组

代码演示

Constructor[] constructors = aClass.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
    System.out.println(constructor);
}
//结果
private cn.reflect01.Person(java.lang.String)
public cn.reflect01.Person(int,java.lang.String)
public cn.reflect01.Person(int)
public cn.reflect01.Person()

获取指定参数的构造方法

代码演示

 Constructor<?> constructor = aClass.getConstructor(int.class,String.class);
 System.out.println(constructor);
// 运行结果
public cn.reflect01.Person(int,java.lang.String)
3.1.3 Constructor 对象实例化对象操作

方法

Object newInstance(Object ... paraterValues);

Object:表示的是指定返回值类型
    
    创建实例化对象注意类型的转换,使用什么类型强制转换什么类型,因为默认的构造的类型是Object类型 

使用方式

通过constructor 构造方法对象调用newInstance 进行实例化对象操作。

参数

当前方法所需的参数是构造方法中的参数列表对应的实际参数,采用的参数形式为Object... 不定长的参数,在方法的内部是一个Object类型的数组。

代码演示

也就是说在进行实例化对象的创建的时候需要注意的是将类型进行转换
Constructor<?> constructor = aClass.getConstructor();

/**
  * 通过实力化无参构造器的对象
  */
Person person = (Person) constructor.newInstance();
System.out.println(person);
运行结果:
    Person [id=0, name=null, test=10]


/**
 * 实例化有参构造器的对象
 */

Constructor<?> constructor1 = aClass.getConstructor(int.class, String.class);
Person person1 = (Person) constructor1.newInstance(10, "张三");

System.out.println(person1);
运行结果:
	Person [id=10, name=张三, test=10]

4. Method(调用对象Class)

4.1 概述

在反射的操作中,我们真正关注的是 方法名称 和 形式参数列表

4.2 通过Class对象获取对应的Method成员方法对象

Method[] getMethods();

​ 获取对应的所有的非私有化的成员方法对象数组,包括父类继承子类,并且为子类提供使用的成员方法。

Method[] getDeclaredMethods();

​ 暴力反射,可以获取对象对应类型的成员方法对象数组,包括私有化成员方法,但是不包括继承父类的成员方法,有且只有子类的自身方法。

核心的方法

Method getMethod(String methodName,Class ... paratetypes);

根据指定的方法名称和指定的方法参数类型,获取成员方法对象,只有是非私有化成员方法和父类继承给子类的方法。

Method getDeclaredMethod(String methodName,Class ... paratetypes);

​ 暴力反射,根据指定的方法名称和制定的方法参数类型,获取成员方法对象,包括私有化成员方法,不包括父类继承到子类的方法,仅仅获得子自有方法。

Method getDeclaredMethod(String methodName,Class ... paratetypes);

methodName: 
		方法名,指定获取的是哪一个方法
	parameterTypes:
		Class用于约束当前使用你的参数数据类型
		... 不定长参数,方法参数个数,顺序,有参无参问题
	例如:
		cls是Class类对象
		cls.getMethod("setName", String.class);
		cls.getMethod("getName");
 /**
   *  使用反射中的调用方法,调用的对象是Class,获取类中的所有非私有的方法数组
   */
Method[] methods = aClass.getMethods();
for (Method method : methods) {
    System.out.println(method);
}
结果:
public java.lang.String cn.reflect01.Person.toString()
public java.lang.String cn.reflect01.Person.getName()
public int cn.reflect01.Person.getId()
public void cn.reflect01.Person.setName(java.lang.String)
public static void cn.reflect01.Person.test()
public void cn.reflect01.Person.game()
public void cn.reflect01.Person.game(java.lang.String)
public void cn.reflect01.Person.setId(int)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()



//获取指定的方法
Method setName = aClass.getMethod("setName", String.class);
System.out.println(setName);

结果:
    public void cn.reflect01.Person.setName(java.lang.String)

4.3 成员对象执行对应方法操作

通过Method 对象使用以下的方法,执行对应方法对应的方法运行目标

Object invoke(Object obj, Object ... parameterValues);
  1. 参数一:用Obj对象调用该方法;
  2. 参数二:调用方法的传递参数,参数可有可无。
  3. 返回值:方法的返回值,没有就不用写;
 		Method setName = aClass.getMethod("setName", String.class);
        System.out.println(setName);

        Person o =(Person) aClass.getConstructor(int.class,String.class).newInstance(100, "吴迪");

        System.out.println(o);
        setName.invoke( o,"zhansna");
        System.out.println(o);

总结:首先需要明确操作的对象,才能够在指定的对象的方法中进行使用。
        
运行结果:
public void cn.reflect01.Person.setName(java.lang.String)
Person [id=100, name=吴迪, test=10]
Person [id=100, name=zhansna, test=10]

5. 反射操作Field数据存储和获取

5.1 概述

成员变量名称是成员变量的唯一标识符

5.2 通过Class对象获取对应的成员变量对象

  1. Field[] getFields();
       
    获取类中的所有的非私有化成员变量数组
    
  2. Field[] getDeclaredFields();
       
    获取类中所有成员变量数组,包括私有化成员变量对象
    
  3. Field getField(String fieldname);
       
    根据指定的成员变量的名称,获取对应的非私有化成员变量对象
    
  4. Field getDeclaredField(String fieldname);
       
    根据指定的成员变量的名称,获取指定的成员变量对象,包括私有化成员变量
    

5.3 (对象)操作获取数据和赋值数据

赋值操作方法

void set(Object obj,Object value);

参数一:表示的是当前成员变量到底是哪一个类对象
参数二:value表示的是给当前的成员变量的赋值和数据的内容,考虑符合成员变量的数据类型。

取值操作

Object get(Object obj);

参数一:表示的是当前成员变量到底是哪一个类对象
 返回值的类型是无论当前成员变量是什么类型,统一返回Object类型,根据所需强转目标数据类型。

代码验证

// 获取所有的成员变量,无论是private或者public

 Field[] declaredFields = aClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
  }

运行的结果:
private int cn.reflect01.Person.id
private java.lang.String cn.reflect01.Person.name
public int cn.reflect01.Person.test
public static int cn.reflect01.Person.testStatic
    
    
    //通过指定的名称获取成员变量
Field na = aClass.getDeclaredField("name");
System.out.println(na);
运行结果:
    private java.lang.String cn.reflect01.Person.name


对象操作获取数据和赋值数据
    
Constructor<?> constructor1 = aClass.getConstructor(int.class, String.class);
Person instance = (Person) constructor1.newInstance(12, "张飞");
instance.setId(12);
int id = instance.getId();
System.out.println(id);
System.out.println(instance);
运行结果:
    
12
Person [id=12, name=张飞, test=10]

针对对象中的成员变量进行操作

 Constructor<?> constructor1 = aClass.getConstructor(int.class, String.class);

Field name = aClass.getDeclaredField("name");
Field id = aClass.getDeclaredField("id");

Person instance = (Person) constructor1.newInstance(12, "张飞");
id.set(instance,13);
name.set(instance,"关羽");
System.out.println(instance);


出现异常错误:
    没有权限,原因是name的权限是private所以只能够通过更改权限。
    
更改权限后

Constructor<?> constructor1 = aClass.getConstructor(int.class, String.class);

Field name = aClass.getDeclaredField("name");
Field id = aClass.getDeclaredField("id");

AccessibleObject.setAccessible(new AccessibleObject[]{id,name},true);

Person instance = (Person) constructor1.newInstance(12, "张飞");
System.out.println(instance);
id.set(instance,13);
name.set(instance,"关羽");
System.out.println(instance);

运行的结果是:

Person [id=12, name=张飞, test=10]
Person [id=13, name=关羽, test=10]
    

6. 暴力反射更改权限

对应方法

void setAccessible(boolean flag)
    
    反射操作中的所有constructor method field对象都可以调用该方法解决私有化的反射操作的权限的问题。参数提供的是true 当前私有化构造方法有对应的操作权限。

工具类方法

public static void setAccessible(AccessibleObject[] array,boolean flag)
    AccessibleObject是一种类工具反方,所需要的参数是AccessibleObject数组和对应的权限标记,flag 通常是true
    
   其中field、method constructor 都是AccessibleObject子类。

7. 总结

​ 在默认的情况下,方法的反射调用是委派的实现,委派给本地实现来进行方法的调用。在调用15次之后,委派实现变回将委派对象切换至动态的实现。这种动态的实现的字节码是自动生成的,它将直接使用invoke指令来调用目标方法。

7.1 优点

  1. 增加了程序的灵活性,面对需求变更的时候,可以灵活地实例化不同的对象

7.2 缺点

  1. 破坏了类的封装性:可以在强制访问private修饰的信息。
  2. 性能的损耗:反射相比直接实例化对象,调用方法、访问变量、中间需要非常多的检查的步骤和解析步骤,JVM无法对他们进行优化处理。
  3. 反射的速度较慢是需要同时调用上100W次才可能体现出来,通过几次上百次的调用,不能体现反射的性能低下。如果程序的性能要求很高,那么尽量不要使用反射。

7.3 反射效率低分析

Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
    
    翻译:由于反射涉及动态解析的类型,因此不能执行某些Java虚拟机优化。因此,反射操作的性能比非反射操作慢,在性能敏感应用程序中频繁调用的代码段中应该避免使用。

由于反射涉及到动态加载的类型,所以无法进行性能的优化操作。

  1. invoke 方法会对参数作封装和解封的操作,首先我们知道involve方法的参数是Object[]类型 ,也就是说如果方法是简单的类型,我们同样需要在此处将之转换成为Object类型。例如 long ,在 javac compile 的时候 用了Long.valueOf() 转型,也就大量了生成了Long 的 Object, 同时 传入的参数是Object[]数值,那还需要额外封装object数组。
  2. 需要检查方法的可见性
  3. 通过校验参数
  4. 反射方法难通过内联
  5. JIT 无法优化
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值