深入理解Java类型信息(Class对象)与反射机制
Java基础之—反射
Java类型信息(Class对象)与反射机制
浅谈反射机制
1. 什么是反射
JAVA
反射机制是在运行状态中,对于任意一个类,只要给定类的名字和所在的Package
就能够得到这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java
语言的反射机制。
通过反射可以把java
类中的各种成分映射成一个个的Java
对象(现在不理解没关系 往下看),也就是我们可以通过反射拿到对应这个类的成员变量,方法,构造函数,他们都是可以通过类对象得到的对象,再通过这些对象进行操作
java
的反射是以利于Class
类和java.lang.reflect
这个类库中的类来实现的;
java.lang.reflect
先来看java.lang.reflect
这个类库中的类:
Field类: 表示类中的成员变量
Method: 表示类中的方法
Constructor: 表示类的构造方法
Array: 提供了动态创建数组和访问数组元素的静态方法
Class
而对于Class
,Class
是用来表示运行时类型信息的对应类
类加载子系统
如图所示,字节码被加载到方法区(只是加载了这个类的信息,和实例对象都还没有关系,那是在初始化才进行的),构成了instanceKlass
的数据结构,在java
的堆内存会产生一个对应的_java_mirror
的对象(类对象),持有instanceKlass
的内存地址(instanceKlass
也持有_java_mirror
对象的内存地址)
如果以后创建了实例对象,实例对象的对象头指向类对象,再通过类对象到元空间找到.class
这里的类对象就是我们要关注的Class
对象,一个类只有一个Class
对象,Class
对象存储了一个类的所有信息,比如所有方法,所有字段等等
通过new
创建实例和反射创建实例,都绕不开Class
对象
2. 怎么获得Class
对象
Class
类只有一个私有的构造函数,所以只有JVM
能创建Class
实例,为了获得Class
类的实例,有以下方法
① 通过对象使用getClass()
方法
class Student{
.....
}
public class Fanshe {
public static void main(String[] args) {
//第一种方式获取Class对象
Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
Class stuClass = stu1.getClass();//获取Class对象
System.out.println(stuClass.getName());
}
}
但是我认为这个方法并没有什么用,我通过反射技术是想得到Class
类对象再通过类对象创建实例对象,从而解决松耦合等等问题(如Spring
),但这个有了实例对象再获得类对象就没啥用了
② 通过.class
任何数据类型(包括基本数据类型)都有一个“静态”的class
属性
package fanshe;
/**
* 获取Class对象的三种方式
* 1 Object ——> getClass();
* 2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
* 3 通过Class类的静态方法:forName(String className)(常用)
*
*/
public class Fanshe {
public static void main(String[] args) {
//第二种方式获取Class对象
Class stuClass2 = Student.class;
System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
通过字面量的方法获取Class
对象的引用不会自动初始化该类。更加有趣的是字面常量的获取Class
对象引用方式不仅可以应用于普通的类,也可以应用用接口,数组以及基本数据类型,这点在反射技术应用传递参数时很有帮助
基本数据类型还有对应的基本包装类型,其包装类型有一个标准字段TYPE
,而这个TYPE
就是一个引用,指向基本数据类型的Class
对象
boolean.class = Boolean.TYPE;
char.class = Character.TYPE;
byte.class = Byte.TYPE;
short.class = Short.TYPE;
int.class = Integer.TYPE;
long.class = Long.TYPE;
float.class = Float.TYPE;
double.class = Double.TYPE;
void.class = Void.TYPE;
③ 通过Class
类的静态方法:forName(String className)
(常用)
package fanshe;
/**
* 获取Class对象的三种方式
* 1 Object ——> getClass();
* 2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
* 3 通过Class类的静态方法:forName(String className)(常用)
*
*/
public class Fanshe {
public static void main(String[] args) {
//第三种方式获取Class对象
try {
Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
注意调用forName
方法时需要捕获一个名称为ClassNotFoundException
的异常,因为forName
方法在编译器是无法检测到其传递的字符串对应的类是否存在的,只能在程序运行时进行检查,如果不存在就会抛出ClassNotFoundException
异常。
我们使用第三种方法就可以通过配置文件来创建对象了(如Spring
里面通过配置xml文件)
3. 通过反射获取构造方法并使用
3.1 获取所有公有构造方法
public static void main(String[] args) throws Exception {
//1.加载Class对象
Class clazz = Class.forName("fanshe.Student");
//2.获取所有公有构造方法
System.out.println("**********************所有公有构造方法*********************************");
Constructor[] conArray = clazz.getConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
}
3.2 获取所有的构造方法(包括:私有、受保护、默认)
conArray = clazz.getDeclaredConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
3.3 返回指定参数类型、具有public
访问权限的构造函数对象
Constructor cs1 =clazz.getConstructor(String.class)//传入的是构造器的参数的类类型
3.4 返回指定参数类型、非公有的构造函数对象
Constructor cs1 =clazz.getDeclaredConstructor(String.class)//传入的是构造器的参数的类类型
3.5 调用构造方法
Object obj = con.newInstance();
Student stu = (Student)obj;
newInstance()
底层就是调用无参构造对象的newInstance()
所以,本质上Class对象要想创建实例,其实都是通过构造器对象。如果没有空参构造对象,就无法使用clazz.newInstance()
,必须要获取其他有参的构造对象然后调用构造对象的
newInstance()
4. 通过反射调用方法
package fanshe.method;
import java.lang.reflect.Method;
/*
* 获取成员方法并调用:
*
* 1.批量的:
* public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
* public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
* 2.获取单个的:
* public Method getMethod(String name,Class<?>... parameterTypes):
* 参数:
* name : 方法名;
* Class ... : 形参的Class类型对象
* public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
*
* 调用方法:
* Method --> public Object invoke(Object obj,Object... args):
* 参数说明:
* obj : 要调用方法的对象;
* args:调用方式时所传递的实参;
):
*/
public class MethodClass {
public static void main(String[] args) throws Exception {
//1.获取Class对象
Class stuClass = Class.forName("fanshe.method.Student");
//2.获取所有公有方法
System.out.println("***************获取所有的”公有“方法*******************");
stuClass.getMethods();
Method[] methodArray = stuClass.getMethods();
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("***************获取所有的方法,包括私有的*******************");
methodArray = stuClass.getDeclaredMethods();
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("***************获取公有的show1()方法*******************");
Method m = stuClass.getMethod("show1", String.class);
System.out.println(m);
//实例化一个Student对象
Object obj = stuClass.getConstructor().newInstance();
m.invoke(obj, "刘德华");
System.out.println("***************获取私有的show4()方法******************");
m = stuClass.getDeclaredMethod("show4", int.class);
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
System.out.println("返回值:" + result);
}
}
5. 获取成员变量并调用
package fanshe.field;
import java.lang.reflect.Field;
/*
* 获取成员变量并调用:
*
* 1.批量的
* 1).Field[] getFields():获取所有的"公有字段"
* 2).Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;
* 2.获取单个的:
* 1).public Field getField(String fieldName):获取某个"公有的"字段;
* 2).public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
*
* 设置字段的值:
* Field --> public void set(Object obj,Object value):
* 参数说明:
* 1.obj:要设置的字段所在的对象;
* 2.value:要为字段设置的值;
*
*/
public class Fields {
public static void main(String[] args) throws Exception {
//1.获取Class对象
Class stuClass = Class.forName("fanshe.field.Student");
//2.获取字段
System.out.println("************获取所有公有的字段********************");
Field[] fieldArray = stuClass.getFields();
for(Field f : fieldArray){
System.out.println(f);
}
System.out.println("************获取所有的字段(包括私有、受保护、默认的)********************");
fieldArray = stuClass.getDeclaredFields();
for(Field f : fieldArray){
System.out.println(f);
}
System.out.println("*************获取公有字段**并调用***********************************");
Field f = stuClass.getField("name");
System.out.println(f);
//获取一个对象
Object obj = stuClass.getConstructor().newInstance();//产生Student对象--》Student stu = new Student();
//为字段设置值
f.set(obj, "刘德华");//为Student对象中的name属性赋值--》stu.name = "刘德华"
//验证
Student stu = (Student)obj;
System.out.println("验证姓名:" + stu.name);
System.out.println("**************获取私有字段****并调用********************************");
f = stuClass.getDeclaredField("phoneNum");
System.out.println(f);
f.setAccessible(true);//暴力反射,解除私有限定
f.set(obj, "18888889999");
System.out.println("验证电话:" + stu);
}
}
6. 反射的使用:通过反射运行配置文件内容
Student类
public class Student {
public void show(){
System.out.println("is show()");
}
}
配置文件以txt文件为例子(pro.txt):
className = cn.fanshe.Student
methodName = show
测试类
/*
* 我们利用反射和配置文件,可以使:应用程序更新时,对源码无需进行任何修改
* 我们只需要将新类发送给客户端,并修改配置文件即可
*/
public class Demo {
public static void main(String[] args) throws Exception {
//通过反射获取Class对象
Class stuClass = Class.forName(getValue("className"));//"cn.fanshe.Student"
//2获取show()方法
Method m = stuClass.getMethod(getValue("methodName"));//show
//3.调用show()方法
m.invoke(stuClass.getConstructor().newInstance());
}
//此方法接收一个key,在配置文件中获取相应的value
public static String getValue(String key) throws IOException{
Properties pro = new Properties();//获取配置文件的对象
FileReader in = new FileReader("pro.txt");//获取输入流
pro.load(in);//将流加载到配置文件对象中
in.close();
return pro.getProperty(key);//返回根据key获取的value值
}
}
当我们升级这个系统时,不要Student
类,而需要新写一个Student2
的类时,这时只需要更改pro.txt的文件内容就可以了。代码就一点不用改动,(开闭原则)
7. 反射的使用:通过反射越过泛型检查
import java.lang.reflect.Method;
import java.util.ArrayList;
/*
* 通过反射越过泛型检查
*
* 例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值?
*/
public class Demo {
public static void main(String[] args) throws Exception{
ArrayList<String> strList = new ArrayList<>();
strList.add("aaa");
strList.add("bbb");
// strList.add(100);
//获取ArrayList的Class对象,反向的调用add()方法,添加数据
Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象
//获取add()方法
Method m = listClass.getMethod("add", Object.class);
//调用add()方法
m.invoke(strList, 100);
//遍历集合
for(Object obj : strList){
System.out.println(obj);
}
}
}