Java反射机制

目录

类加载器机制

反射机制

java.lang.Class:

java.lang.reflect.Method:

可变长度参数

java.lang.reflect.Constructor:

java.lang.reflect.Field:

加载资源文件

文件路径的问题

加载文件

资源绑定器


类加载器机制

1、什么是类加载器?
        专门负责类加载的命令/工具。(ClassLoader)

2、JDK中自带3个类加载器
          启动类加载器: rt.jar
          扩展类加载器: ext/*.jar
          应用类加载器: classpath

3、假设有这样一段代码:
            String s = "abc";

            代码在开始执行之前,会将所需要类全部加载到JVM当中。通过类加载器加载,看到以上代码类加载会找到String.class文件,找到就加载

            那么是怎么进行加载的呢?

                首先,通过“启动类加载器”加载。
                    注意:启动类加载器专门加载:jdk根路径\jre\lib\rt.jar
                    rt.jar中都是JDK最核心的类库。

                如果通过“启动类加载器”加载不到的时候,
                会通过“扩展类加载器”加载。
                    注意:扩展加载器专门加载:jdk根路径\jre\lib\ext\*.jar

                如果“扩展类加载器”没有加载到,那么会通知“应用类加载器”加载。
                    注意:应用类加载器专门加载:classpath中的类。

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

反射机制

1、反射机制有什么用?
        通过java语言中的反射机制可以操作(读和修)字节码文件。
        通过反射机制可以操作代码片段。下(class文件。)

2、反射机制的相关类在 java.lang.reflect.*; 包下

3、反射机制相关的重要的类和方法?

java.lang.Class

        代表整个字节码,代表一个类型。

要操作一个类的字节码,需要首先获取到这个类的字节码,怎么获取java.lang.Class实例?
        三种方式:
                第一种:Class c = Class.forName("完整类名带包名");

try {
    Class c1 = Class.forName("java.lang.String"); 
    Class c2 = Class.forName("java.util.Date");  // c2代表Date类型
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

解析:c1代表String.class文件,或者说c1代表String类型。而c2代表Date类型。        

                第二种:Class c = 对象.getClass();

String s = "abc";
Class x = s.getClass();        
Date time = new Date();
Class y = time.getClass();

解析:x代表String.class字节码文件,x代表String类型。

【注意】:String.class 字节码文件对应内存地址是一致的。

System.out.println(c1 == x); // true

附加内存图

Java反射机制

                第三种:Class c = 任何类型.class;

Class z = String.class; //z代表String类型
Class k = Date.class; // k代表Dare类型

注意:java语言中任何一种类型,包括基本数据类型,他都有.class属性。

获取到class后,可以通过Class的newInstance()方法来实例化对象

创建User类

public class User {
    public User() {
        System.out.println("无参构造方法!");
    }
}

普通方式创建对象

User user = new User();
System.out.println(user);

使用反射机制创建对象

try {
    Class c = Class.forName("test.User"); // c代表User类型
    Object obj = c.newInstance();
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InstantiationException e) {
    e.printStackTrace();
}

【注意】:
        newInstance()方法内部实际上调用了无参数构造方法,必须保证无参数构造存在才可以。

        forName()方法参数是类名的全限定名称,简单点 就是包名 + "类名"。

        如果你只是希望一个类的静态代码块执行,其他代码一律不执行,可以使用:
                Class.forName("完整类名");
         这个方法的执行会导致类加载,类加载时,静态代码块执行。

public class MyClass {
    // 静态代码块在类加载时执行,并且只执行一次。
    static {
        System.out.println("MyClass类的静态代码块执行了!");
    }
}

【重点】:获取一个类的父类,已经实现了哪些接口

try {
    Class stringClass = Class.forName("java.lang.String");

    // 获取String的父类
    Class superClass = stringClass.getSuperclass();
    System.out.println(superClass);

    // 获取String类实现的所有接口(一个类可以实现多个接口)
    Class[] interfaces = stringClass.getInterfaces();
    for (Class in : interfaces) {
        System.out.println(in.getName());
    }
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

java.lang.reflect.Method:

        代表字节码中的方法字节码。代表类中的方法。

可变长度参数

int... args 这就是可变长度参数
        语法是:类型... (注意:一定是3个点。)
1、可变长度参数要求的参数个数是:0~N个。
2、可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有1个。

public static void m12(int... args2, String... args1){}

上面这样是错误的定义方式。
3、可变长度参数可以当做一个数组来看待。(args有length属性,说明args是一个数组。)

public class ArgsTest {
    public static void main(String[] args) {
        m2(100);
        m2(200, "abc");
        m2(200, "abc", "def");

        String[] strs = {"a", "b", "c"};
        m3(strs);// 也可以传1个数组

        // 直接传1个数组
        m3(new String[]{"我", "是", "好", "人"}); // 没必要
        m3("我", "是", "好", "人");
    }

    public static void m2(int a, String... args1){
    }

    public static void m3(String... args){
        for (int i = 0; i < args.length; i++) {
            System.out.println(args[i]);
        }
    }
}

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

创建UserService类

public class UserService {
    public boolean login(String name, String password){
        if ("admin".equals(name) && "123".equals(password)){
            return true;
        }
        return false;
    }
    // java中怎么区分一个方法,依靠方法名和参数列表。
    public void login(int i){
    }
}

使用一般方式调用对象方法

UserService userService = new UserService();
boolean loginSuccess = userService.login("admin", "123");
System.out.println(loginSuccess ? "登录成功" : "登录失败");

使用反射机制调用

try {
    Class userServiceClass = Class.forName("test.UserService");
    Object obj = userServiceClass.newInstance();
    // 获取Method
    Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
    //Method loginMethod = userServiceClass.getDeclaredMethod("login", int.class);
    // 调用方法
    Object retValue = loginMethod.invoke(obj, "admin", "123");
    System.out.println(retValue);
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (NoSuchMethodException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
}

【记住】:invoke(); 这个方法很重要。

java.lang.reflect.Constructor:

        代表字节码中的构造方法字节码。代表类中的构造方法。

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

创建Vip类

public class Vip {
    int no;
    String name;
    String birth;
    boolean sex;

    public Vip() {
    }
    public Vip(int no, String name, String birth, boolean sex) {
        this.no = no;
        this.name = name;
        this.birth = birth;
        this.sex = sex;
    }
    @Override
    public String toString() {
        return "Vip{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", birth='" + birth + '\'' +
                ", sex=" + sex +
                '}';
    }
}

使用一般方式调用构造方法

Vip v1 = new Vip();
Vip v2 = new Vip(110, "zhangsan", "200-20-11", true);

使用反射机制调用构造方法的步骤

第一步:先获取到这个有参数的构造方法

第二步:调用构造方法new对象

try {
    Class c = Class.forName("test.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", "199-10-11", true);
    System.out.println(newObj);

    // 获取无参数构造方法
    Constructor con2 = c.getDeclaredConstructor();
    Object newObj2 = con2.newInstance();
    System.out.println(newObj2);
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (NoSuchMethodException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
}

java.lang.reflect.Field:

        代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。

通过反射机制访问一个java对象的属性?
        给属性赋值set;获取属性的值get

创建Student类

public class Student {
    public int no; // Field对象
    private String name;
    protected int age; 
    boolean sex; 
    public static final double MTH_PI = 3.1415926;
}

使用一般方式操作属性

Student s = new Student();
s.no = 1111; 
System.out.println(s.no);

使用反射机制

try {
    Class studentClass = Class.forName("test.Student");
    Object obj = studentClass.newInstance(); 
    // 获取no属性(根据属性的名称来获取Field)
    Field noField = studentClass.getDeclaredField("no");
    noField.set(obj, 2222);  // 给obj对象的no属性赋值2222
    System.out.println(noField.get(obj));// 读取属性的值

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

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

    nameField.set(obj, "jackson");// 给name属性赋值
    System.out.println(nameField.get(obj));// 获取name属性的值
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (NoSuchFieldException e) {
    e.printStackTrace();
}

注意:反射机制让代码复杂了,但是少了一个“灵活”,这也是值得的。

        方法私有的属性,需要打破封装,可能会变得不安全。

加载资源文件

创建classinfo.properties文件

className=java.util.Date

文件路径的问题

获取一个文件的绝对路径

FileReader reader = null;
try {
    reader = new FileReader("src/classinfo.properties");
} catch (FileNotFoundException e) {
    e.printStackTrace();
}

上面这种方式的路径的缺点是:可移植性差,在IDEA中默认的当前路径是project的根。这个代码假设离开了IDEA。换到其他的位置,可能当前路径就不是project的根了,这时这个路径就无效了。

以下讲解的这种方式是通用的,但前提是:文件需要在类路径下,才能用这种方式。

【注】:src是类的根路径。

String path = Thread.currentThread().getContextClassLoader()
                .getResource("classinfo.properties").getPath();

解析:Thread.currentThread() 当前线程对象
          getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象。
           getResource() 【获取资源】这是类加载器对象的方法,当前线程的类加载默认从类的根路径下加载资源。

        classinfo.properties文件位于src下(从类的根路径下作为起点开始)

加载文件

方式一

String path = Thread.currentThread().getContextClassLoader()
                .getResource("classinfo.properties").getPath();

FileReader reader = null;
Properties pro = new Properties();
try {
    reader = new FileReader(path);   // 路径有中文会报错。
    pro.load(reader);
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
// 通过key获取value
String  className = pro.getProperty("className");
System.out.println(className);

方拾二:直接以流的形式返回

InputStream reader = Thread.currentThread()
                .getContextClassLoader().getResourceAsStream("classinfo.properties");
Properties pro = new Properties();
try {
    pro.load(reader);
} catch (IOException e) {
    e.printStackTrace();
}
// 通过key获取value
String  className = pro.getProperty("className");
System.out.println(className);

资源绑定器

java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容。

ResourceBundle bundle = ResourceBundle.getBundle("classinfo");
String className = bundle.getString("className");
System.out.println(className);

使用以上这种方式的时候,属性配置文件xxx.properties必须放到类路径下,并且在写路经的时候,路径后面的扩展名不能写。

上一篇:java 读写UTF-8文件的方法


下一篇:Java IO流