【学习笔记】Java基础入门——第11章·反射机制

第11章 反射机制

11.1  反射机制

11.1.1 概述

通过java语言中的反射机制可以操作字节码文件。优点类似于黑客(可以读和修改字节码文件)。通过反射机制可以操作代码片段(class文件)。反射机制的相关类在java.lang.reflect.*;包下。

11.1.2 使用反射机制的好处

验证反射机制的灵活性。Java代码写一遍,再不改变Java源代码的基础之上,可以做到不同对象的实例化。非常之灵活(符合OCP开闭原则:对扩展开放,对修改关闭)。

public class ReflectTest03 {
    public static void main(String[] args) throws Exception{

        // 这种方式代码就写死了。只能创建一个User类型的对象
        //User user = new User();

        // 以下代码是灵活的,代码不需要改动,可以修改配置文件,配置文件修改之后,可以创建出不同的实例对象。
        // 通过IO流读取classinfo.properties文件
        FileReader reader = new FileReader("第12章_反射机制/src/reflect/classinfo.properties");
        // 创建属性类对象Map
        Properties pro = new Properties(); // key value都是String
        // 加载
        pro.load(reader);
        // 关闭流
        reader.close();

        // 通过key获取value
        String className = pro.getProperty("className");
        System.out.println(className);

        // 通过反射机制实例化对象
        Class c = Class.forName(className);
        Object obj = c.newInstance();
        System.out.println(obj);
    }
}

11.2  构造Class对象

  1. Class c = Class.forName("完整类名带包名");这个方法会导致类加载,类加载时,静态代码块执行,如果只是希望一个类的静态代码块执行,其他代码一律不执行,可以使用。
  2. Class c = 对象.getClass();
  3. Class c = 任何类型.class;
public class ReflectTest01 {
    public static void main(String[] args) {
        /*
        Class.forName()
            1、静态方法
            2、方法的参数是一个字符串。
            3、字符串需要的是一个完整类名。
            4、完整类名必须带有包名。java.lang包也不能省略。
         */
        Class c1 = null;
        Class c2 = null;
        try {
            c1 = Class.forName("java.lang.String"); // c1代表String.class文件(c1代表String类型)
            c2 = Class.forName("java.util.Date"); // c2代表Date类型
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // java中任何一个对象都有一个方法:getClass()
        String s = "abc";
        Class x = s.getClass(); // x代表String.class字节码文件,x代表String类型。
        System.out.println(c1 == x); // true(==判断的是对象的内存地址。)

        Date time = new Date();
        Class y = time.getClass();
        System.out.println(c2 == y); // true (c2和y两个变量中保存的内存地址都是一样的,都指向方法区中的字节码文件。)

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

        System.out.println(x == z); // true
    }
}

 

11.3  反射机制相关的重要类

  • java.lang.Class:代表整个字节码,代表一个类型,代表整个类。
  • java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法。
  • java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法
  • java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。

11.3.1 Field通过反射机制访问对象属性 

public class ReflectTest07 {
    public static void main(String[] args) throws Exception{
        Class studentClass = Class.forName("com.bjpowernode.java.bean.Student");
        Object obj = studentClass.newInstance(); // obj就是Student对象。(底层调用无参数构造方法)
        // 获取no属性(根据属性的名称来获取Field)
        Field noFiled = studentClass.getDeclaredField("no");
        // 给obj对象(Student对象)的no属性赋值
        /*
        虽然使用了反射机制,但是三要素还是缺一不可:
            要素1:obj对象
            要素2:no属性
            要素3:2222值
        注意:反射机制让代码复杂了,但是为了一个“灵活”,这也是值得的。
         */
        noFiled.set(obj, 22222); // 给obj对象的no属性赋值2222
        // 读取属性的值。两个要素:获取obj对象的no属性的值。
        System.out.println(noFiled.get(obj));

        // 可以访问私有的属性吗?
        Field nameField = studentClass.getDeclaredField("name");

        // 打破封装(反射机制的缺点:打破封装,可能会给不法分子留下机会),这样设置完之后,在外部也是可以访问private的。
        nameField.setAccessible(true);

        // 给name属性赋值
        nameField.set(obj, "jackson");
        // 获取name属性的值
        System.out.println(nameField.get(obj));
    }
}

11.3.2 Method

利用反射机制,获取类文件的信息:

public class ReflectTest08 {
    public static void main(String[] args) throws Exception{
        StringBuilder sb = new StringBuilder();
        // 获取类了
        Class userServiceClass = Class.forName("service.UserService");
        sb.append(Modifier.toString(userServiceClass.getModifiers()) + " class "+userServiceClass.getSimpleName()+" {\n");

        // 获取所有的Method(包括私有的!)
        Method[] methods = userServiceClass.getDeclaredMethods();

        // 遍历Method
        for(Method method : methods){
            // 获取修饰符列表
            String 访问权限 = Modifier.toString(method.getModifiers());//method.getModifiers()返回的是数字
            sb.append("\t").append(访问权限).append(" ");
            // 获取方法的返回值类型
            String 返回值 = method.getReturnType().getSimpleName();
            sb.append(返回值).append(" ");
            // 获取方法名
            String 方法名 = method.getName();
            sb.append(方法名).append("(");
            // 方法的修饰符列表(一个方法的参数可能会有多个。)
            Class[] parameterTypes = method.getParameterTypes();
            for(Class parameterType : parameterTypes){
                String 参数类型 = parameterType.getSimpleName();
                sb.append(参数类型);
                sb.append(",");
            }
            if (parameterTypes.length > 0){
                sb.deleteCharAt(sb.length()-1);
            }
            sb.append("){}\n");
        }
        sb.append("}");
        System.out.println(sb);
    }
}

反射机制,让代码很具有通用性,可变化的内容都是写到配置文件当中,将来修改配置文件之后,创建的对象不一样了,调用的方法也不同了,但是Java代码不需要做任何该懂。这就是反射机制的魅力,通过反射机制怎么调用一个对象的方法:

public class ReflectTest10 {
    public static void main(String[] args) throws Exception{
        Class userServiceClass = Class.forName("service.UserService");
        // 创建对象
        Object obj = userServiceClass.newInstance();
        // 获取Method
        Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
        Object retValue = loginMethod.invoke(obj, "admin","123123");
        System.out.println(retValue);
    }
}

11.3.3 Constructor

通过反射机制调用构造方法实例化Java对象:

public class ReflectTest12 {
    public static void main(String[] args) throws Exception{
        Class c = Class.forName("bean.Vip");
        // 调用无参数构造方法
        Object obj = c.newInstance();
        // 调用有参数的构造方法
        // 第一步:先获取到这个有参数的构造方法
        Constructor con = c.getDeclaredConstructor(int.class, String.class, String.class,boolean.class);
        // 第二步:调用构造方法new对象
        Object newObj = con.newInstance(110, "jackson", "1990-10-11", true);
        System.out.println(newObj);

        // 获取无参数构造方法
        Constructor con2 = c.getDeclaredConstructor();
        Object newObj2 = con2.newInstance();
    }
}

重点:给你一个类,怎么获取这个类的父类,已经实现了哪些接口?

public class ReflectTest13 {
    public static void main(String[] args) throws Exception{
        // String举例
        Class stringClass = Class.forName("java.lang.String");
        // 获取String的父类
        Class superClass = stringClass.getSuperclass();	//java.lang.Object
        // 获取String类实现的所有接口(一个类可以实现多个接口。)
        Class[] interfaces = stringClass.getInterfaces();
        for(Class in : interfaces){
            System.out.println(in.getName());
        }
    }
}

11.4  文件路径

11.4.1 获取文件绝对路径

public class AboutPath {
    public static void main(String[] args) throws Exception{
        // 这种方式的路径缺点是:移植性差,在IDEA中默认的当前路径是project的根。
        // 这个代码假设离开了IDEA,换到了其它位置,可能当前路径就不是project的根了,这时这个路径就无效了。
        //FileReader reader = new FileReader("chapter25/classinfo2.properties");

        // 接下来说一种比较通用的一种路径。即使代码换位置了,这样编写仍然是通用的。
        // 注意:使用以下通用方式的前提是:这个文件必须在类路径下。
        // 什么类路径下?方式在src下的都是类路径下。
        /*
        解释:
            Thread.currentThread() 当前线程对象
            getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象。
            getResource() 【获取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。
         */
        String path = Thread.currentThread().getContextClassLoader().getResource("reflect/classinfo2.properties").getPath(); // 这种方式获取文件绝对路径是通用的。
    }
}

11.4.2 改进IO流

public class IoPropertiesTest {
    public static void main(String[] args) throws Exception{
        // 获取一个文件的绝对路径了!!!!!
        /*String path = Thread.currentThread().getContextClassLoader().getResource("classinfo2.properties").getPath();
        FileReader reader = new FileReader(path);*/
        // 直接以流的形式返回。
        InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("reflect/classinfo2.properties");
        Properties pro = new Properties();
        pro.load(reader);
        reader.close();
        // 通过key获取value
        String className = pro.getProperty("className");
        System.out.println(className);
    }
}

11.4.3 快速绑定属性资源文件

public class ResourceBundleTest {
    public static void main(String[] args) {
        // 资源绑定器,只能绑定xxx.properties文件。并且这个文件必须在类路径下。文件扩展名也必须是properties
        // 并且在写路径的时候,路径后面的扩展名不能写。
        //ResourceBundle bundle = ResourceBundle.getBundle("classinfo2");
        ResourceBundle bundle = ResourceBundle.getBundle("bean/db");
        String className = bundle.getString("className");//fdjskalfjkdlsajfkldsjaklfdjskalf
    }
}

11.5  类加载器(ClassLoader

专门负责加载类的命令/工具。JDK中自带了3个类加载器

  • 启动类加载器:jdk1.8.0_101\jre\lib\rt.jar
  • 扩展类加载器:jdk1.8.0_101\jre\lib\ext/*.jar
  • 应用类加载器:classpath

Java中为了保证类加载的安全,使用了双亲委派机制。优先从启动类加载器中加载,这个称为“父”“父”无法加载到,再从扩展类加载器中加载,这个称为“母”。双亲委派。如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值