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 对象,就可以做一系列的反射操作

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值