zoukankan      html  css  js  c++  java
  • 011类加载器和反射

    TOC

    类加载器和反射

    类加载器

    概述

    • 定义:当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
    • 加载
      就是指将class文件读入内存,并为之创建一个Class对象(构造方法、成员变量、成员方法)。
      任何类被使用时系统都会建立一个Class对象。
    • 连接
      • 验证是否有正确的内部结构,并和其他类协调一致
      • 准备负责为类的静态成员分配内存,并设置默认初始化值(静态的)--
        静态成员是随着类的加载而加载的
      • 解析将类的二进制数据中的符号引用替换为直接引用
    • 初始化 就是我们以前讲过的初始化步骤

    类初始化时机

    1. 创建类的实例(实例化)
    2. 访问类的静态变量,或者为静态变量赋值
    3. 调用类的静态方法
    4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
    5. 初始化某个类的子类
    6. 直接使用java.exe命令来运行某个主类

    类加载器

    • 类加载器概述:负责将.class文件加载到内在中,并为之生成对应的Class对象。

      虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。

    • 类加载器的组成
      • Bootstrap ClassLoader 根类加载器
      • Extension ClassLoader 扩展类加载器
      • Sysetm ClassLoader 系统类加载器
    • 类加载器的作用
      • Bootstrap ClassLoader 根类加载器
        也被称为引导类加载器,负责Java核心类的加载
        比如System,String等。在JDK中JRE的lib目录下rt.jar(全部是class文件)文件中
      • Extension ClassLoader 扩展类加载器
        负责JRE的扩展目录中jar包的加载。
        在JDK中JRE的lib目录下ext目录
      • Sysetm ClassLoader 系统类加载器(自己写的文件)
        负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径

    反射

    反射概述

    JAVA反射机制是在运行状态中,对于任意一个,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

    要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.

    • 反射定义:就是通过class文件对象,去使用该文件中的成员变量,构造方法,成员方法。
    Person p = new Person();
    p.使用------这不叫反射

    要想这样使用,首先你必须得到class文件对象,其实也就是得到Class类的对象。

    • Class类组成:
      • 成员变量 Field
      • 构造方法 Constructor
      • 成员方法 Method
    • 获取class文件对象的方式:
      1. Object类的getClass()方法
      2. 数据类型的静态属性class
      3. Class类中的静态方法(开发建议使用)
    public static Class forName(String className)//forName

    一般我们到底使用谁呢?
    A:自己玩 任选一种,第二种比较方便
    B:开发 第三种
    为什么呢?因为第三种是一个字符串,而不是一个具体的类名。这样我们就可以把这样的字符串配置到配置文件中

    class

    拿到class字节码文件对象

    public class ReflectDemo {
        public static void main(String[] args) throws ClassNotFoundException {
            // 方式1
            Person p = new Person();
            Class c = p.getClass(); //1.有泛型,所以有黄线
            //c拿到p的字节码文件
            Person p2 = new Person();
            Class c2 = p2.getClass();
            System.out.println(p == p2); // false
            System.out.println(c == c2); // true  ,他们是同一个class文件
            // 方式2
            Class c3 = Person.class;//2.
            // int.class;
            // String.class;   只要是数据类型都有class文件对象
            System.out.println(c == c3); // true ,他们是同一个class文件
            // 方式3
            // ClassNotFoundException需要抛出异常
            Class c4 = Class.forName("cn.itcast_01.Person"); //3.需要类的全部路径
            System.out.println(c == c4);
        }
    }
    

    注意:第三种写法容易出错,

    1. 可以在外侧写完了在剪切过去
    2. 右击类对应的绿色图标,选择复制类的全路径,就可以直接粘贴了。
    获取父类class
    Person p = new Person();
    p.getClass().getSuperclass()

    构造方法Constructor。

    获取构造方法
    • 所有公共构造方法,public
    public Constructor[] getConstructors()
    • 所有构造方法
    public Constructor[] getDeclaredConstructors()
    Constructor[] cons = c.getDeclaredConstructors();
    • 获取单个公共构造方法

      …表示几个参数都可以

    public Constructor<T> getConstructor(Class<?>… parameterTypes)
    Constructor con = c.getConstructor();//返回的是构造方法对象
    • 获取私人构造
    Constructor con = c.getDeclaredConstructor(String.class);
    con.setAccessible(true);//强制访问私有方法,然后就可以和普通构造一样使用了
    调用构造方法
    public T newInstance(Object... initargs)

    使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。获得的是初始化的相应构造方法,及所有变量的初始化值

    Object obj = con.newInstance();
    Constructor con = c.getConstructor(String.class, int.class,String.class);

    相当于:

    Person p = new Person();
    System.out.println(p);
    • 获得所有构造方法,并输出
    //输出的构造方法是全名称的
    public class ReflectDemo {
        public static void main(String[] args) throws Exception {
            // 获取字节码文件对象
            Class c = Class.forName("cn.itcast_01.Person");
    
            Constructor[] cons = c.getDeclaredConstructors(); //获得所有构造方法
    
            for (Constructor con : cons) { //遍历输出构造方法。
                System.out.println(con);
            }
        }
    }

    • 无参构造
    public class ReflectDemo {
        public static void main(String[] args) throws Exception {
            // 获取字节码文件对象
            Class c = Class.forName("cn.itcast_01.Person");
    
            Constructor con = c.getConstructor(); // 返回的是单个构造方法对象,无参
    
            Object obj = con.newInstance(); //调用并获得构造方法,
    
            System.out.println(obj);
    
            //获得的是初始化的相应构造方法,及初始化的变量
        }
    }
    

    • 带参构造
    /*带参构造
    * public Person(String name, int age, String address)
    * Person p = new Person("林青霞",27,"北京");
    * System.out.println(p);
    
    反射的方法是:
    
     */
    public class ReflectDemo2 {
        public static void main(String[] args) throws Exception {
            // 获取字节码文件对象
            Class c = Class.forName("cn.itcast_01.Person");
    
            // 获取带参构造方法对象
            Constructor con = c.getConstructor(String.class, int.class, String.class);
    
            // 通过带参构造方法对象创建对象
            // public T newInstance(Object... initargs)
            Object obj = con.newInstance("林青霞", 27, "北京");
    
            System.out.println(obj);
        }
    }
    
    • 私有构造
    /*私有构造
     * private Person(String name){}
     * Person p = new Person("风清扬");
     * System.out.println(p);
     */
    public class ReflectDemo3 {
        public static void main(String[] args) throws Exception {
            // 获取字节码文件对象
            Class c = Class.forName("cn.itcast_01.Person");
    
            // 获取私有构造方法对象
            // NoSuchMethodException:每个这个方法异常1:
            // 原因是一开始我们使用的方法只能获取公共的,下面这种方式就可以了。
            Constructor con = c.getDeclaredConstructor(String.class);
    
            // 用该私有构造方法创建对象
            // IllegalAccessException:非法的访问异常2:。
            // 暴力访问
            con.setAccessible(true); // 值为true则指示反射的对象在使用时应该取消Java语言访问检查。
    
            Object obj = con.newInstance("风清扬");
    
            System.out.println(obj);
        }
    }
    强制转换(一般不用)

    强制转换为对应的类。

    可以直接使用对应方法(反射没机会这样用)

    Person p = (Person)obj;
    强制访问

    强制访问私有构造方法,然后就可以和普通构造一样使用了

    con.setAccessible(true);

    Field成员变量

    c是一个class

    获取所有public成员变量(自己的public和父类的public的)
    Field[] fields = c.getFields();
    获取所有成员变量(自己的所有)
    Field[] fields = c.getDeclaredFields();
    获取单个的成员变量
    Field addressField = c.getField("address");//获得address
    变量赋值
    public void set(Object obj,Object value)
    addressField.set(obj, "北京");//先创建构造,在获取成员变量,才能赋值
    强制访问私有变量

    通过getDeclaredFields可以获取私有的变量,但是私有的无法赋值,使用这个设置true之后,就可以正常访问这个字段了(读/写)

    ageField.setAccessible(true);
    获取字段名
    String column = field.getName();
    获取值
    //获取所有字段
    Field[] fields = obj.getClass().getDeclaredFields();
    for (Field field : fields) {
        //设置是否允许访问,不是修改原来的访问权限修饰词。
        field.setAccessible(true);
        Object value = field.get(obj);
    }
    案例
    // Person p = new Person(); p.address = "北京"; System.out.println(p);
    public class ReflectDemo {
        public static void main(String[] args) throws Exception {
            // 获取字节码文件对象
            Class c = Class.forName("cn.itcast_01.Person");
    
            // 通过无参构造方法创建对象
            Constructor con = c.getConstructor();
    
            Object obj = con.newInstance();
    
            System.out.println(obj);
    
            // 获取单个的成员变量
            // 获取address并对其赋值
            Field addressField = c.getField("address");
    
            //赋值
            addressField.set(obj, "北京"); // 给obj对象的addressField字段设置值为"北京"
    
            System.out.println(obj);
    
            // 获取name并对其赋值
    
            // NoSuchFieldException
            Field nameField = c.getDeclaredField("name");
    
            // IllegalAccessException
            nameField.setAccessible(true); //强制访问私有
    
            nameField.set(obj, "林青霞");
    
            System.out.println(obj);
    
            // 获取age并对其赋值
            Field ageField = c.getDeclaredField("age");
    
            ageField.setAccessible(true);
    
            ageField.set(obj, 27);
    
            System.out.println(obj);
        }
    }
    

    Method方法

    获取所有public方法---public,包括父类public
    Method[] getMethods
    获取自己的所有方法(包括私有)(无父类)
    Method[] getDeclaredMethods
    获取单个public方法
    Method getMethod
    Method m1 = c.getMethod("show");
    Method m3 = c.getMethod("getString", String.class, int.class);
    获取单个方法—可以私有
    getDeclaredMethod
    暴力访问
    method.setAccessible(true);
    调用方法
    public Object invoke(Object obj,Object... args)
    m2.invoke(obj);
    Object objString = m3.invoke(obj, "hello", 100);
    //返回值是Object接收,第一个参数表示对象是谁,后面参数表示调用该方法的实际参数
    案例
    //Person p = new Person(); p.show();
    public class ReflectDemo {
        public static void main(String[] args) throws Exception {
            // 获取字节码文件对象
            Class c = Class.forName("cn.itcast_01.Person");
    
            // 获取所有的方法,并输出
            // Method[] methods = c.getMethods(); // 获取自己的包括父亲的公共方法
            // Method[] methods = c.getDeclaredMethods(); // 获取自己的所有的方法
            // for (Method method : methods) {
            // System.out.println(method);
            // }
            Constructor con = c.getConstructor(); //获取无参构造
            Object obj = con.newInstance(); //调用无参构造
    
            // 获取单个方法并使用
    
            // 无参方法
            Method m1 = c.getMethod("show");
    
            //调用方法
            m1.invoke(obj); // 调用obj对象的m1方法
            System.out.println("----------");
    
            // 获取有参方法
            Method m2 = c.getMethod("method", String.class);
    
            //调用方法(方法,参数)
            m2.invoke(obj, "hello");
            System.out.println("----------");
    
            // public String getString(String s, int i)
            Method m3 = c.getMethod("getString", String.class, int.class);
            Object objString = m3.invoke(obj, "hello", 100);
            System.out.println(objString); //直接输出会自动转换为字符串
            System.out.println("----------");
            // private void function()
            Method m4 = c.getDeclaredMethod("function");
            m4.setAccessible(true);
            m4.invoke(obj);
        }
    }
    

    反射案例

    通过配置文件运行类中的方法(便于修改调用不同类)

    需要有配置文件配合使用。

    做配置文件,键是固定的,值是可变的

    • class.txt代替。
    • 并且你知道有两个键。
      • className
      • methodName

    class.txt存需要调用的类的名称和方法,className保存类名,methodName保存方法名,这样修改调用的方法的时候,直接修改class.txt文件就行了

    public class Test {
        public static void main(String[] args) throws Exception {
            // 反射后的做法
    
            // 加载键值对数据
            Properties prop = new Properties();
            FileReader fr = new FileReader("class.txt");
            prop.load(fr);
            fr.close();
    
            // 获取数据
            String className = prop.getProperty("className"); //根据键获取值,获得类名
    
            String methodName = prop.getProperty("methodName"); //获得方法名
    
            // 反射
            Class c = Class.forName(className); //获得字节码文件对象
    
            Constructor con = c.getConstructor(); //通过无参构造对象
    
            Object obj = con.newInstance();
    
            // 调用方法
            Method m = c.getMethod(methodName);
    
            m.invoke(obj);
        }
    }
    public class Student {
        public void love() {
            System.out.println("爱生活,爱Java");
        }
    }
    public class Teacher {
        public void love() {
            System.out.println("爱生活,爱青霞");
        }
    }

    ArrayList对象,添加一个字符串数据

    /*
    
     * 我给你ArrayList<Integer>的一个对象,我想在这个集合中添加一个字符串数据,如何实现呢?
    
     */
    public class ArrayListDemo {
        public static void main(String[] args)
            throws NoSuchMethodException, SecurityException, IllegalAccessException,
                IllegalArgumentException, InvocationTargetException { //Exception也可以
    
            // 创建集合对象
            ArrayList<Integer> array = new ArrayList<Integer>();
    
            //源码是object类型,使用源码就可以了
            Class c = array.getClass(); // 集合ArrayList的class文件对象
    
            Method m = c.getMethod("add", Object.class); //获得方法
    
            m.invoke(array, "hello"); // 调用array的add方法,传入的值是hello
    
            m.invoke(array, "world");
    
            m.invoke(array, "java");
    
            System.out.println(array);
        }
    }

    将对象的名为propertyName的属性的值设置为value

    写一个方法,public void setProperty(Object obj, String propertyName, Object value){},此方法可将obj对象中名为propertyName的属性的值设置为value。

    定义方法强制赋值(比如原来是私有的,实例化之后不能赋值,就暴力赋值)
    Final的值仍是不可变

    定义:

    public class Tool {
        public void setProperty(Object obj, String propertyName, Object value)
            throws NoSuchFieldException, SecurityException, IllegalArgumentException,
                IllegalAccessException {
            // 根据对象获取字节码文件对象
            Class c = obj.getClass();
    
            // 获取该对象的propertyName成员变量
            Field field = c.getDeclaredField(propertyName);
    
            // 取消访问检查—暴力访问
            field.setAccessible(true);
    
            // 给对象的成员变量赋值为指定的值
            field.set(obj, value);
        }
    }
    

    调用方法:

    public class ToolDemo {
        public static void main(String[] args)
            throws NoSuchFieldException, SecurityException, IllegalArgumentException,
                IllegalAccessException {
            Person p = new Person(); //name是私有的,不能直接赋值
    
            Tool t = new Tool();
    
            t.setProperty(p, "name", "林青霞"); //调用方法后,直接暴力赋值
    
            //new Tool().setProperty(p, "name", "林青霞");这样也可以,不过多次应用会麻烦。
            t.setProperty(p, "age", 27);
    
            System.out.println(p);
    
            System.out.println("-----------");
    
            Dog d = new Dog();
    
            t.setProperty(d, "sex", '男');
    
            t.setProperty(d, "price", 12.34f);
    
            System.out.println(d);
        }
    }
    
    
    class Dog {
        char sex;
        float price;
    
        @Override
        public String toString() {
            return sex + "---" + price;
        }
    }
    
    
    class Person {
        private String name;
        public int age;
    
        @Override
        public String toString() {
            return name + "---" + age;
        }
    }
    

    通过用户的增删改查和学生的登录注册引出中介

    用户操作接口

    public interface UserDao {
        public abstract void add(); //增
        public abstract void delete(); //删
        public abstract void update(); //改
        publicabstract void find(); //查
    }

    接口实现类

    public class UserDaoImpl implements UserDao {
    
        @Override
        publicvoid add() {
            System.out.println("添加功能");
        }
    
        @Override
        publicvoid delete() {
            System.out.println("删除功能");
        }
    
        @Override
        publicvoid update() {
            System.out.println("修改功能");
        }
        @Override
        publicvoid find() {
            System.out.println("查找功能");
        }
    }

    测试类

    public class UserDaoDemo {
        publicstatic void main(String[] args) {
    
            // 基本的用户操作
            UserDao ud = new UserDaoImpl(); //多态
            ud.add();
            ud.delete();
            ud.update();
            ud.find();
            System.out.println("---------");
    
            // 真实的需求应该是这个样子的:
            // 在每个操作执行前:应该看看你是否有权限进行这个操作
            // 谁操作的这个东西,你得给我留下记录
            //在每一个方法前加权限校验,后面加日志记录(看是谁操作的)
            UserDao ud2 = new UserDaoImpl2();
    
            ud2.add();
            ud2.delete();
            ud2.update();
            ud2.find();
    
            // 假设我还有一个学生类,也具备这样的操作,
            // 我还有一个老师类,也具备同样的操作
            //那么每种类都要对应一个接口(StudentDemo),两个类(StudentDemoImpl和StudentDemoImpl2)
            System.out.println("---------");
    
            StudentDao sd = new StudentDaoImpl();
    
            sd.login();
    
            sd.regist();
    
            System.out.println("---------");
    
            // 真实的需求应该是这个样子的:
            // 在每个操作执行前:应该看看你是否有权限进行这个操作
            // 谁操作的这个东西,你得给我留下记录
            StudentDao sd2 = new StudentDaoImpl2();
    
            sd2.login();
            sd2.regist();
    
        }
    
    }

    添加内容

    //想要有新添加的内容,不能直接修改已经完成的方法,而是新建一个类继承之前的类
    public class UserDaoImpl2 implements UserDao {
    
        @Override
        publicvoid add() {
            System.out.println("权限校验");
            System.out.println("添加功能");
            System.out.println("日志记录");
        }
    
        @Override
        publicvoid delete() {
            System.out.println("权限校验");
            System.out.println("删除功能");
            System.out.println("日志记录");
        }
    
        @Override
        publicvoid update() {
            System.out.println("权限校验");
            System.out.println("修改功能");
            System.out.println("日志记录");
        }
    
        @Override
        publicvoid find() {
            System.out.println("权限校验");
            System.out.println("查找功能");
            System.out.println("日志记录");
        }
    }
    public interface StudentDao {
        public abstract void login();
        public abstract void regist();
    }
    
    //______________________________________________________
    
    public class StudentDaoImpl implements StudentDao {
        @Override
        public void login() {
            System.out.println("登录功能");
        }
    
        @Override
        public void regist() {
            System.out.println("注册功能");
        }
    }
    
    //______________________________________________________
    
    public class StudentDaoImpl2 implements StudentDao {
        @Override
        public void login() {
            System.out.println("权限校验");
            System.out.println("登录功能");
            System.out.println("日志记录");
        }
        @Override
        public void regist() {
            System.out.println("权限校验");
            System.out.println("注册功能");
            System.out.println("日志记录");
        }
    }

    权限和日志都是一样的,可以提取出来,作为中介---动态代理





  • 相关阅读:
    【Linux】PuTTY----------windows访问Linux 快捷方便
    接口测试、概念及常用方法小结
    设计模式
    事务
    Struts2技术详解
    message from server: "Host 'xxx' is not allowed to connect to this MySQL server的解决
    Java中多态性的实现
    应用上下文webApplicationContext
    ubuntu 12.04下访问windows共享文件夹
    排序问题分析
  • 原文地址:https://www.cnblogs.com/ziyue7575/p/13408792.html
Copyright © 2011-2022 走看看