通过iava语言中的反射机制可以操作字节码文件。优点类似于黑客。(以读和修改字节码文件。 )
通过反射机制可以操作代码片段。(class文件)
java.lang.reflect.*
java.lang.Class 代表整个字节码,代表一个类型(代表整个类)
java.lang.reflect.Method 代表字节码中的方法字节码(代表类中的方法)
java.lang.reflect.Constructor 代表字节码中的构造方法字节码(代表类中的构造方法)
java.lang.reflect.Field 代表字节码中的属性字节码(代表类中的成员变量(静态变量、实例变量))
public class User{ -------------------> 这个整个的代码,我们称之为class
// Field
int no;
// Constructor
public User(){
}
public User(int no){
}
// Method
public void setNo(int no){
this.no = no;
}
public int getNo(){
return no;
}
}
public class Reflect01 {
public static void main(String[] args) {
/**
* Class.forName()
* 1. 静态方法
* 2. 方法的参数是一个字符串
* 3. 字符串需要的是一个完整的类名
* 4. 完整类目必须带一包名。 java.lang包也不能省略
*/
try {
Class c1 = Class.forName("java.lang.String");
Class c2 = Class.forName("java.util.Date");
Class c3 = Class.forName("java.lang.Integer");
Class c4 = Class.forName("java.lang.System");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
// java中任何一个对象都有一个方法: getClass()
String s = "abc";
Class x = s.getClass();
输出结果:
true
// 第三种方式,java语言中任何一种类型,包括基本数据类型,它都有.cLass属性。
Class z = String.class;
Class k = Date.class;
Class f = int.class;
Class e = double.class;
通过newInstance()方法调用无参构造方法。
// 通过反射机制,获取Class,通过Class来实例化对象
Class c = Class.forName("com.bean.User");
// newInstance()这个方法会调用user这个类的无参数构造方法,完成对象的创建。
// 重点: newInstance()调用的是无参构造,必须保证无参构造是存在的!
Object o = c.newInstance();
System.out.println(o);
执行结果:
com.bean.User@1b6d3586
验证反射机制的灵活性:
java代码写一遍,再不改变java源代码的基础之上,可以做到不同对象的实例化。非常之灵活。
classInfo.properties文件:(符合OCP开闭原则: 对扩展开放,对修改关闭)
代码:
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 通过IO流读取classInfo.properties文件
FileReader reader = new FileReader("src/classInfo.properties");
// 创建属性类对象 Map
Properties pro = new Properties(); // key value都是String
// 加载
pro.load(reader);
// 关闭流
reader.close();
// 通过 key 获取value
String className = pro.getProperty("className");
Class o = Class.forName(className);
System.out.println(o);
}
后期所接触的都是高级框架,包括工作也是,这些高级框架底层实现原理都是采用的反射机制!反射机制有利于理解框架底层的源代码!
public class ReflectTest04 {
public static void main(String[] args) {
try {
Class.forName("com.reflect.MyClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class MyClass{
// 静态代码块在类加载时执行,并且只执行一次
static{
System.out.println(" MyClass类的静态代码块执行了!! ");
}
}
以上代码运行结果:
MyClass类的静态代码块执行了!!
Process finished with exit code 0
解释; Class.forName()这个方法的执行会导致: 类加载。而类加载会导致静态代码块的执行。
public static void main(String[] args) throws FileNotFoundException {
// 这种方式的路径缺点: 移植性差,在IDEA中默认的当前路径是project的根。
// 这个代码假设离开了IDEA,换到了其它位置,可能当前路径就不是project的根了,这时这个路径就无效了。
//FileReader reader = new FileReader("chapter25/classInfo.properties");
// 接下来说一种比较通用的一种路径。即使代码换位置了,这样编写仍然是通用的。
// 注意:使用以下通用方式的前提是:这个文件必须在类路径下。
// 什么路径? 方式在src下的都是类路径下. [记住它]
// src是类的根路径 都是以src路径作为起点
/**
* Thread.currentThread()当前线程对象
* getContextClassLoader() 是线程对象的方法,可以获得当前线程的类加载器对象
* getResource("") 【获取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。
*/
// 拿到文件的绝对路径; 以下代码的方式是通用的;
String path = Thread.currentThread().getContextClassLoader().getResource("classInfo.properties").getPath();
System.out.println(path);
}
怎么获取一个文件的绝对路径。以上讲解的这种方式是通用的。但前提是:文件需要在类路径下。才能用这种方式。
note: 这种方式的文件扩展名必须是properties
/**
* @Auther liu xiang zheng
* @Date 2022/11/4
* @Description 资源绑定器
* java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容。
* 使用以下这种方式的时候,属性配置文件xxx. properties必须放到类路径下。
**/
public class ResourceBundleTest {
public static void main(String[] args) {
// src是类的根路径 都是以src路径作为起点
// 资源绑定器,只能绑定xx.properties文件。并且这个文件必须在类路径下。文件扩展名也必须是properties
// 并且在写路径的时候,路径后面的扩展名不能写。
ResourceBundle bundle = ResourceBundle.getBundle("classInfo");
String className = bundle.getString("className");
System.out.println(className);
}
}
public class ReflectTest05 {
public static void main(String[] args) throws ClassNotFoundException {
// 获取整个类
Class studentClass = Class.forName("com.bean.Student");
String className = studentClass.getName();
System.out.println("完整类名: " + className);
String simpleName = studentClass.getSimpleName();
System.out.println("简类名: " + simpleName);
// 获取类中所有的Field
Field[] fields = studentClass.getFields();
System.out.println(fields.length);
Field f = fields[0];
// 取出这个Field它的名字
String name = f.getName();
System.out.println(name);
// 获取所有的Field
Field[] fs = studentClass.getDeclaredFields();
System.out.println(fs.length);
// 遍历
for (int i = 0; i < fs.length; i++) {
// 获取属性的修饰符列表
int modifiers = fs[i].getModifiers(); // 返回的修饰符是一个数字,每个数字是修饰符的代号。
System.out.println("属性符的修饰符列表是: " + modifiers);
// 可以将这个"代号"转换成字符串吗?
String s = Modifier.toString(modifiers);
System.out.println("该代号对应的字符串是: " + s);
// 获取属性的类型
Class type = fs[i].getType();
System.out.println("属性的类型是: " + type);
Field g = fs[i];
System.out.println(g.getName());
}
}
}
Student类:
public class Student {
// Field翻译为字段,其实就是属性或者成员
public int no ;
protected int age ;
private String name;
boolean sex;
public static final double MATH_PI = 3.1415926;
}
反编译ReflectTest06类:
public class ReflectTest06 {
public static void main(String[] args) throws ClassNotFoundException {
// 创建这个是为了拼接字符串
StringBuilder s = new StringBuilder();
Class studentClass = Class.forName("com.bean.Student");
Field[] fields = studentClass.getDeclaredFields();
s.append("public class " + studentClass.getSimpleName() + " { \n");
for (Field field : fields){
s.append("\t");
s.append(Modifier.toString(field.getModifiers()));
s.append(" ");
s.append(field.getType().getSimpleName());
s.append(" ");
s.append(field.getName());
s.append(";\n");
}
s.append("}");
System.out.println(s);
}
}
运行结果:
public class Student {
public int no;
protected int age;
private String name;
boolean sex;
public static final double MATH_PI;
}
Process finished with exit code 0
代码的运行结果,让大家看一下反射机制的威力! (这个不需要掌握)
Student类:
public class Student {
// Field翻译为字段,其实就是属性或者成员
public int no ;
protected int age ;
private String name;
boolean sex;
public static final double MATH_PI = 3.1415926;
}
测试代码:
public class ReflectTest07 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
// 通过反射机制去修改访问一个对象的属性
Class c = Class.forName("com.bean.Student");
Object o = c.newInstance(); // o就是Student对象(底层调用无参数构造方法)
// 获取 no 属性(根据属性的名称来获取Field)
/**
* 要素1: obj对象
* 要素2: no属性
* 要素3: 222值
* 反射机制让和代码复杂了,但是为了一个灵活,这也是灵活的!
*/
Field no = c.getDeclaredField("no");
no.set(o,2222); // 给对象的no属性赋值222
// 读取属性的值
// 两个要素: 获取obj对象的no属性的值。
System.out.println(no.get(o));
}
}
note: 如果想要访问私有的属性。需要打破封装(这也是反射机制的缺点)
属性名.setAccessible(true);
代码:
/**
* @Auther liu xiang zheng
* @Date 2022/11/5
* @Description 可变长参数
* int... args 这就是可变长参数
* 语法是: 类型 ... (注意: 一定是3个点。)
* 1. 可变长度参数要求的参数个数是: 0~N
* 2. 可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只有一个
**/
public class ArgsTest {
public static void main(String[] args) {
m();
m(10);
m(10,20);
}
public static void m(int... args){
// args有length属性,说明args是一个数组!
System.out.println("m方法执行了!");
}
}
运行结果:
m方法执行了!
m方法执行了!
m方法执行了!
Process finished with exit code 0
public class ReflectTest10 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
// 通过反射机制调用一个对象的方法应该怎么做?
Class c = Class.forName("com.bean.UserService");
// 创建对象
Object obj = c.newInstance();
// 获取Method 参数: 1.方法名 2.方法中的参数 3.方法中的参数
Method ms = c.getDeclaredMethod("login",String.class,String.class);
// 调用方法
// 调用方法有几个要素? 也需要4要素
// ms 方法 obj 对象 "admin" "123"是实参 invoke 返回值
Object invoke = ms.invoke(obj, "admin", "123");
System.out.println(invoke);
}
}
反射机制,让代码很具有通用性,可变化的内容都是写到配置文件当中,将来修改配置文件之后,创建的对象不一样了,调用的方法也不同了,但是java代码不需要任何改动,这就是反射机制的魅力。