Java 基础知识--反射
反射在Java编程中是很常用的功能,开发和阅读源码时,总能看到反射的身影。这里要强调一下,Java的反射真的很简单,很简单,很多人以为Java的反射是一个很高深的知识点,一直不敢去触碰,以至于成为很多程序员的短板。接下来就一起来看看这个简单了一逼的反射机制
Java的反射
反射概述
Java的反射是 在运行状态中 ,对于任何一个类,都能知道它的所有属性和方法;对于任何一个对象,都能调用它的所有属性和方法。这种能够 动态获取类的信息 和 动态调用对象的方法 的特性就是Java的反射机制
简单的说反射就是:动态获取类的信息 和 动态调用对象的方法(或属性)
反射的关键:获取代表字节码文件(.class文件)的Class对象
注: .java 文件通过编译最终生成 .class 文件
注: Class对象代表着 .class(字节码文件) [ 当然要先得到Class对象 ]
类加载简单示例
先简单了解一下类的加载过程
Java编译器会将 .java 文件编译成 .class 文件
1.当程序执行 new User() 时,JVM会查找并加载 User.class 到内存中
2.Jvm 将 .class 加载到内存,自动创建一个Class对象。 Class对象由JVM创建,有且仅有一个,第二次 new User() 不再产生新的 Class 对象
3.一个类对应一个Class对象
反射的本质就是:得到 Class 对象后,反向获取 User 类的各种信息。比如:构造方法,成员变量,方法等
接下来就看看如何使用 Class 对象获取到 User 类的各种信息
Java反射基础API使用
首先,创建一个 User 类
public class User {
}
这个类本身没有什么意义,用于演示反射示例
1.获取Class对象的方式
1.通过 Object 的 getClass()
Object 类中存在 getClass() 方法,所有对象继承自 Object 因此可以使用 Object 的 getClass() 方法获取 Class 对象
2.通过任意类的“静态”class属性 例如:User.class
3.通过Class类的静态方法 Class.forName(String name)
public static void main(String args[]) {
//1.使用Object 的 getClass() 方式获取 Class 对象
User user = new User();
Class uClass1 = user.getClass();
System.out.println("type1:" + uClass1.getName());
//2.使用任意类的"静态"class 属性
Class uClass2 = User.class;
System.out.println("type2:" + uClass2.getName());
//3.使用Class静态方法 Class.
try {
Class uClass3 = Class.forName("demo.reflex.User");//参数是包括包名的完整路径
System.out.println("type3:" + uClass3.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
结果:
type1:demo.reflex.User
type2:demo.reflex.User
type3:demo.reflex.User
注意:运行期间,只有一个Class对象存在
以上几种方式都可以获取Class对象。第一个种能获取到对象了,一般就不需要使用反射;第二种需要导入包,依赖性太强;第三种较为常用,也比较符合反射的场景,根据一个完整的类名获取Class对象
2.通过反射获取类构造函数 Constructor
User 类
public class User {
User(char ch) {
System.out.println("默认 构造方法 ch:" + ch);
}
public User() {
System.out.println("public 构造方法 无参");
}
public User(String name) {
System.out.println("public 构造方法 name:" + name);
}
public User(String name, int age) {
System.out.println("public 构造方法 name:" + name + " age:" + age);
}
protected User(int age) {
System.out.println("protected 构造方法 age:" + age);
}
private User(boolean flag) {
System.out.println("private 构造方法 flag:" + flag);
}
}
User 类构造方法,包括了 public , private , protected , 默认 类型的构造方法
测试使用
public static void main(String args[]) throws Exception {
//获取Class
Class cls = Class.forName("demo.reflex.User");
System.out.println("-------获取所有公共的构造方法---------");
Constructor[] consArray = cls.getConstructors();
for (int i = 0; i < consArray.length; i++) {
System.out.println(consArray[i]);
}
System.out.println("-------获取所有的构造方法 包括:public,private,protected,默认类型---------");
consArray = cls.getDeclaredConstructors();
for (int i = 0; i < consArray.length; i++) {
System.out.println(consArray[i]);
}
System.out.println("-------获取 无参 构造方法并调用---------");
Constructor cons = cls.getConstructor();
System.out.println(cons);
cons.newInstance();
System.out.println("-------获取 private 构造方法并调用---------");
cons = cls.getDeclaredConstructor(boolean.class);
System.out.println(cons);
cons.setAccessible(true);//暴力访问(忽略访问修饰符)
cons.newInstance(true);
}
结果输出
-------获取所有公共的构造方法---------
public demo.reflex.User(java.lang.String,int)
public demo.reflex.User(java.lang.String)
public demo.reflex.User()
-------获取所有的构造方法 包括:public,private,protected,默认类型---------
private demo.reflex.User(boolean)
protected demo.reflex.User(int)
public demo.reflex.User(java.lang.String,int)
public demo.reflex.User(java.lang.String)
public demo.reflex.User()
demo.reflex.User(char)
-------获取 无参 构造方法并调用---------
public demo.reflex.User()
public 构造方法 无参
-------获取 private 构造方法并调用---------
private demo.reflex.User(boolean)
private 构造方法 flag:true
api解释
1.获取所有构造方法 返回符合要求的 列表
//获取所有"公共的"构造方法 public
public Constructor<?>[] getConstructors()
//获取所有的构造方法 包括: public,private,protected,默认类型
public Constructor<?>[] getDeclaredConstructors()
2.获取单个构造方法 返回符合要求的 对象
//获取"公共的"构造方法
public Constructor<T> getConstructor(Class<?>... parameterTypes)
/**
*获取任意访问类型的构造方法
*
*parameterTypes:形参类型(记住是类型) 例:int.class ,String.class
*/
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
3.调用构造方法
/**
*调用构造方法
*
*initargs:构造方法参数
*/
public T newInstance(Object ... initargs)
newInstance()
方法属于 Constructor
类,无参构造方法参数可以 null 或者 不填; 对于调用 private 私有构造方法需要调用 cons.setAccessible(true);
表示打破限制,强制访问,否则出错
3.通过反射获取类成员变量 Field
User类
public class User {
public User() {
}
/*成员变量*/
private int age;
public String name;
protected String phone;
boolean isVip;
@Override
public String toString() {
return "[User name=" + name + ", age=" + age + "]";
}
}
测试使用
public static void main(String args[]) throws Exception {
//获取Class
Class cls = Class.forName("demo.reflex.User");
System.out.println("-------获取所有公共的成员变量---------");
Field[] fieldArray = cls.getFields();
for (int i = 0; i < fieldArray.length; i++) {
System.out.println(fieldArray[i]);
}
System.out.println("-------获取所有的成员变量 包括:public,private,protected,默认类型---------");
fieldArray = cls.getDeclaredFields();
for (int i = 0; i < fieldArray.length; i++) {
System.out.println(fieldArray[i]);
}
System.out.println("-------获取公共属性---------");
Field field = cls.getField("name");
System.out.println(field);
System.out.println("-------设置属性并查看值---------");
//根据构造函数 .newInstance() 获取类对象
Object object = cls.getConstructor().newInstance();
field.set(object, "Ruffian");
//验证刚刚设置的属性
User user = (User) object;
System.out.println("查看name属性值:" + user.name);
System.out.println("-------获取私有属性---------");
field = cls.getDeclaredField("age");
System.out.println(field);
System.out.println("-------设置私有属性并查看值---------");
field.setAccessible(true);
field.set(object, 18);
//验证刚刚设置的属性
System.out.println("查看age属性值:" + user.toString());
}
结果输出:
-------获取所有公共的成员变量---------
public java.lang.String demo.reflex.User.name
-------获取所有的成员变量 包括:public,private,protected,默认类型---------
private int demo.reflex.User.age
public java.lang.String demo.reflex.User.name
protected java.lang.String demo.reflex.User.phone
boolean demo.reflex.User.isVip
-------获取公共属性---------
public java.lang.String demo.reflex.User.name
-------设置属性并查看值---------
查看name属性值:Ruffian
-------获取私有属性---------
private int demo.reflex.User.age
-------设置私有属性并查看值---------
查看age属性值:[User name=Ruffian, age=18]
api解释
1.获取所有属性 返回符合要求的 列表
//获取所有"公共的"属性 public
public Constructor<?>[] getFields()
//获取所有的属性 包括: public,private,protected,默认类型
public Constructor<?>[] getDeclaredFields()
2.获取单个属性 返回符合要求的 对象
//获取"公共的"属性
public Field getField(String fieldName)
/**
*获取任意访问类型的属性
*
*fieldName:属性名称 例: name , age
*/
public Field getDeclaredField(String fieldName)
3.设置属性值
/**
*1.obj:要设置的字段所在的对象;
*2.value:要为字段设置的值;
*/
public void set(Object obj,Object value)
field.set(object, "Ruffian");
表示为属性设置值: user.name=Ruffian
第一个参数:要设置的字段所在的对象
第二个参数:要为字段设置的值
至于上述示例中获取属性值的方式,是为了方便校验通过反射设置值是否成功,一般我们通过反射获取方法,得到属性值。下面看看如何通过反射获取成员方法和调用
4.通过反射获取成员方法 调用方法 Method
User类
public class User {
public void showName(String name) {
System.out.println("public showName() 参数name = " + name);
}
protected void show() {
System.out.println("public show()");
}
void showDefault() {
System.out.println(" showDefault() 默认无参方法");
}
private String getAge(int age) {
System.out.println("private getAge() 返回值:字符串 参数:int age = " + age);
return "age:" + age;
}
}
使用示例
public static void main(String args[]) throws Exception {
//获取Class
Class cls = Class.forName("demo.reflex.User");
System.out.println("-------获取所有方法---------");
//Method[] methodArray = cls.getMethods();
Method[] methodArray = cls.getDeclaredMethods();
for (int i = 0; i < methodArray.length; i++) {
System.out.println(methodArray[i]);
}
System.out.println("-------调用任意访问类型方法---------");
//根据构造函数 .newInstance() 获取类对象
Object object = cls.getConstructor().newInstance();
Method method = cls.getDeclaredMethod("getAge", int.class);
method.setAccessible(true);
Object result = method.invoke(object, 18);
System.out.println("方法返回值:" + result.toString());
}
结果输出
-------获取所有方法---------
private java.lang.String demo.reflex.User.getAge(int)
public void demo.reflex.User.showName(java.lang.String)
protected void demo.reflex.User.show()
void demo.reflex.User.showDefault()
-------调用任意访问类型方法---------
private getAge() 返回值:字符串 参数:int age = 18
方法返回值:age:18
api解释
1.获取所有方法 返回符合要求的 列表
//获取所有"公共的"方法 public
public Method[] getMethods()
//获取所有的方法 包括: public,private,protected,默认类型
public Method[] getDeclaredMethods()
2.获取单个方法 返回符合要求的 对象
//获取"公共的"方法
public Method getMethod(String name, Class<?>... parameterTypes)
/**
*获取任意访问类型的方法
*
*name:方法名称 例: getAge , showName
*parameterTypes:方法形参类型(记住是类型) 例:int.class ,String.class
*/
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
3.调用方法
/**
*obj:要调用方法所在的对象
*args:调用方法所需的参数
*/
public native Object invoke(Object obj, Object... args)
method.invoke(object, 18);
表示调用方法: User user=new User(); user.getAge(18);
第一个参数:
要调用方法所在的对象
如果调用方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null
第二个参数:
调用方法所需的参数
调用方法所需的形参个数为 0,则所提供的 args 数组长度可以为 0 或 null
4.Method 类的一点拓展
了解一个方法可以通过: 方法名称,方法修饰符,返回类型,形参类型/个数
看下示例程序
public static void main(String args[]) throws Exception {
//获取Class
Class cls = Class.forName("demo.reflex.User");
Method[] methodArray = cls.getDeclaredMethods();
Method method;
for (int i = 0; i < methodArray.length; i++) {
method = methodArray[i];
String name = method.getName();//方法名称
int modifiers = method.getModifiers();//方法修饰符
Class returnType = method.getReturnType();//方法返回类型
Class[] parameterTypes = method.getParameterTypes();//方法参数类型数组(形参类型)
System.out.println("方法名:" + name + " 修饰符:" + Modifier.toString(modifiers) + " 返回值类型:" + returnType);
//方法参数(形参)
for (int j = 0; j < parameterTypes.length; j++) {
System.out.println("形参" + j + ":" + parameterTypes[j]);
}
System.out.println("");
}
}
运行结果:
方法名:showName 修饰符:public 返回值类型:void
形参0:class java.lang.String
方法名:show 修饰符:protected 返回值类型:void
方法名:showDefault 修饰符: 返回值类型:void
方法名:getAge 修饰符:private 返回值类型:class java.lang.String
形参0:int
5.Java 反射的一点场景使用
5.1 反射结合配置文件使用
User类
public class User {
public void show() {
System.out.println("show方法被调用...");
}
}
在 D 盘目录下新建一个 pro.txt 文件
className = demo.reflex.User //类名完整路径
methodName = show //方法名
示例代码:
public static void main(String args[]) throws Exception {
//获取Class
Class cls = Class.forName(getValue("className"));
//获取方法
Method method = cls.getMethod(getValue("methodName"));
Object object = cls.getConstructor().newInstance();//获取对象
//调用方法
method.invoke(object, null);
}
/***
* 根据key获取配置文件中 value
*
* @param key
* @return
* @throws IOException
*/
public static String getValue(String key) throws IOException {
Properties pro = new Properties();//获取配置文件的对象
File file = new File("D:\\pro.txt");//获取文件
FileReader in = new FileReader(file);//获取输入流
pro.load(in);//将流加载到配置文件对象中
in.close();
return pro.getProperty(key);//返回根据key获取的value值
}
输出结果:
show方法被调用...
5.2 通过反射越过泛型检查
还记得 method.invoke(Object obj, Object... args)
方法吗? 第二个参数表示:调用方法所需参数。这是一个 Object 对象,那么,嘿嘿嘿~~~
示例代码:
public static void main(String args[]) throws Exception {
ArrayList<String> list = new ArrayList<>();
list.add("AAA");
list.add("BBB");
/**
* 获取ArrayList的Class对象,反向的调用add()方法,添加数据
*/
Class listClass = list.getClass(); //获取Class对象
//获取add()方法
Method m = listClass.getMethod("add", Object.class);
//调用add()方法
m.invoke(list, false);
m.invoke(list, 100);
//遍历集合
for (Object obj : list) {
System.out.println(obj);
}
}
输出结果:
AAA
BBB
false
100
上述代码,ArrayList 指定类型 String 如果直接 list.add(100) 则会包类型错误,通过反射调用 m.invoke(obj, obj); 传入其他类型的对象,从而实现越过泛型的检查。当然这只是一个例子,实际编码中,还是需要严格按照规范去写代码
虽然,博文比较长,但是内容真的很简单,纯属无脑式的API调用,起码通过学习反射 API 的使用先了解反射的基础。这也是为什么文章开篇说反射简单了一逼,就是为了鼓舞大家有勇气面对这些知识点
由于本文都是API的简单使用,也都参考网络资料,就不一一附上连接了。