运行java代码要经历的三个阶段
反射,程序框架设计的灵魂,将类的各个组成部分封装成其他对象,这就是反射机制。
框架:半成品的软件,在框架的基础上进行开发,可以简化编码
反射的好处:
- 可以在程序运行的过程中,操作这些对象
- 可以解耦,提高程序的可拓展性
在运行状态中:
- 对于任意一个类,都能够知道这个类的所有属性和方法
- 对于任意一个对象,都能够调用它的任意属性和方法
与反射机制相关的类
- Java.lang.class :代表整个类编译得到的class文件(字节码)
- Java.lang.reflect.Method :代表字节码中的方法字节码
- Java.lang.reflect.Constructor :代表字节码中的构造器字节码
- Java.lang.reflect.Field :代表字节码中的属性字节码
要操作一个字节码,首先要得到这个类的字节码,下面是三种得到字节码的方式
static Class.forName(String className) 这里的className需要时类的全路径,这是一个静态方法
public class reflect1 { public static void main(String[] args) throws ClassNotFoundException { //第一种方式 Class c1=Class.forName("java.lang.String"); System.out.println(c1); } }
java中的任意一个对象.getClass() ,这个方法继承自Object
public class reflect1 { public static void main(String[] args) throws ClassNotFoundException { //第一种方式 Class c1=Class.forName("java.lang.String"); System.out.println(c1); //第二种方式 String s="hello"; Class c2=s.getClass(); System.out.println(c2); } }
java语言中的任意一种类型,包括基本数据类型,都有.class属性。
public class reflect1 { public static void main(String[] args) throws ClassNotFoundException { //第一种方式 Class c1=Class.forName("java.lang.String"); System.out.println(c1); //第二种方式 String s="hello"; Class c2=s.getClass(); System.out.println(c2); //第三种方式 Class c3=String.class; System.out.println(c3); } }
JVM在加载一个类的时候,会把这个类的.class字节码文件放在方法区中,栈中放入main方法进行压栈,堆中new出对象c,但是无论使用什么方法获取字节码,一样的class字节码文件只会放一份在方法区中,得到的c变量都是指向方法区中的同一份.class文件。
使用反射机制来实例化对象
通过Class的newInstance() 方法可以实例化对象
但是newInstance()方法内部实际上调用了无参构造方法,必须要保证无参构造的存在才可以
private static void test2() throws InstantiationException, IllegalAccessException { Object o=Student.class.newInstance(); System.out.println(o); }
反射机制的灵活性,在不修改源码的情况下,可以做到不同对象的实例化,符合OCP开闭原则:对拓展开放,对修改关闭.
- 下面的代码中首先利用FileReader读取配置文件,配置文件里面保存了键和值,值是类名,之后new了一个Properties对象,Properties对象的键和值都是String类型,是一种Map数据类型,之后将文件流对象传入Properties对象,再根据键得到值,然后利用反射机制传入类名新建一个对象,实现了对象的灵活创建。
private static void test3() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException { FileReader fr=new FileReader("Student.properties"); Properties pro=new Properties(); pro.load(fr); fr.close(); String forName=pro.getProperty("Student"); Class C=Class.forName(forName); Object o=C.newInstance(); System.out.println(o); }
如果只想要让类里面的某些代码被执行,那么可以使用静态代码和Class.forName,像下面的代码那样,每次类加载器加载一个类的时候,就会先执行静态代码块,使用Class.forName而不用创建类的实例化变量
public class reflect1 { static { System.out.println("静态代码执行!"); } }
private static void test4() { try { Class.forName("reflect1"); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } }
获取文件的类路径,可以通过类加载器获取,具体如下:
private static void test4() { String path=Thread.currentThread().getContextClassLoader().getResource("a.txt").getPath(); System.out.println(path); }
资源绑定器
用来获取属性配置文件中的内容
属性配置文件必须要放在类路径下(src下面)
必须是properties后缀
绑定某个属性配置文件的时候,只需要填写配置文件的名字,不需要写后缀,也不能写后缀
private static void test4() { ResourceBundle bundle=ResourceBundle.getBundle("Student"); String path=bundle.getString("Student"); System.out.println(path); }
获取并操作属性字节码
首先获取整个字节码
利用字节码的getFields可以获取到public修饰的所有属性
利用字节码的getDeclaredFields可以获取到所有属性,无论什么修饰符都可以获取到
遍历属性列表
通过属性.getName()可以获取到属性的名字
通过属性.getType()可以获取到属性的完整类型,getSimpleName是获取简化的类名
通过属性.getModifiers()可以获取到属性的修饰符id,通过Modifier.toString(id)就可以拿到完整的修饰符
import java.io.Serializable; public class Student implements Serializable { // 属性设置为private private String name; private int age; private static String room; //无参数构造 public Student() { } //有参数构造 public Student(String name, int age, String room) { this.name = name; this.age = age; this.room=room; } //getter方法 public String getName() { return name; } public int getAge() { return age; } public static String getRoom() { return room; } //setter方法 public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public static void setRoom(String room) { Student.room = room; } //重写equals方法 public boolean equals(Object obj){ if(obj instanceof Student) { //首先将得到的Object转为Student,否则使用不了Student特有的方法(多态的弊端) Student t = (Student) obj; return t.getAge() == this.getAge() && t.getName() == this.getName() && t.getRoom() == this.getRoom(); } return false; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
private static void test5() throws ClassNotFoundException { //获取整个类 Class studentClass=Student.class; //获取public修饰的属性,一个Filed包括private static String room; Field[] fileds=studentClass.getFields(); for (Field filed : fileds) { System.out.println(filed); } //获取所有的属性,一个Filed包括private static String room; fileds=studentClass.getDeclaredFields(); for (Field filed : fileds) { //获取属性的名字 System.out.println("属性的名字:"); System.out.println(filed.getName()); //获取属性的类型 Class filedTypeClass=filed.getType(); //getName是获取完整类型,getSimpleName是获取简化的类名 String filedType=filedTypeClass.getSimpleName(); System.out.println("属性的类型"); System.out.println(filedType); //获取属性的修饰符 int modify=filed.getModifiers(); String modifyName= Modifier.toString(modify); System.out.println("属性的修饰符"); System.out.println(modifyName); System.out.println("=================="); } }
利用反射机制访问一个对象的属性
要使用反射,当然要先获取一个类的字节码文件
利用获取到的字节码文件,再用newInstance方法创建一个对象(使用反射的类必须有无参构造)
利用字节码文件获取这个类的某个属性
利用这个属性给创建的对象的对应的属性赋值
在设置对象的属性值之前,需要在使用
set
方法之前,调用setAccessible(true)
方法来取消对字段的访问权限检查。否则, 会报IllegalAccessException
异常。这是因为name
字段被声明为private
,无法直接访问。但是使用反射机制绕过访问权限限制可能会导致安全问题。private static void test6() throws NoSuchFieldException, InstantiationException, IllegalAccessException { //获取整个类 Class studentClass=Student.class; Field filed=studentClass.getDeclaredField("name"); Object obj=studentClass.newInstance(); filed.setAccessible(true); // 取消对字段的访问权限检查 filed.set(obj,"zs"); System.out.println(obj); }
利用反射机制获取Method
获取整个类的字节码文件
通过字节码文件得到方法的字节码,并且遍历它
通过.getName()获取方法名字
通过.getReturnType().getSimpleName()获取返回值类型
通过.getModifiers()获取修饰符id,然后通过Modifier.toString(id)得到修饰符
通过.getParameterTypes()得到修饰符列表,遍历这个列表,通过.getSimpleName()得到参数名称
private static void test7() { Class studentClass=Student.class; Method[] methods=studentClass.getDeclaredMethods(); for (Method method : methods) { //获取方法名 System.out.println("方法名"); System.out.println(method.getName()); //获取方法的返回值类型 System.out.println("方法的返回值类型"); System.out.println(method.getReturnType().getSimpleName()); //获取修饰符列表 System.out.println("方法的修饰符列表"); System.out.println(Modifier.toString(method.getModifiers())); //方法的参数列表 System.out.println("方法的参数列表"); Class[] parametorType= method.getParameterTypes(); for (Class aClass : parametorType) { System.out.println(aClass.getSimpleName()); } System.out.println("========================"); } }
通过反射机制操纵Method
首先获取字节码
利用得到的字节码新建一个对象
利用得到的字节码获取一个指定的方法,在java里面,由于存在方法的重载,所以区分一个方法是由方法名+方法参数类型,而不仅仅是方法名,所以使用getDeclaredMethod方法需要一个方法名和若干参数的class
最后使用这个方法的invoke方法,传入要操纵的对象和实参
private static void test8() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { Class studentClass=Student.class; Object obj=studentClass.newInstance(); Method setName=studentClass.getDeclaredMethod("setName",String.class); Object returnVa=setName.invoke(obj,"zs"); System.out.println(obj); }
通过反射机制获取构造方法
获取整个类的字节码文件
通过字节码文件得到构造方法的字节码,并且遍历它
通过.getName()获取方法名字
通过.getModifiers()获取修饰符id,然后通过Modifier.toString(id)得到修饰符
通过.getParameterTypes()得到修饰符列表,遍历这个列表,通过.getSimpleName()得到参数名称
private static void test9() { Class studentClass=Student.class; Constructor[] constructors=studentClass.getDeclaredConstructors(); for (Constructor constructor : constructors) { //获取构造方法名 System.out.println("构造方法名"); System.out.println(constructor.getName()); //获取修饰符列表 System.out.println("构造方法的修饰符列表"); System.out.println(Modifier.toString(constructor.getModifiers())); //方法的参数列表 System.out.println("构造方法的参数列表"); Class[] parametorType= constructor.getParameterTypes(); for (Class aClass : parametorType) { System.out.println(aClass.getSimpleName()); } System.out.println("========================"); } }
使用反射机制的构造方法创建对象
直接利用字节码创建(已过时)
首先获取无参构造函数,然后调用无参构造函数
首先获取有参构造函数,然后调用有参构造函数
private static void test10() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { //使用反射创建对象 //1. 直接利用字节码创建 Class studentClass=Student.class; Object t1=studentClass.newInstance(); //2. 先利用字节码获取构造函数,然后再利用构造函数创建对象 //首先是无参构造 Constructor con1=studentClass.getDeclaredConstructor(); Object t2=con1.newInstance(); //最后是有参构造方法 Constructor con2=studentClass.getDeclaredConstructor(String.class,int.class,String.class); Object t3=con2.newInstance("zs",18,"h111"); System.out.println(t1); System.out.println(t2); System.out.println(t3); }
使用反射获取一个类的父类以及接口
private static void test11() { Class stringClass=String.class; //获取String的父类 Class superClass=stringClass.getSuperclass(); System.out.println("父类是"); System.out.println(superClass.getSimpleName()); //获取接口 Class[] interface1=stringClass.getInterfaces(); System.out.println("接口是"); for (Class aClass : interface1) { System.out.println(aClass.getSimpleName()); } }