反射机制是Java程序开发语言的特征之一,它允许运行中的Java程序对自身进行检查,并可以直接操作程序的内部属性。参考动力节点杜老师的视频,总结了反射机制的基本知识点。
1、反射机制有什么用?
可以操作字节码文件即.class文件(读和修改)。
2、反射机制相关的类在那个包下?
java.lang.reflect.*
3、反射机制相关的重要的类:
java.lang.Class 代表了整个字节码文件 代表整个类
java.lang.reflect.Constructor 代表了字节码中的构造方法字节码
java.lang.reflect.Method 代表了字节码文件中的方法字节码
java.lang.reflect.Field 代表了字节码文件中的属性字节码
4、要操作字节码文件,首先要获取字节码文件,即获取java.lang.Class ,有三种方式,这三种方式均会导致类加载去加载这个要反射的类:
第一种方式:Class.forName();
public class ReflectTest{
public static void main(String[] args){
/*
Class.forName()
1、静态方法
2、参数是字符串类型,需要的是完整的类名,即必须带包名
3、这个方法会抛出ClassNotFoundException异常
*/
Class c1 = Class.forName("java.lang.String");//c1就代表了String类
Class c2 = Class.forName("java.util.Date"); //c2就代表了Date类
}catch(ClassNotFoundException e){
e.printStackTrace();
}
}
第二种方式:Object类有一个getClass()方法,即每一个对象都有这个方法。
String s = "abc";
Class x = s.getClass();
System.out.println(x == c1);
//输出true,即一个程序中只会加载一个String.class,因此两种方式得到的结果内存地址相同。
第三种方式:Java中任何一种类型,包括基本数据类型,都有.class属性
Class y1 = String.class;
Class y2 = Date.class;
Class y3 = int.Class;
System.out.println(y1 == c1);//对应的类内存地址同样相同
5、获取到Class后,我们能干什么呢?首先我们可以以此来实例化对象:
首先,这里有一个User类
package com.exercise.test;
public class User{
public User(){}
}
要创建一个User对象,应该怎么做呢?
public class ReflectTest02{
public static void main(String[] args){
Class u1 = Class.forName("com.exercise.test.User");
//newInstance()这个方法会调用User类的无参数构造方法,所以前提是User类有无参构造方法存在
Object obj = u1.newInstance();
}
}
这个时候你可能会问,费了这么大功夫居然只是为了创建对象?我直接new User()不行吗?这个时候便要说明一下反射机制到底灵活在哪里?
首先,我在模块src文件夹下创建一个属性配置文件classinfo.properties ,在文件中写上:
className=com.exercise.test.User
接下来,通过IO流读取这个文件,再交给反射机制:
public class ReflectTest02{
public static void main(String[] args) throws Exception{
/*
//在src根目录下获取一个文件的绝对路径
String path = Thread.currentThread().getContextClassLoader().
getResource("classinfo.properties").getPath();
//IO流读取
Reader reader = new FileReader(path);//括号里面可以写绝对路径
*/
//或者直接以流的形式返回
InputStream reader = Thread.currentThread().getContextClassLoader().
getResourceAsStraem("classinfo.properties");
//创建属性类对象
Properties pro = new Properties();
//加载
pro.load(reader);
//关闭流
reader.close
//通过key获取value
String className = pro.getProperties("className");
//反射机制实例化对象
Class u1 = Class.forName("className");
//newInstance()这个方法会调用User类的无参数构造方法,所以前提是User类有无参构造方法存在
Object obj = u1.newInstance();
}
}
到了这一步相信你已经看出反射机制的灵活性了,是的,只要我修改配置文件中的类名(比如把com.exercise.test.User改为java.lang.String),就可以在不动代码的基础上,实现不同类的对象的创建。
可能你会觉得这个这种方法好是好,但是代码太多了,而且记不住,没关系,还有更简便的,java.util包下提供了一个资源绑定器,便于获取属性配置文件的内容,当然也只限于在类路径下,也就是src路径下,并且只能是属性配置文件,即xxx.properties。
//注意路径的文件扩展名不能写!!
ResourceBundle bundle = ResourceBundle.getBundle("classinfo");
String className = bundle.getString("className");
6、操作字节码文件只能实例化对象吗?当然不是,接下来说明如何获取类中的属性:
这里有一个Student类,分别以四种修饰符修饰4个属性:
package com.exercise.test;
public class Student{
public int no;
private String name;
protected int age;
boolean sex;
public Student(){}
public Student(int no){
this.no = no;
}
public Student(int no,String name){
this.no = no;
this.name = name;
}
}
使用反射机制获取Student类的属性:
public class ReflectTest03 throws Exception{
public static void main(String[] args){
Class c1 = Class.forName("com.exercise.test.Student");
String className = c1.getName();
System.out.println("完整类名:"+className); //获取完整类名
//com.exercise.test.Student
String simpleName = c1.getSimpleName();
System.out.println("简类名:"+className); //获取简单类名 Student
//获取类中所有public修饰的属性
Field[] fields = c1.getFields();
System.out.println(filelds.length); //数组中只有一个元素
//取出这个元素,得到它的名字
System.out.println(filed[0].getName()); //得到 no
//获取类中的所有属性
Filed[] fs = c1.getDeclaredFields();
System.out.println(fs.length); //数组中有4个元素
for(Field f : fs){
//获取属性的修饰符列表
int i = f.getModifiers(); //返回的是一个数字,是修饰符的代号
//将这个数字转换成字符串
String ms = Modifier.toString(i);
System.out.println(ms);
//获取属性的类型及类型名
Class fieldType = f.getType();
String typeName = fieldType.getName();
System.out.println(typeName.getName);
//获取属性名
System.out.println(f.getName); //输出no name age sex
}
}
}
那么如何访问对象属性呢?
public class ReflectTest04 throws Exception{
public static void main(String[] args){
Class c1 = Class.forName("com.exercise.test.Student")
Object obj = c1.newInstance();
//给no这个属性赋值,首先根据属性名拿到这个属性
Field noField = c1.getDeclaredField("no");
//调用set方法
noField.set(obj,1111); //给obj对象的no属性赋值1111
//获取属性的值
System.out.println(noField.get(obj)); //输出1111
}
}
但是要注意,这种方法只能访问public修饰的属性,要想访问其他属性,需要先打破封装。
Field nameField = c1.getDeclaredField("name");
//打破封装
nameField.setAccessible(true);
nameField.set(obj,"jack");
System.out.println(nameField.get(obj));
如何调用方法呢?
这里有一个UserService类,里面有一些方法
public class UserService{
public boolean login(String userName,String password){
if("admin".equals(userName) && "123".equals(password)){
return true;
}
return false;
}
public void logout(){
System.out.println("系统已安全退出");
}
}
现在我们来调用类中的方法:
public class ReflectTest05 throws Exception{
public static void main(String[] args){
Class c1 = Class.forName("com.exercise.test.UserService")
//获取对象,没有对象不能调方法
Object obj = c1.newInstance();
//获取Method,Java中区别一个方法靠的是方法名和参数列表,所以
Method loginMethod = c1.getDeclaredMethod(login,String.class,String.class);
//调用方法,需要方法、对象、参数、返回值
Object reValue = loginMethod.invoke(obj,"admin","123");
}
}
如何调用构造方法呢?
public class ReflectTest06 throws Exception{
public static void main(String[] args){
//获取User类
Class c1 = Class.forName("com.exercise.test.User")
//调用无参数构造方法
Object o1 = c1.newInstance();
//调用有参数构造方法
//首先要获取到这个构造方法
Constructor con = c1.getDeclaredConstructor(in.class,String.class);
//调用构造方法new对象
Object o2 = con.newInstance(111,"jack");
}
}