Java反射机制

目录:

      • 反射机制
        • 1.反射机制概述?
          • 1.1 反射机制有什么用?
        • 1.2 反射机制的相关类在哪个包下?
        • 1.3 反射机制的相关的类有哪些?
        • 2.获取class的三种方式
          • 2.1 第一种方式
          • 2.2 第二种方式
          • 2.3 第三种方式
        • 3.通过反射实例化对象
          • 3.1 反射机制具有灵活性
        • 4. 只让静态代码块执行可以只使用forName
        • 5. 获取文件绝对路径
        • 6. 资源绑定器
        • 7. Field
          • 7.1 获取Filed
          • 7.2 反编译Filed
        • 8. 通过反射机制访问对象属性(掌握)
        • 9. 可变长参数
        • 10. 反射机制调用方法(重点)

反射机制

1.反射机制概述?

1.1 反射机制有什么用?

通过iava语言中的反射机制可以操作字节码文件。优点类似于黑客。(以读和修改字节码文件。 )

通过反射机制可以操作代码片段。(class文件)

1.2 反射机制的相关类在哪个包下?

java.lang.reflect.*

1.3 反射机制的相关的类有哪些?

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;
    }
}

2.获取class的三种方式

2.1 第一种方式
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();
        }
    }
}
2.2 第二种方式
// java中任何一个对象都有一个方法: getClass()
  String s = "abc";
  Class x = s.getClass();

Java反射机制_第1张图片

输出结果:

true

字节码文件只有一个!
Java反射机制_第2张图片

2.3 第三种方式
// 第三种方式,java语言中任何一种类型,包括基本数据类型,它都有.cLass属性。
Class z = String.class;
Class k = Date.class;
Class f = int.class;
Class e = double.class;

3.通过反射实例化对象

通过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
3.1 反射机制具有灵活性

验证反射机制的灵活性:

​ java代码写一遍,再不改变java源代码的基础之上,可以做到不同对象的实例化。非常之灵活。

classInfo.properties文件:(符合OCP开闭原则: 对扩展开放,对修改关闭)
Java反射机制_第3张图片

代码:

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);
    }

运行结果:
Java反射机制_第4张图片

后期所接触的都是高级框架,包括工作也是,这些高级框架底层实现原理都是采用的反射机制!反射机制有利于理解框架底层的源代码!

4. 只让静态代码块执行可以只使用forName

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()这个方法的执行会导致: 类加载。而类加载会导致静态代码块的执行。

5. 获取文件绝对路径

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);
}

怎么获取一个文件的绝对路径。以上讲解的这种方式是通用的。但前提是:文件需要在类路径下。才能用这种方式。

6. 资源绑定器

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);
    }
}

7. Field

7.1 获取Filed
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());
        }
    }
}
7.2 反编译Filed

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

代码的运行结果,让大家看一下反射机制的威力! (这个不需要掌握)

8. 通过反射机制访问对象属性(掌握)

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);

9. 可变长参数

代码:

/**
 * @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

10. 反射机制调用方法(重点)

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代码不需要任何改动,这就是反射机制的魅力。

你可能感兴趣的:(Java基础知识,java,jvm,开发语言)