反射机制和类加载机制

反射

1.反射的定义 

Java的反射机制是在 运行 状态中,对于任意一个类,都能够知道这个类的 所有属性和方法 ;对于任
意一个对象,都能够调用它的任意方法和属性,既然能拿到,我们就可以修改部分类型信息;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

2.反射的用途

  •  利用java的反射机制来获取私有成员或方法
  • 开发各种通用框架

3.反射相关的类

类名                用途
Class类            代表类的实体,在运行的java应用程序中表示类和接口
Field类            代表类的成员变量/类的属性
Method类           代表类的方法
Constructor类      代表类的构造方法

 4.Class类

代表类的实体,在运行的java应用程序中表示类和接口。

java文件被编译后生成.class文件,JVM此时就要去解读.class文件。java.class文件也会被JVM解析为一个对象,这个对象就是java.lang.class。这样当程序在运行时,每个java文件就最终变成了Class类对象的实例。我们通过java的反射机制应用到这个实例,就可以去获取、添加、改变这个类的属性和方法。使得这个类成为一个动态的类。

Class对象是反射的基础,一个类对应一个class对象 

Class类的一些知识点 

1.Class也是类,也是继承Object类

2.Class类对象不是new出来的,而是系统创建的(是通过类加载器ClassLoader里面的方法loadClass创建出来的) 

3.每个类的实例都会记住自己是由哪个Class实例所生成的

4.Class对象是存放在堆中的

5.类的字节码二进制数据是存放在内存中的方法区的,类的字节码二进制数据又被称为源数据

6.对于某个Class类对象,不管实例化多少次,在内存中只存在一份。如果加载类不相同,那在堆中Class就不是同一个对象(如下所示)

package Reflect;


class Student {
    private String name = "jack";
    public int age = 18;
    protected double size = 175.5;
    
    public Student() {
        System.out.println("你好");
    }

    private Student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("西华大学");
    }

    private void eat() {
        System.out.println("正在吃东西");
    }

    public void sleep() {
        System.out.println("正在睡觉");
    }

    public void function(String str) {
        System.out.println(str);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
class teacher{
    private String name = "Tom";    
}
package Reflect;
@SuppressWarnings({"all"})
public class TestClass {
    public static void main(String[] args) throws Exception {
        Class cls1 = Class.forName("Reflect.Student");
        Class cls2 = Class.forName("Reflect.Student");
        Class cls3  = Class.forName("Reflect.teacher");
        System.out.println(cls1.hashCode());
        System.out.println(cls2.hashCode());
        System.out.println(cls3.hashCode());
    }
}

 

JAVA反射机制原理图 

Class类中的相关方法 

获得类的相关方法
方法                                用途
getClassLoader()                   获得类的加载器
getDeclaredClasses()               返回一个数组,数组中包含该类中所有类和接口类的对象(包括私有的)
forName(String className)          根据类名返回类的对象
newInstance()                      创建类的实例
getName()                          获得类的完整路径名字
获得类中属性的相关方法(以下方法返回值为Filed类型)
方法                                    用途
getField(String name)                   获得某个公有的属性对象
getFields()                             获得所有公有的属性对象
getDeclaredField(String name)           既可以获得公有的对象,也可以获得私有的对象
getDeclaredFields()                     获得所有属性对象
getModefiers                            以int形式返回修饰符(默认修饰符是0,public是1,private是2,Procted是4,static是8,final是16,组合:public(1)+static(8) = 9)
getType                                 返回字段的类型
getName                                 返回属性名

获得类中注解相关的方法
方法                                           用途
getAnnotation(Class annotationClass)          返回该类中与参数类型匹配的公有注释对象
getAnnotations()                              返回该类所有的公有注解对象
getDeclaredAnnotation(Class annotationClass)  返回该类中与参数类型匹配的所有注释对象
getDeclaredAnnotations()                      返回该类所有的注释对象

获得类中构造器相关的方法(以下方法返回值为Constructor类型)
方法                                                 用途
getConstructor(Class...<?>paramentTypes)            获得该类中与参数类型匹配的公有构造方法
getConstructors()                                   获得该类的所有公有构造方法
getDeclaredConstructor(Class...<?>paramentTypes)    获得该类中与参数类型匹配的构造方法
getDeclaresConstructors()                           获得该类所有构造方法
getModefiers                                        以int形式返回修饰符
getName                                             返回构造器名(全类名)
getParameterTypes                                   以Class[]返回参数类型数组

获得类中方法相关的方法(以下方法返回值为Method类型)
方法                                                        用途
getMethod(String name,Class...<?>paramentTypes)            获得该类某个公有的方法
getMethod()                                                获得该类所有公有的方法
getDeclaredMethod(String name,Class...<?>paramentTypes)    获得该类某个方法
getDeclaredMethods()                                       获得该类所有的方法
getModifiers                                               以int形式返回修饰符
getReturnType                                              以Class形式获取返回类型
getName                                                    返回方法名
getParamenterTypes                                         以Class[]返回参数类型数组    

package Reflect;

/**
 * 获得Class对象的三种方式
 */
public class TestGetClass {
    public static void main(String[] args) {
        /**
         * 1.通过getClass获取Class对象(getClass获取对象的运行类型),适用于知道了某个类的实例,通过该实例的getClass()方法获取Class对象
         */
        Student s1 = new Student();
        Class c1 = s1.getClass();
        System.out.println(c1);//输出打印class Reflect.Student

        /**
         * 2.直接通过类名.class的方式得到,该方法最为安全可靠,程序性能更高,多用于参数的传递,比如通过反射得到对应构造器对象。
         * 这点说明任何一个类都有一个隐含的静态成员变量class
         */
        Class<Student> s2 = Student.class;
        System.out.println(s2);

        /**
         * 3.通过Class对象的forName()静态方法来获取(或者通过读取配置文件),用的最多
         * 但是可能抛出ClassNotFoundException异常
         */

        Class c3 = null;
        try {
            c3 = Class.forName("Reflect.Student");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println(c3);
    }
}

 使用反射机制解决问题的步骤

  •  加载类(有三种方式,看前面),得到一个类型为Class的对象obj
  • 通过对象obj创建被你加载类比如Person的对象实例o(也就是实例化对象,这里要用到newInstances()这个方法)

        Cat cat;————只是创建了对象

        Cat cat = new Cat();————实例化对象

  • 有了实例化对象o,就可以调用被你加载的那个类,比如Person中的属性、方法、构造器等。

在使用反射机制解决问题的过程中,主要有两种实例化方法 

一:直接使用类变量来实例化对象 

package Reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@SuppressWarnings({"all"})
public class TestMethod {
    public static void main(String[] args) {
        Class<?> c3 = null;
        try {
            c3 = Class.forName("Reflect.Student");
            Student o = (Student)c3.newInstance();//输出你好————说明这里的newInstance()默认是调用不带参数的构造方法
            //下面是根据对象调用相关的字段、方法等(这里不能访问私有的成员)
            System.out.println(o);
            o.sleep();
            System.out.println(o.age);
            System.out.println(o.size+"cm");

//        Constructor<?>[] constructors = aClass.getDeclaredConstructors();
//        for (Constructor<?> constructor:
//                constructors) {
//            System.out.println("==================================");
//            System.out.println("本类中所有构造器="+constructor.getName());
//
//            Class<?>[] parameterTypes = constructor.getParameterTypes();
//            for (Class<?> parameterType:
//                    parameterTypes) {
//                System.out.println("该构造器的形参类型="+parameterType);
//            }
//        }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

 二:使用构造方法来实例化对象

package Reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@SuppressWarnings({"all"})
public class TestMethod {
    public static void main(String[] args) {
        Class<?> c3 = null;
        try {
            Class<?> c31 = Class.forName("Reflect.Student");

            Constructor<?> constructor = c31.getDeclaredConstructor(String.class, int.class);//获得构造器constructor(这里的String.class、int.class是通过类名.class这种方式来获取Class的对象)
            constructor.setAccessible(true);
            /**
             * 会抛出Class Reflect.TestMethod can not access a member of class Reflect.Student with modifiers "private"异常。
             * 也就是说反射类TestMethod不能访问反射类Student带有"private"修饰的成员
             * 说明现在使用构造器来实例化对象它和直接
             * 使用类变量来实例化对象对private属性的成员都不能访问。若要进行访问,就必须让方法使用setAccessible函数。
             * setAccessible是JAVA启用和禁用访问安全检查的开关,值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
             * 值为 false 则指示反射的对象应该实施 Java 语言访问检查
             */
            Student smith = (Student) constructor.newInstance("smith", 28);//通过构造方法来实例化对象
            smith.function("杀马特团长,你就是个寄吧");
            smith.sleep();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

以上两种方式都是使用newInstance()来创建类的实例,但是通过构造器来实例化对象construtor.newInstance()中newInstance是构造器的,而通过类变量来实例化对象newInstance是Class的。

实例化对象以后就可以调用对象里面的方法、字段等

一:调用反射类里面的字段

package Reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@SuppressWarnings({"all"})
public class TestMethod {
    public static void main(String[] args) {
        Class<?> c3 = null;
        try {
            c3 = Class.forName("Reflect.Student");
            Student o = (Student)c3.newInstance();
            System.out.println(o);
            Field field = (Field) c3.getDeclaredField("name");
            field.setAccessible(true);//因为字段name是私有属性的,所以为了访问它,必须要调用setAccessible
            field.set(o,"王磊");
            /**
            *反射语法:成员变量对象.get/set(对象)
            */
            System.out.println(o);

        /**
            Class<?> aClass = Class.forName("Reflect.Student");
            Object o = aClass.newInstance();
            Field[] fields = aClass.getDeclaredFields();
            for (Field field:
                 fields) {
                System.out.println("本类所有的属性=:"+field.getName()
                +" 该属性的修饰符值="+field.getModifiers()
                +" 该属性的类型="+field.getType());
            }
        */
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

二:调用反射类里面的方法 

package Reflect;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@SuppressWarnings({"all"})
public class TestMethod {
    public static void main(String[] args) {
        Class<?> c3 = null;
        try {
            Class<?> c31 = Class.forName("Reflect.Student");

            Constructor<?> constructor = c31.getDeclaredConstructor(String.class, int.class);//获得构造器constructor
            constructor.setAccessible(true);
            Student smith = (Student) constructor.newInstance("smith", 28);//通过构造方法来实例化对象
            Method method = c31.getDeclaredMethod("eat");
            method .setAccessible(true);
            /**
            *调用方法的语法:方法.invoke(对象)
            */
            method .invoke(smith);

//        Method[] methods = aClass.getDeclaredMethods();
//        for (Method method:
//             methods) {
//            System.out.println("本类所有的方法="+method.getName()
//            +" 该方法的修饰符值="+method.getModifiers()
//            +" 该方法的返回类型="+method.getReturnType());
//
//            //输出当前这个方法的形参数组情况
//            Class<?>[] paramentTypes = method.getParameterTypes();
//            for (Class<?> paramentType:
//                    paramentTypes) {
//                System.out.println("该方法的形参类型="+paramentType);
//            }
//        }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();

        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

 通过上面的代码我们发现,在调用私有的成员之前,必须使用方法或字段.setAccessible()

setAccessible是JAVA启用和禁用访问安全检查的开关。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查,值为 false 则指示反射的对象应该实施 Java 语言访问检查

Java中反射的好处

  • 对于任何一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法
  • 反射能够运用到各种框架中,可以通过外部配置文件,在不修改原码的情况下来控制程序,列入像下面这样

根据配置文件re.properties中指定的信息,调用指定类中的方法

下面是配置文件中的信息

classfullpath = Reflect.Student//被操作类的全路径名
method = sleep//被操作类中的方法

下面是通过反射来执行的操作

package Reflect;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

@SuppressWarnings({"all"})
public class TestMethod {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException,
            IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\re.propertise"));
        String classfullpath = properties.get("classfullpath").toString();
        String methodName = properties.get("method").toString();
        Class<?> c4 = Class.forName(classfullpath);
        Object o = c4.newInstance();//这里会输出你好,因为我们在无参构造器里面写了一个输出你好的操作
        Method method = c4.getDeclaredMethod(methodName);
        method.setAccessible(true);
        method.invoke(o);
    }
}

现在我们想调用Student类里面的其他方法,我们只需要修改配置文件的信息即可

classfullpath = Reflect.Student
method = eat

 然后在TestMethod类里面的代码不变,看一下输出的效果

JAVA中反射的缺点

反射的执行效率慢,并且反射代码比直接代码复杂(看下面的示例)

package Reflect;

import java.lang.reflect.Method;

@SuppressWarnings({"all"})
public class TestClass {
    public static void main(String[] args) throws Exception {
        c1();
        c2();
    }
    public static void c1(){
        Student student = new Student();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            student.sleep();
        }
        long end = System.currentTimeMillis();
        System.out.println("传统方式代码执行时间:"+(end-start));
    }
    public static void c2() throws Exception {
        Class aClass = Class.forName("Reflect.Student");
        Object o = aClass.newInstance();
        Method method = aClass.getMethod("sleep");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            method.invoke(o);
        }
        long end = System.currentTimeMillis();
        System.out.println("反射方式代码执行时间:"+(end-start));
    }
}

 

 可以看到传统方式代码简洁而且速度快。

不过反射代码执行时间也可以优化一点点,如下所示

    public static void c3() throws Exception {
        Class aClass = Class.forName("Reflect.Student");
        Object o = aClass.newInstance();
        Method method = aClass.getMethod("sleep");
        method.setAccessible(true);//就是将JAVA的检查开关给关掉
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            method.invoke(o);
        }
        long end = System.currentTimeMillis();
        System.out.println("优化以后的反射方式代码执行时间:"+(end-start));
    }

代码执行时间对比

 Class对象扩展

在上面java反射机制原理图中,在加载阶段内存堆中有一个Class对象。只有通过这个Class对象,java才能反射,所以说是非常重要的存在 ,那哪些些数据类型包含Class对象呐?

1.外部类、成员内部类、静态内部类、局部内部类、匿名内部类

2.接口(接口是一种特殊的类)

3.数组(一维、二维、多维)

4.enum:枚举

5.annotation:注解

6.基本数据类型

7.void

import java.io.Serializable;

public class ClassObject {
    public static void main(String[] args) {
        Class<String> s1 = String.class;
        Class<Serializable> s2 = Serializable.class;
        Class<int[]> s3 = int[].class;
        Class<Deprecated> s4 = Deprecated.class;
        Class<Thread.State> s5 = Thread.State.class;
        Class<Void> s6 = void.class;
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
        System.out.println(s4);
        System.out.println(s5);
        System.out.println(s6);
    }
}

类加载机制

在java反射机制原理图中,我们知道Class对象将编译产生的.class文件通过加载器加载进内存的堆中的,这里就体现出类加载机制。

类加载机制:JVM通过文件系统将一系列.class文件转化为二进制流加载到JVM内存并生成一个该类的Class对象,为后面的程序运行提供资源的动作。

1.类加载的分类

  • 静态加载:编译期间就要加载相关的类,加载过程中,遇到没有的类它就会报错
  • 动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,也不会报错,用到了但有没有就会报错。

演示静态加载:

在本地的环境中用记事本写java程序,然后运行

上面我没有给Teacher这个类,但是在用户输入数字时输入的可能是1或者其他错误数字,也就是说没有执行2的操作,但编译期间就直接报错了。这就是静态加载,只要在编译时检查到没有相关的类就直接报错。

演示动态加载

 

上面是通过反射机制实现类的动态加载,生成了class类文件,说明编译通过了(也只是编译通过了)。动态加载是在运行期间加载的,而且只有当程序运行到相应代码位置的时候才会加载。

当输入2的时候,因为没有执行到case3,就不会报错。当输入3的时候,因为代码里面没有写Student这个类,所以就会报错 ,这就是动态加载。

2.类的加载时间 

  • 创建对象时(new)
  • 当子类被加载时,父类也会被加载
  • 调用类中的静态成员时
  • 反射

前三种属于静态加载,反射属于动态加载。

3.类加载过程和各阶段完成的任务

 (1)类加载流程图

(2)各阶段完成的任务

加载阶段 

JVM在这阶段的主要目的是将字节码从不同的数据源(可能是class文件、jar包等)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象

连接中的验证阶段

  • 确保Class文件的字节流中包含信息符合当前虚拟机的要求,并且不会危害虚拟机的安全
  • 验证文件格式(是否以模数oxcafebabe开头)、源数据、字节码和符号引用
  • 为了缩短虚拟机加载的时间,可以使用-Xverify:none参数来关闭大部分的验证措施

连接中的准备阶段

JVM对静态变量分配内存并默认初始化。这些变量所使用的内存都将在方法区中进行分配

class Test{
    public int n1 = 100;
    public static int n2 = 25;
    public static final int n3 = 1;
}

分析上面的n1、n2、n3;

n1是实例属性不是静态变量,所以在准备阶段,不会分配内存。

n2是静态变量,分配内存默认初始化为0

n3 static final 是常量,不是静态变量,常量一旦被赋值就不会改变,n3 = 30

连接中的解析阶段 

虚拟机将常量池的符号引用替换为直接引用的过程

解释:在加载过程中A类、B类还没有真正放入内存中,它们的引用是一种符号引用。当加载器将他们加载到内存后,JVM给他们分配类地址,它们才真正的变成直接能被操作的对象,也就是变成了直接引用。

初始化阶段

  • 到初始化阶段,才真正开始执行类中定义的java程序代码,此阶段是执行<clinit>()方法的过程
  • <clinit>()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
  • 虚拟机会保证一个类 的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。
public class ClassObject {
    public static void main(String[] args) {
        System.out.println(Test.value);
    }
}
class Test{
    static{
        System.out.println("测试静态代码块");
        value = 1;
    }
    static int value = 2;
    public Test(){
        System.out.println("Test()的构造器");
    }
}

猜你喜欢

转载自blog.csdn.net/yahid/article/details/125611981