开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 12 天,点击查看活动详情
最近碰到一个问题:数据库字段变更,所有字段都变为 not null。那么程序里面对应的插入/更新操作的代码就需要改变。需要对实体类进行初始化。
刚开始在方法内,我习惯性的想写一个公共方法 ,进入方法的时候把实体类传过去,初始化实体类的值。但是我发现我不知道怎么处理,虽然隐约感觉使用反射可以解决。但是不会啊。0.0
这就为学习反射埋下伏笔,借此总结一下反射的学习心得。(当然这里更好的方法是直接改实体类的代码)
反射概念
Java 的反射是指程序在运行期可以拿到一个对象的所有信息。也就是我们不用具体的 new 一个对象,而是在运行时期才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
Class 类
如何获取 Class
获取 Class 对象的三种方式:定义一个 Foo.java
class Foo{
public String publicString;
protected String protectedString;
String defaultString;
private String privateString;
//创建了三个构造函数
public Foo(){
}
private Foo(String publicString, String protectedString) {
this.publicString = publicString;
this.protectedString = protectedString;
}
public Foo(String publicString, String protectedString, String defaultString, String privateString) {
this.publicString = publicString;
this.protectedString = protectedString;
this.defaultString = defaultString;
this.privateString = privateString;
}
//下面省略 get/set 方法以及 toString() 方法
}
- Class.forName("完整的包名"); //编译前
- 最熟悉的就是加载数据库驱动,多用于配置文件
- Foo.class:我们将 .java 文件编译之后,就是 .class 文件 //编译后
- 一般用在作为参数传递
- new Foo.getClass(); //运行时
- 对象获取字节码方式
Class 常用方法
获取 Field 对象
- getFields()
- getDeclaredFields()
//Class 获取 Field 对象
//以 Class.foeName("") 方式获取 Class 对象
Class foo = Class.forName("com.practice.thinkinbasic.invoke.Foo");
Field[] fields = foo.getFields();
for(Field f:fields){
System.out.println(f);
}
//打印日志如下:
public java.lang.String com.practice.thinkinbasic.invoke.Foo.publicString
Field[] fields = foo.getDeclaredFields();
//打印日志如下:
public java.lang.String com.practice.thinkinbasic.invoke.Foo.publicString
protected java.lang.String com.practice.thinkinbasic.invoke.Foo.protectedString
java.lang.String com.practice.thinkinbasic.invoke.Foo.defaultString
private java.lang.String com.practice.thinkinbasic.invoke.Foo.privateString
getFields():只能获取 public 修饰的属性
getDeclaredFields():可以获取所有修饰符修饰的属性
//获取单个属性:
Field field = foo.getDeclaredField("privateString"); //正常获取
Field field1 = foo.getField("privateString"); //会报错:java.lang.NoSuchFieldException: privateString
设置 Field 对象的值
Foo f = new Foo();
//Class 获取 Field 对象
//以 Class.foeName("") 方式获取 Class 对象
Class foo = Class.forName("com.practice.thinkinbasic.invoke.Foo");
Field field = foo.getDeclaredField("publicString");
field.set(f,"1234");
Field field1 = foo.getDeclaredField("privateString");
field1.setAccessible(true); //暴力反射(必须加上才能给非 public 修饰的属性设置值)
field1.set(f,"4321");
System.out.println(f);
获取 Constructor 对象
getConstructors() //只能获取 public 修饰的构造函数
getDeclaredConstructors() //能获取所有的构造函数
//获取单个 Constructor 对象在下面设置 Constructor 里面介绍
设置 Constructor 对象
//Class 获取 Constructor 对象
//这里的 c 是接受其他方法传递过来的 Class 对象
Constructor cons = c.getDeclaredConstructor(String.class,String.class);
cons.setAccessible(true);
Object obj=cons.newInstance("1234","4321");
System.out.println(obj);
//获取单个的构造函数需要指定参数列表(当你有多个参数的时候)
//这里就可以解答我最开始提出的问题,传一个 .class 参数到公共方法,然后使用 getDeclaredConstructor() 初始化这个实体类
获取 Method 对象
getMethods() //获取自己和父类的所有 public 方法
getDeclaredMethods() //获取自己的所有方法,但是没有父类的
调用 Method 方法
//Class 获取 Method 对象
//这里的 foo 是使用对象.方法获取的。(这里只是演示,实际不可能这么写)
Foo f = new Foo();
Class foo = f.getClass();
Method methods = foo.getMethod("publicMethod");
//methods.setAccessible(true); 如果需要调用私有方法,则需要设置暴力反射
methods.invoke(f);
//就会调用 Foo 类下的 publicMethod() 方法
桥接方法
public interface SuperClass<T> {
void method(T t);
}
class ziClass implements SuperClass<String> {
@Override
public void method(String o) {
System.out.println("abc");
}
}
1.先使用 javac SupperClass.java 生成 class 文件
2.javap -p ziClass.class 查看反编译后的信息
上面的 Object 入参的方法就是桥接方法。实际没有使用的。
isAssignableFrom
methodType.isAssignableFrom(getterType)
getterType 是不是 methodType 的子类或子接口
总结
对于一个类来说,无非就是 成员变量(Field)、成员方法(Method)、构造函数(Constructor) 三种。掌握这三种,基本就可以了解反射。
像 IDEA 开发工具中的方法提示,其实就是使用了反射的功能;Spring IOC/DI ; MyBatis 等等,更是反射的良好体现。