java 常量反射_Java中的反射

什么是反射

Java 反射是可以让我们在运行时获取类的函数、属性、父类、接口等 Class 内部信息的机制。

能做什么

反射可以访问或者修改程序的运行时行为

通过反射还可以让我们在运行期实例化对象,调用方法,通过调用 get/set 方法获取变量的值,即使方法或属性是私有的的也可以通过反射的形式调用,这种“看透 class”的能力被称为内省,这种能力在框架开发中尤为重要。 有些情况下,我们要使用的类在运行时才会确定,这个时候我们不能在编译期就使用它,因此只能通过反射的形式来使用在运行时才存在的类(该类符合某种特定的规范,例如 JDBC),这是反射用得比较多的场景。

还有一个比较常见的场景就是编译时我们对于类的内部信息不可知,必须得到运行时才能获取类的具体信息。比如 ORM 框架,在运行时才能够获取类中的各个属性,然后通过反射的形式获取其属性名和值,存入数据库。这也是反射比较经典应用场景之一。

核心API

java.lang.Class.java:类本身

java.lang.reflect.Constructor.java:类的构造器

java.lang.reflect.Method.java:类的方法

java.lang.reflect.Field.java:类的属性

例子

为了说明方便,先写了几个类

Person类

public class Person {

String name;

String sex;

public int age;

public Person(String name, String sex, int age) {

this.name = name;

this.sex = sex;

this.age = age;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public String getSex() {

return sex;

}

public void setSex(String sex) {

this.sex = sex;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

private String getDescription() {

return "黄种人";

}

}

ICompany接口

public interface ICompany {

String getCompany();

boolean isTopCompany();

}

继承 Person 实现 ICompany 接口的 ProgrameMonkey 类

public class ProgrameMonkey extends Person implements ICompany {

private String language = "C#";

private String company = "BBK";

public ProgrameMonkey(String name, String sex, int age) {

super(name, sex, age);

}

public ProgrameMonkey(String name, String sex, int age, String language, String company) {

super(name, sex, age);

this.language = language;

this.company = company;

}

public String getLanguage() {

return language;

}

public void setLanguage(String language) {

this.language = language;

}

public void setCompany(String company) {

this.company = company;

}

private int getSalaryPerMonth() {

return 123456;

}

@Override

public String getCompany() {

return company;

}

@Override

public boolean isTopCompany() {

return true;

}

}

Class

三种获取类信息的方式

通过 类名.class 的方式

Class> classObj = ProgrameMonkey.class;

通过调用 类的实例.getClass() 方法的方式

ProgrameMonkey programeMonkey = new ProgrameMonkey("小明", "男", 18);

Class> classObj = programeMonkey.getClass();

通过 Class.forName(完整类名) 的方式

Class> aClassObj = Class.forName("com.okada.reflect.ProgrameMonkey");

利用反射实例化一个对象

对于构造方式是私有的类 要怎么实例化

获取父类

Class> superclass = ProgrameMonkey.class.getSuperclass();

while (superclass != null) {

System.out.println(superclass.getName());

superclass = superclass.getSuperclass();

}

打印结果

com.okada.reflect.Person

java.lang.Object

获取实现的接口

Class>[] interfaces = ProgrameMonkey.class.getInterfaces();

for (Class> itf : interfaces) {

System.out.println(itf.getName());

}

打印结果

com.okada.reflect.ICompany

Constructor

获取 public 修饰的构造器

根据参数列表,调用相应的构造器,实例化一个对象。注意一下,getConstructor() 获取的是用 public 修饰的构造器

Constructor constructor = ProgrameMonkey.class

.getConstructor(String.class, String.class, int.class);

ProgrameMonkey programeMonkey = constructor.newInstance("小明", "男", 18);

获取私有的构造器

如果构造器是私有的怎么办?

Constructor constructor = ProgrameMonkey.class

.getDeclaredConstructor(String.class, String.class, int.class);

constructor.setAccessible(true);

ProgrameMonkey programeMonkey = constructor.newInstance("小明", "男", 18);

System.out.println(programeMonkey.getName());

要调用 getDeclaredConstructor(方法签名定义) 来获取 Constructor,同时还要调用 constructor.setAccessible(true)

Method

获取类的方法

获取当前类以及父类和接口的所有公开方法

Class> classObj = ProgrameMonkey.class;

Method[] methods = classObj.getMethods();

获取当前类以及接口的所有公开方法

Class> classObj = ProgrameMonkey.class;

Method[] declaredMethods = classObj.getDeclaredMethods();

调用方法

观察一下 ProgrameMonkey 类的定义。其中 setLanguage() 方法是这样定义的

public class ProgrameMonkey extends Person implements ICompany {

// ...

public void setLanguage(String language) {

this.language = language;

}

// ...

}

先传入方法名和方法签名,获取到一个表示 setLanguage 方法的 Method 对象

Method setLanguageMethod = classObj.getMethod("setLanguage", String.class);

然后传入 ProgrameMonkey 的实例和参数,得到运行结果。因为 setLanguage() 这个方法没有返回值,所以 result 为 null

Object result = setLanguageMethod.invoke(classObj, "JavaScript");

运行 getLanguage() 方法,观察一下是否真的改变了属性的值

System.out.println(programeMonkey.getLanguage());

打印结果

JavaScript

可以看到,属性 language 的值变了

调用私有方法

ProgrameMonkey 类有一个私有方法

public class ProgrameMonkey extends Person implements ICompany {

// ...

private int getSalaryPerMonth() {

return 123456;

}

// ...

}

如果按照一般的写法来写

Method getSalaryPerMonthMethod = classObj.getMethod("getSalaryPerMonth");

Object result = getSalaryPerMonthMethod.invoke(classObj);

会抛出异常

java.lang.IllegalAccessException: Class com.okada.filemanager.ReflectDemo can not access a member of class com.okada.reflect.ProgrameMonkey with modifiers "private"

at sun.reflect.Reflection.ensureMemberAccess(Unknown Source)

at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(Unknown Source)

at java.lang.reflect.AccessibleObject.checkAccess(Unknown Source)

at java.lang.reflect.Method.invoke(Unknown Source)

at com.okada.reflect.ReflectDemo.main(ReflectDemo.java:13)

应该调用 getDeclaredMethod() 方法,否则会抛出 java.lang.NoSuchMethodException 异常。因为 getMethod() 获取的是公开方法

Method getSalaryPerMonthMethod = programeMonkey.getClass().getDeclaredMethod("getSalaryPerMonth");

另外还要加一句话 getSalaryPerMonthMethod.setAccessible(true);

Method getSalaryPerMonthMethod = classObj.getMethod("getSalaryPerMonth");

getSalaryPerMonthMethod.setAccessible(true);

Object result = getSalaryPerMonthMethod.invoke(classObj);

获取方法返回值类型

Class> returnType = getSalaryPerMonthMethod.getReturnType();

判断访问修饰符是否为 private

Modifier.isPrivate(getSalaryPerMonthMethod.getModifiers())

Field

获取属性

获取所有当前类以及父类和接口中用 public 修饰的属性

ProgrameMonkey programeMonkey = new ProgrameMonkey("小明", "男", 18);

Field[] fields = programeMonkey.getClass().getFields();

for (Field field : fields) {

System.out.println(field.getName());

}

因为只有 age 属性是用 public 修饰的,所以打印结果为

age

获取当前类所有的属性,不管是什么修饰符修饰的

ProgrameMonkey programeMonkey = new ProgrameMonkey("小明", "男", 18);

Field[] fields = programeMonkey.getClass().getDeclaredFields();

for (Field field : fields) {

System.out.println(field.getName());

}

可以看到 ProgrameMonkey 用 private 修饰的属性都拿到了

language

company

获取属性的值

ProgrameMonkey programeMonkey = new ProgrameMonkey("小明", "男", 18);

Field[] fields = programeMonkey.getClass().getDeclaredFields();

for (Field field : fields) {

try {

field.setAccessible(true); // 别忘了这句话

System.out.println(field.get(programeMonkey));

} catch (IllegalArgumentException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

打印结果

C#

BBK

还可以获取指定属性的值

try {

ProgrameMonkey programeMonkey = new ProgrameMonkey("小明", "男", 18);

Field ageField = programeMonkey.getClass().getField("age");

System.out.println(ageField.getInt(programeMonkey));

} catch (NoSuchFieldException e) {

e.printStackTrace();

} catch (SecurityException e) {

e.printStackTrace();

} catch (IllegalArgumentException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

打印结果

18

设置属性值

try {

ProgrameMonkey programeMonkey = new ProgrameMonkey("小明", "男", 18);

Field ageField = programeMonkey.getClass().getField("age");

System.out.println("before age=" + ageField.getInt(programeMonkey));

ageField.setInt(programeMonkey, 10086);

System.out.println("after age=" + ageField.getInt(programeMonkey));

} catch (NoSuchFieldException e) {

e.printStackTrace();

} catch (SecurityException e) {

e.printStackTrace();

} catch (IllegalArgumentException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

打印结果

before age=18

after age=10086

反射可以修改 final 修饰的常量的值吗?

编译器会对代码进行优化

来看一个例子

public class Config {

public final int CONSTANT_VARIABLE = 9527;

}

public class ReflectDemo {

public static void main(String[] args) {

System.out.println(new Config().CONSTANT_VARIABLE);

}

}

在编译 .java 文件得到 .class 文件的过程中,编译器会对代码进行优化。

来实验一下就知道了。首先使用 Eclipse 导出 jar 包,然后使用 jd-gui 工具打开可以看到反编译之后得到的 .java 文件

public class Config

{

public final int CONSTANT_VARIABLE = 9527;

}

public class ReflectDemo

{

public static void main(String[] args)

{

new Config().getClass();System.out.println(9527);

}

}

可以看到 System.out.println(Config.CONSTANT_VARIABLE); 被编译器优化成了 System.out.println(9527);,Config 类没有被引用到。这些都是编译器对代码进行优化的结果。

所以即使使用反射把 CONSTANT_VARIABLE 的值给改了,依然不能改变 System.out.println(9527); 的结果,这没有意义。

如果我一定要改呢?

那只能修改源码了,换一种代码的写法,不让编译器对代码进行优化。刚才的写法,常量是在声明的时候同时赋值,现在改成常量在构造器里赋值

public class Config {

public final int CONSTANT_VARIABLE;

public Config() {

CONSTANT_VARIABLE = 9527;

}

}

public class ReflectDemo {

public static void main(String[] args) {

System.out.println(new Config().CONSTANT_VARIABLE);

}

}

反编译之后的代码

public class Config

{

public final int CONSTANT_VARIABLE;

public Config()

{

this.CONSTANT_VARIABLE = 9527;

}

}

public class ReflectDemo {

public static void main(String[] args) {

System.out.println(new Config().CONSTANT_VARIABLE);

}

}

可以看到,编译器没有对这两个类进行优化,因为根本无法优化。现在使用反射来改变一下 CONSTANT_VARIABLE 的值

public class ReflectDemo {

public static void main(String[] args) {

try {

Config cfg = new Config();

Field finalField = cfg.getClass().getDeclaredField("CONSTANT_VARIABLE");

finalField.setAccessible(true);

System.out.println("before modify, CONSTANT_VARIABLE=" + finalField.getInt(cfg));

finalField.setInt(cfg, 1234);

System.out.println("after modify, CONSTANT_VARIABLE=" + finalField.getInt(cfg));

System.out.println("actually, CONSTANT_VARIABLE=" + cfg.CONSTANT_VARIABLE);

} catch (NoSuchFieldException e) {

e.printStackTrace();

} catch (SecurityException e) {

e.printStackTrace();

} catch (IllegalArgumentException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

}

打印结果

before modify, CONSTANT_VARIABLE=9527

after modify, CONSTANT_VARIABLE=1234

actually, CONSTANT_VARIABLE=1234

可以看到,在修改之前,CONSTANT_VARIABLE 的值是 9527,接着使用反射把 CONSTANT_VARIABLE 的值改成 1234。最后调用 cfg.CONSTANT_VARIABLE 验证一下,是否修改成功,发现修改成功了。

所以,如果要该常量的值,只能在代码的写法上进行变通,避免编译器的优化。

开发中的实际应用

获取注解信息

在 Android 应用开发的过程中,经常需要写 findViewById()。这样的重复工作可以交给注解来完成。

首先定义一个注解

@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)

public @interface InjectView {

int value();

}

在 Activity 中使用注解

public class MainActivity extends AppCompatActivity {

@InjectView(R.id.tv)

TextView mTextView;

}

然后在 onCreate() 方法中解析注解,去帮我们执行 findViewById 的操作

public class MainActivity extends AppCompatActivity {

@InjectView(R.id.tv)

TextView mTextView;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

Views.inject(this); // 解析注解

}

}

解析注解的过程如下

public class Views {

public static void inject(Activity activity) {

Class extends Activity> activityClass = activity.getClass();

Field[] fields = activityClass.getDeclaredFields();

for (Field field : fields) {

InjectView injectViewAnnotation = field.getAnnotation(InjectView.class);

if (injectViewAnnotation != null) {

View view = activity.findViewById(injectViewAnnotation.value());

try {

field.setAccessible(true);

field.set(activity, view);

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

}

}

}

利用反射,获取实例的注解信息,然后获取到注解的值,最后去调用 findViewById() 方法。

工作原理

当我们编写完一个 Java 项目之后,所有的 Java 文件都会被编译成一个.class 文件,这些 Class 对象承载了这个类型的父类、接口、构造函数、方法、属性等原始信息,这些 class 文件在程序运行时会被 ClassLoader 加载到虚拟机中。当一个类被加载以后,Java 虚拟机就会在内存中自动产生一个 Class 对象。我们通过 new 的形式创建对象实际上就是通过这些 Class 来创建,只是这个过程对于我们是不透明的而已。

所以,只要拿到了 Class 对象,就可以做一系列的反射操作

参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值