Java学习笔记-反射机制
通过反射机制可以操作 .class 字节码文件
反射机制,让代码具有通用性,可变化的内容都是写到配置文件当中
将来只需要修改配置文件,创建的对象不一样,调用的方法也不一样
但是java代码不需要做任何的改动
反射机制相关类:
java.lang.Class:代表字节码文件
java.lang.reflect.*;
java.lang.reflect.Method : 代表字节码中方法的字节码
java.lang.reflect.Constructor:代表构造方法的字节码
java.lang.reflect.Field:代表属性字节码
获取Class的三种方式
- Class c = Class.forName("完整类名");
- Class c = 对象.getClass();
- Class c = 类名.class;
重要方法:
- public T newInstance() :通过Class对象调用,实例化对象
示例方法:
public class ReflectTest01 {
public static void main(String[] args) throws Exception {
// Class.forName()
//1. 静态方法
//2. 方法的参数是一个字符串,字符串需要的是一个完整类名
Class c1 = Class.forName("java.lang.String");
Class c2 = Class.forName("java.util.Date");
//Object中的方法: getClass()
String s = "abc";
Class x = s.getClass();
System.out.println(c1 == x);
// 第三种方式:静态属性
Class z = String.class;
Class k = Date.class;
//重要方法:public T newInstance()
//功能:这个方法对应类的无参数构造方法,完成对象的创建
Object obj = c1.newInstance();
System.out.println(obj);
}
}
如果只想让一个类的”静态代码块“执行
public class ReflectTest04 {
public static void main(String[] args) {
try{
//执行的时候,类会被加载到方法区中,而类加载时静态代码块会执行
//通过这种方式,可以只让静态代码块执行
Class.forName("Reflect.User");
} catch (ClassNotFoundException e){
e.printStackTrace();
}
}
}
class User{
static {
System.out.println("静态代码块执行");
}
}
文件路径问题
相对路径:不同编译器的相对路径不同
绝对路径:不同电脑可能存放的也不同,不同的系统的绝对路径也不同
比较通用的方式:即使代码换了位置也能找到
- 使用前提:这个文件必须在类路径下
- 类路径:D:/biancheng/java/file/chapter19/out/production/chapter23/Reflect/CreatObject/properties
- 可以看到类路径是out/production目录下的,编译器中的"src"代指这个目录
- 在编译器中新建的文件是保存在这个目录下的,但是.java源文件不是保存在个目录下的
示例程序
public class AboutPath {
public static void main(String[] args) {
// Thread.currentThread()当前线程对象
// getContextClassLoader() 是线程对象的方法,可以获取到当前对象的类加载器
// getResource() 类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源
//获取一个文件的绝对目录,该文件必须放在类路径下
String path = Thread.currentThread().getContextClassLoader()
.getResource("Reflect//CreatObject//properties").getPath();
System.out.println(path); //path是该文件的绝对路径
//直接以一个输入流的形式返回
InputStream in = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("Reflect/CreatObject/properties")
}
}
通过读取配置文件来创建对象
通过上述方式读取文件:
示例程序:
//Reflect/CreatObject/properties文件
className=Reflect.ReflectTest01
public class CreateObj {
public static void main(String[] args) throws Exception {
//升级:以前直接写路径的方式都需要修改成这样的方式获取路径,提高程序的可移植性
// String path = Thread.currentThread().getContextClassLoader().getResource("Reflect//CreatObject//properties").getPath();
// FileReader fr = new FileReader(path);
InputStream fr = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("Reflect//CreatObject//properties");
//创建属性类对象Map
Properties pro = new Properties();
//加载
pro.load(fr);
//关闭流
fr.close();
//通过key获取value
String className = pro.getProperty("className");
//通过反射机制实例化对象
Class c = Class.forName(className);
Object obj = c.newInstance();
System.out.println(obj);
}
}
通过资源绑定器读取.properties配置文件
- java.util包下提供了一个资源绑定器,便于获得属性配置文件中的内容
- 使用以下这种方式的时候,属性配置文件xxx.properties必须放到类路径下
- 只能绑定在xxx.properties文件
- 并且在写路径的时候,路径后面的扩展名不能写
示例程序:
//Reflect/classinfo.properties配置文件
className=Reflect.ReflectTest01
import java.util.ResourceBundle;
public class ResourceBundleTest {
public static void main(String[] args) throws Exception {
ResourceBundle bundle = ResourceBundle.getBundle("Reflect//classinfo");
String className = bundle.getString("className");
Class c = Class.forName(className);
Object obj = c.newInstance();
System.out.println(obj);
}
}
总结:
- 通过此方式在任何环境下都能读取文件,只要此文件在类目录下
- 只需要修改配置文件就能创建不同的类,不需要修改任何java源代码
- 这样编写的程序很灵活,符合OCP原则
(重点)通过反射机制读取/设置类的属性
常用方法:
- public Class<?> getType() 获取数据类型
- (Class.java)public String getSimpleName() 返回数据类型的名字
- public int getModifiers() 获取权限标识符,返回int类型,每一个标识符对应一个数字
- (MModifier.java)public static String toString(int mod)
示例程序 -读取属性:
package Reflect;
public class Student {
private String name;
int age;
protected double grade;
public String id;
public static final double PI = 3.1015;
public Student() {
}
public Student(String name, int age, double grade, String id) {
this.name = name;
this.age = age;
this.grade = grade;
this.id = id;
}
public void show(){
System.out.println("无参数show方法执行");
}
public void show(int x,String s){
System.out.println("有参数show方法执行");
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", grade=" + grade +
", id='" + id + '\'' +
'}';
}
}
public class ReflectTest05 {
public static void main(String[] args) throws ClassNotFoundException {
//获取整个类(完整类名)
Class c = Class.forName("Reflect.Student");
String name = c.getName(); //获取完整类名
String simpleName = c.getSimpleName();
//获取类中所有的public修饰的Field(属性)
Field[] fields = c.getFields();
System.out.println(fields.length); //1
System.out.println(fields[0]); //public java.lang.String Student.id
//=======================================
Field[] fs = c.getDeclaredFields();
for(Field f : fs){
Class t = f.getType();
System.out.print(t.getSimpleName() + " ");
System.out.print(f.getModifiers() + " ");
System.out.print(Modifier.toString(f.getModifiers()) + " ");
System.out.println(f.getName());
}
}
}
示例程序 -反编译Field
public class ReflectTest06 {
public static void main(String[] args) throws ClassNotFoundException {
//拼接字符串
StringBuilder s = new StringBuilder();
Class c = Class.forName("Reflect.Student");
s.append(Modifier.toString(c.getModifiers())
+ " class " + c.getSimpleName() + " {\n");
Field[] fs = c.getDeclaredFields();
for(Field f : fs){
s.append("\t");
//获取权限
s.append(Modifier.toString(f.getModifiers()));
s.append(" ");
//获取类型
s.append(f.getType().getSimpleName());
s.append(" ");
//获取名字
s.append(f.getName());
s.append(";\n");
}
s.append("}");
System.out.println(s);
}
}
示例程序 -修改属性
- Field getDeclaredField(String name) 根据属性名获取属性,private也可以获取
- void setAccessible(boolean flag) 打破封装,使得在外部也可以修改private修饰的属性值
- void set(Object obj, Object value) 设置属性值,三要素:对象、属性、值
- Object get(Object obj) 获取属性的值,两要素:对象、属性
public class ReflectTest07 {
public static void main(String[] args) throws Exception{
Class c = Class.forName("Reflect.Student");
Object obj = c.newInstance();
//获取属性 -根据名称
Field f = c.getDeclaredField("name");
//打破封装,
f.setAccessible(true);
//对象:obj 属性:f 值:"zhangsan"
f.set(obj,"zhangsan");
System.out.println(f.get(obj));
}
}
通过反射机制调用方法
可变长度参数
-
语法:类型... args
-
- 可变长度参数要求的参数个数:0~N
-
- 可变长度参数必须在参数列表的最后一个位置上,且只能有一个
-
- 可变长度参数可以当作一个数组来看待
public class ArgsTest {
public static void main(String[] args) {
m(2,"ad","ap");
String[] s = new String[]{"aa","bb","cc"};
m(3,s);
}
public static void m(int a, String...args){
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}
调用普通方法
- Method getDeclaredMethod(String name, Class<?>... parameterTypes) 获取目标方法的对象, 参数:方法名,方法参数列表
- public Object invoke(Object obj, Object... args) 调用目标方法,四要素:方法对象、对象、实参、返回值(执行方法的返回值)
示例程序
public class ReflectTest08 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("Reflect.Student");
Object obj = c.newInstance();
//方法名:show
Method m = c.getDeclaredMethod("show",int.class, String.class);
//方法:m 对象:obj 实参:args... 返回值:retValue
Object retValue = m.invoke(obj,12,"34");
System.out.println(retValue);
}
}
调用有参数构造方法
- Constructor
getDeclaredConstructor(Class<?>... parameterTypes) 获取构造方法的构造器 - T newInstance(Object ... initargs) 调用构造方法,返回实例化后的对象
public class ReflectTest09 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("Reflect.Student");
//调用无参数构造方法(要保证无参数构造方法存在)
Object obj1 = c.newInstance();
//调用无参数构造方法
//第一步:获取此有参数构造方法
Constructor con = c.getDeclaredConstructor(String.class, int.class, double.class, String.class);
//第二步:调用构造方法new对象
Object obj2 = con.newInstance("zhangsan",12,34,"1111");
System.out.println(obj2);
}
}
获取类的父类或者接口
示例程序:
public class ReflectTest10 {
public static void main(String[] args) throws ClassNotFoundException {
Class c = Class.forName("java.lang.String");
//获取String的父类
Class superClass = c.getSuperclass();
System.out.println(superClass.getName());
//获取String类实现的接口
Class[] is = c.getInterfaces();
for(Class i : is){
System.out.println(i.getName());
}
}
}