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("日志记录");
    }
}

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





原文地址:https://www.cnblogs.com/ziyue7575/p/13408792.html