Java反射学习

1、Java反射机制概述
1.1、静态 VS 动态语言:
  • 动态语言:
    是一类在运行时可以改变其结构的语言,例如:新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说:就是在运行时代码可以根据某些条件而改变自身结构的语言。
      主要动态语言:Object-C、C#、JavaScript、PHP、Python 等。

  • 静态语言:
     与动态语言相对应的,运行时结构不可变的语言就是静态语言。如:Java、C、C++。
     Java不是动态语言,但 Java 可以称之为 ”准动态语言“。即:Java 有一定的动态性,我们可以利用反射机制获得类似动态语言的特性。Java 的动态性让编程的时候更加灵活!

1.2、Java 反射概述:
  • 概述:
    (1). Reflection (反射) 时Java被视为动态语言的关键,反射机制允许程序在执行其借助 Reflection API 取得任何类的内部信息,并能直接操作任意内部信息,并能直接操作任意对象的内部属性以及方法。例如:通过如下手段获得一个类的内部信息:

    Class c = Class.forName("java.lang.String")
    

    (2). 加载完类之后,在堆内存的方法区就产生了一个 Class 类型的对象 (注意:该对象是类的Class 对象,每一个类只有一个Class对象,参考 Class 对象详解!)
    (3). 这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

  • Class 对象详解:
    实际上在Java中每个类都有一个Class对象,每当我们编写并且编译一个新创建的类就会产生一个对应Class的对象并且这个Class对象会被保存在同名.class文件里(编译后的字节码文件保存的就是Class对象),那为什么需要这样一个Class对象呢?是这样的,当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象。

    这里个人认为:我们通过反射获得就是这个类所对应的 Class 对象!

  • 正常使用类的对象的方式和反射使用该类的对象的方式对比:
    在这里插入图片描述

  • 使用反射的一个示例:
    (1). User类:

    class User{
    
        //封装
        //私有属性通过正常手段无法操作
        private int id;
        private int age;
        private String name;
    
        public User() {
        }
    
        public User(int id, int age, String name) {
            this.id = id;
            this.age = age;
            this.name = name;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", age=" + age +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    

    (2). 主类:(使用反射的方法来获得该类的对象!)

    
    public class Test2 extends Object {
        public static void main(String[] args) throws Exception {
            // 看着是死代码;是在运行的时候解析
            Class class1 = Class.forName("com.kuang.reflection.User"); // 这一句体现了反射的动态性,如果我们平常new 一个对象,如果该类不存在的话,我们连编译期的检查都过不去(IEDA就会提示报错!),但利用反射的这种写法,计算找不到相关的类,代码在编译器检查时没有问题的,IDEA也不会报错,只时在运行期的时候才会抛出错误,所以使用这种方法必须捕捉异常或者抛出异常!
            User user = (User) class1.newInstance();
            user.setAge(1);
            user.setId(1);
            user.setName("小多");
    
            System.out.println(user);
        }
    }
    -------------------
    输出:
    User{id=1, age=1, name='小多'}
    
1.3、Java 反射机制提供的相关功能:
  • 功能:
    (1). 在运行时判断任意一个对象所属的类。(对象.getClass()方法。)
    (2). 在运行时构造任意一个类的对象。(通过反射获得)
    (3). 在运行时判断任意一个类所具有的成员变量和方法 (通过反射获得类所对应的Class类对象,该对象中包含了很多有关于类的信息)
    (4). 在运行时获得泛型信息。(待补充!)
    (5). 在运行时调用任意一个对象的成员变量和方法。(通过反射可以获得代表该对象的类的Class对象,从而 new 出新的对象,然后就可以调用其成员变量和方法)
    (6). 在运行时处理注解。(利用反射机制可以获得该类的注解信息,通过读取注解信息中的相关数据,可以做的事情就更多了)
    (7). 生成动态代理。(待补充)
1.4、Java 反射机制的优点和缺点:
  • 优点:
     可以实现动态创建对象和编译,体现出很大的灵活性。
  • 缺点:
     对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总时慢于直接执行相同的操作。(比如:通过反射获得对象的速度就比直接new该类的对象慢的多,但这个过程却是动态的)
  • 问题:
    如果说Java反射机制创建对象和正常创建对象到底有什么不同?为何会导致它的速度变慢???
1.5、Java 反射相关的主要API:
API作用
java.lang.Class代表一个类
java.lang.reflect.Method代表类的方法
java.lang.reflect.Field代表类的成员变量
java.lang.reflect.Constructor代表类的构造器
其他功能
2、理解 Class 类并获取 Class 实例:
2.1、Class类的概述:
  • 概述:
     在Object类中定义了以下的方法,此方法将被所有子类继承
    public final Class getClass()
    
     解释:以上方法返回值的类型是一个Class类(而返回的结果是一个Class类的对象,该对象代表了对象所在的类 ----> 相对于Class 类来说,这些类都是一个个对象。),此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。如同下述的示意图:
    在这里插入图片描述
     在上述图示中,Person类和Animal类是我们定义的类,张三、李四、王五、小猫、小狗等都是我们自定义类的对象,而Person类和Animal类相当于是Class类的对象(这样的说法并不准确,但却能表述一些意思。)
2.2、Class类的进一步理解:
  • 对象照镜子后可以得到的信息:
    (1). 某个类的属性
    (2). 某个类的方法和构造器
    (3). 某个类到底实现了哪些接口。
    注意:对于每个类而言,JRE都为其保留了一个不变的 Class 类型的对象。一个 Class 对象包含了特定的某个结构(class/interface/enum/annotation/primitive type/void/[])的相关信息。

  • 重点:Class 类的一些关键性的概念
    (1). Class 本身也是一个类。
    (2). Class 对象只能由系统建立。(通过反射只是获取这个由系统已经创建好的对象!)
    (3). 一个加载的类在JVM中只会由一个 Class 实例(对象)。
    (4). 一个 Class 对象对应的是一个加载到 JVM 中的一个 .class 文件。
    (5). 每个类的实例都会记得自己是由哪个 Class 实例所生成。 (关键)
    (6). 通过 Class (对象)可以完整地得到一个类中的所有被加载的结构。
    (7). Class 类是Reflection 的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class 对象。

2.3、获取Class类的实例:
  • 若已知具体的类,通过类的 Class 属性获取,该方法最为安全可靠,程序性能最高。
    Class clazz = Person.class;
    
  • 已知某个类的实例,调用该实例的getClass()方法获取 Class 对象
    Class clazz = person.getClass();
    
  • 已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法 forName() 获取,该方法可能会抛出 ClassFoundException异常
    Class clazz = Class.forName("demo01.Student");
    
  • 内置基本数据类型可以直接用类名.Type。
  • 还可以利用 ClassLoader 我们之后讲解。
2.4、Class 类的常用方法:
  • 常用方法一览表
方法名功能说明
static ClassForName(String name)返回指定类名 name 的 Class 对象
Object newInstance()调用缺省构造函数,返回 Class 对象(即:一个类)的一个实例
getName()返回此 Class 对象所表示的实体(类,接口,数组类或 void )的名称。
Class getSuperClass()返回当前 Class 对象的父类 Class 对象
Class[] getInterfaces()获取当前 Class 对象的接口
ClassLoader getClassLoader()返回该类的类加载器
Constructor[] getConstructors()返回一个包含某些 Constructors 对象的数组
Constructor[] getConstructor(Class<?>… parameterTypes)返回一个包括固定参数的类的构造器
Method getMethods()返回一个包含该类所有方法的结果数组
Method getMethod(String name, Class<?>… parameterTypes)返回一个 Method 对象,此对象的形参类型为 paramType
Field[] getDeclaredFields()返回 Field 对象的一个数组 (返回全部的属性,可以获得私有的)
Field[] getDeclaredField(String name)返回某一个指定的 Field 对象 (返回全部的属性,可以获得私有的)
  • 示例:
    (1). User 类的代码:
    class User{
    
        //封装
        //私有属性通过正常手段无法操作
        private int id;
        private int age;
        private String name;
    
        public User() {
        }
    
        public User(int id, int age, String name) {
            this.id = id;
            this.age = age;
            this.name = name;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", age=" + age +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    
    (2). 反射常用函数使用的代码:
    //获得类的信息
    public class Test6 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            Class class1 = Class.forName("com.kuang.reflection.User"); //user 的 Class 对象,镜子,反射
    
            System.out.println("===========================================");
    
            System.out.println(class1.getName()); //全类名  包名 + 类名
            System.out.println(class1.getSimpleName()); //类名
    
            //获得字段 : get、set!
            System.out.println("===========================================");
            Field[] fields = class1.getFields(); //只能获得public的属性
            System.out.println(fields.length);
    
            Field[] fields2 = class1.getDeclaredFields(); //返回全部的属性,可以获得私有的
            System.out.println(fields2.length);
    
            for (Field field : fields2) {
                System.out.println("field=>\t"+field);
            }
    
            Field name = class1.getDeclaredField("name"); //获取指定的字段!
            System.out.println(name);
    
            //获得方法 : 执行 invoke
            System.out.println("===========================================");
    
            Method[] methods = class1.getMethods(); //返回当前类和被继承的类的public方法
            System.out.println(methods.length);
    
            for (Method method : methods) {
                System.out.println("methods: "+method);
            }
    
            Method[] declaredMethods = class1.getDeclaredMethods(); //获得当前类的所有方法
            System.out.println(declaredMethods.length);
    
            for (Method method : declaredMethods) {
                System.out.println("declaredMethods: "+method);
            }
    
            System.out.println("=====");
            //如果只获得方法的名字就会有问题: 重载!(参数类型)
            Method setName = class1.getMethod("setName",String.class);
            Method setAge = class1.getMethod("setAge",int.class);
            System.out.println(setName);
            System.out.println(setAge);
    
            //获得构造器 : 创建对象  newInstance()
            System.out.println("===========================================");
            Constructor[] constructors = class1.getConstructors();  //获得所有public的构造器
            System.out.println(constructors.length);
            for (Constructor constructor : constructors) {
                System.out.println(constructor);
            }
    
            Constructor[] constructors2 = class1.getDeclaredConstructors();  //获得所有public的构造器
            System.out.println(constructors2.length);
            for (Constructor constructor : constructors2) {
                System.out.println(constructor);
            }
    
            System.out.println("====================");
    
            //获得指定的构造器: 重载,只能通过参数名判断  null
            Constructor constructor = class1.getConstructor(null);
            Constructor constructor2 = class1.getConstructor(int.class,int.class,String.class);
            System.out.println(constructor);
            System.out.println(constructor2);
    
        }
    }
    ------------------
    输出:
    ===========================================
    com.kuang.reflection.User
    User
    ===========================================
    0
    3
    field=>	private int com.kuang.reflection.User.id
    field=>	private int com.kuang.reflection.User.age
    field=>	private java.lang.String com.kuang.reflection.User.name
    private java.lang.String com.kuang.reflection.User.name
    ===========================================
    15
    methods: public java.lang.String com.kuang.reflection.User.toString()
    methods: public java.lang.String com.kuang.reflection.User.getName()
    methods: public int com.kuang.reflection.User.getId()
    methods: public void com.kuang.reflection.User.setName(java.lang.String)
    methods: public void com.kuang.reflection.User.setId(int)
    methods: public void com.kuang.reflection.User.setAge(int)
    methods: public int com.kuang.reflection.User.getAge()
    methods: public final void java.lang.Object.wait() throws java.lang.InterruptedException
    methods: public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
    methods: public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
    methods: public boolean java.lang.Object.equals(java.lang.Object)
    methods: public native int java.lang.Object.hashCode()
    methods: public final native java.lang.Class java.lang.Object.getClass()
    methods: public final native void java.lang.Object.notify()
    methods: public final native void java.lang.Object.notifyAll()
    7
    declaredMethods: public java.lang.String com.kuang.reflection.User.toString()
    declaredMethods: public java.lang.String com.kuang.reflection.User.getName()
    declaredMethods: public int com.kuang.reflection.User.getId()
    declaredMethods: public void com.kuang.reflection.User.setName(java.lang.String)
    declaredMethods: public void com.kuang.reflection.User.setId(int)
    declaredMethods: public void com.kuang.reflection.User.setAge(int)
    declaredMethods: public int com.kuang.reflection.User.getAge()
    =====
    public void com.kuang.reflection.User.setName(java.lang.String)
    public void com.kuang.reflection.User.setAge(int)
    ===========================================
    2
    public com.kuang.reflection.User()
    public com.kuang.reflection.User(int,int,java.lang.String)
    2
    public com.kuang.reflection.User()
    public com.kuang.reflection.User(int,int,java.lang.String)
    ====================
    public com.kuang.reflection.User()
    public com.kuang.reflection.User(int,int,java.lang.String)
    
2.5、哪些类可以有 Class 对象:
  • 列举:
    (1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类。
    (2)interface:接口
    (3)[]:数组
    (4)enum:枚举 (Java的枚举是什么?)
    (5)annotation:注解@interface
    (6)primitive type:基本数据类型
    (7)void

  • 示例:

public class Test4 {
    public static void main(String[] args) {

        Class c1 = Object.class; 		//class
        Class c2 = Comparable.class; 	//接口
        Class c3 = String[].class; 		// 数组
        Class c4 = String[][].class; 	// 二维数组
        Class c5 = ElementType.class; 	//枚举类型
        Class c6 = Override.class; 		// 注解
        Class c7 = Integer.class; 		//基本数据类型
        Class c8 = void.class; 			//void
        Class c9 = Class.class; 		//反射对象本身

        //数组中,和长度无关,只要是一个维度并且类型相同,就是同一个class
        int[] a = new int[10];
        String[] b = new String[100];

        Class c10 = a.getClass();
        Class c11 = b.getClass();

        System.out.println(c10);
        System.out.println(c11);

        System.out.println(c10==c11);


        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
        System.out.println(c4);
        System.out.println(c5);
        System.out.println(c6);
        System.out.println(c7);
        System.out.println(c8);
        System.out.println(c9);

    }
}
--------------
输出:
class [I
class [Ljava.lang.String;
false
class java.lang.Object
interface java.lang.Comparable
class [Ljava.lang.String;
class [[Ljava.lang.String;
class java.lang.annotation.ElementType
interface java.lang.Override
class java.lang.Integer
void
class java.lang.Class
2.6、正常new对象和反射获得对象到底有什么本质性的区别?(待解决!):
3、Java类的加载:
3.1、Java内存分析:

在这里插入图片描述

3.2、类的加载过程:
  • 概念:
     类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括了:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称链接。
    在这里插入图片描述
3.2.1 加载:(重点)
  • 概念:
     加载阶段是“类加载机制”中的一个阶段,这个阶段通常也被称作“装载”,主要完成:

    1. 通过“类全名”来获取定义此类的二进制字节流。
    2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
    3. 在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口
  • 相对于类加载过程的其他阶段,加载阶段(准备地说,是加载阶段中获取类的二进制字节流的动作)是开发期可控性最强的阶段,因为加载阶段可以使用系统提供的类加载器(ClassLoader)来完成,也可以由用户自定义的类加载器完成,开发人员可以通过定义自己的类加载器去控制字节流的获取方式。

  • 加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式有虚拟机实现自行定义,虚拟机并未规定此区域的具体数据结构。然后在java堆中实例化一个java.lang.Class类的对象,这个对象作为程序访问方法区中的这些类型数据的外部接口。

  • 简单来说:加载,是指Java虚拟机查找字节流(查找.class文件),并且根据字节流创建java.lang.Class对象的过程。这个过程,将类的.class文件中的二进制数据读入内存,放在运行时区域的方法区内。然后在堆中创建java.lang.Class对象,用来封装类在方法区的数据结构。
    (1)Java虚拟机将.class文件读入内存,并为之创建一个Class对象。

    (2)任何类被使用时系统都会为其创建一个且仅有一个Class对象。

    (3)这个Class对象描述了这个类创建出来的对象的所有信息,比如有哪些构造方法,都有哪些成员方法,都有哪些成员变量等。

3.2.2 验证:(了解)
  • 概念:验证是链接阶段的第一步,这一步主要的目的是确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。
  1. 文件格式验证
     验证class文件格式规范,例如: class文件是否已魔术0xCAFEBABE开头 , 主、次版本号是否在当前虚拟机处理范围之内等

  2. 元数据验证
     这个阶段是对字节码描述的信息进行语义分析,以保证起描述的信息符合java语言规范要求。验证点可能包括:这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类)、这个类是否继承了不允许被继承的类(被final修饰的)、如果这个类的父类是抽象类,是否实现了起父类或接口中要求实现的所有方法。

  3. 字节码验证
     进行数据流和控制流分析,这个阶段对类的方法体进行校验分析,这个阶段的任务是保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。如:保证访法体中的类型转换有效,例如可以把一个子类对象赋值给父类数据类型,这是安全的,但不能把一个父类对象赋值给子类数据类型、保证跳转命令不会跳转到方法体以外的字节码命令上。

  4. 符号引用验证
     符号引用中通过字符串描述的全限定名是否能找到对应的类、符号引用类中的类,字段和方法的访问性(private、protected、public、default)是否可被当前类访问。

3.2.3 准备:(了解)
  • 概念:
     准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。这个阶段中有两个容易产生混淆的知识点:
    (1)首先是这时候进行内存分配的仅包括类变量(static 修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中
    (2)其次是这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量定义为:

    public static int value  = 12;
    

    那么变量value在准备阶段过后的初始值为0而不是12,因为这时候尚未开始执行任何java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器()方法之中,所以把value赋值为12的动作将在初始化阶段才会被执行。

  • 注意:
     上面所说的“通常情况”下初始值是零值,那相对于一些特殊的情况,如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为ConstantValue属性所指定的值,建设上面类变量value定义为:

    public static final int value = 123;
    

    编译时javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value设置为123。

3.2.4 解析:(了解)
  • 概念:
     解析阶段是虚拟机常量池内的符号引用替换为直接引用的过程。

    (1)符号引用: 符号引用是一组符号来描述所引用的目标对象(例如:变量名),符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标对象并不一定已经加载到内存中。

    (2)直接引用: 可以是直接指向目标对象的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机内存布局实现相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用,那引用的目标必定已经在内存中存在。

  • 注意:
    虚拟机规范并没有规定解析阶段发生的具体时间,只要求了在执行anewarry、checkcast、getfield、instanceof、invokeinterface、invokespecial、invokestatic、invokevirtual、multianewarray、new、putfield和putstatic这13个用于操作符号引用的字节码指令之前,先对它们使用的符号引用进行解析,所以虚拟机实现会根据需要来判断,到底是在类被加载器加载时就对常量池中的符号引用进行解析,还是等到一个符号引用将要被使用前才去解析它。

     解析的动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行。分别对应编译后常量池内的CONSTANT_Class_Info、CONSTANT_Fieldref_Info、CONSTANT_Methodef_Info、CONSTANT_InterfaceMethoder_Info 四种常量类型。

    1.类、接口的解析

    2.字段解析

    3.类方法解析

    4.接口方法解析

3.2.5 初始化:(了解)
  • 概念:
    类的初始化阶段是类加载过程的最后一步,在准备阶段,类变量已赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器()方法的过程。在以下四种情况下初始化过程会被触发执行:
  1. 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需先触发其初始化。生成这4条指令的最常见的java代码场景是:使用new关键字实例化对象、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用类的静态方法的时候。

  2. 使用java.lang.reflect包的方法对类进行反射调用的时候。

  3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先出发其父类的初始化。

  4. jvm启动时,用户指定一个执行的主类(包含main方法的那个类),虚拟机会先初始化这个类。

  • 例如:
     在上面准备阶段 public static int value = 12; 在准备阶段完成后 value的值为0,而在初始化阶调用了类构造器()方法,这个阶段完成后value的值为12。

  • 注意:
    (1). 类构造器()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句快可以赋值,但是不能访问。

    (2). 类构造器()方法与类的构造函数(实例构造函数()方法)不同,它不需要显式调用父类构造,虚拟机会保证在子类()方法执行之前,父类的()方法已经执行完毕。因此在虚拟机中的第一个执行的()方法的类肯定是java.lang.Object。

    (3). 由于父类的()方法先执行,也就意味着父类中定义的静态语句快要优先于子类的变量赋值操作。

    (4). <clinit >()方法对于类或接口来说并不是必须的,如果一个类中没有静态语句,也没有变量赋值的操作,那么编译器可以不为这个类生成<clinit>()方法。

    (5). 接口中不能使用静态语句块,但接口与类不太能够的是,执行接口的()方法不需要先执行父接口的()方法。只有当父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始化时也一样不会执行接口的()方法。

    (6). 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步,如果多个线程同时去初始化一个类,那么只会有一个线程执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。如果一个类的()方法中有耗时很长的操作,那就可能造成多个进程阻塞。

3.3、什么时候会发生类的初始化:
  • 类的主动引用(一定会发生类的初始化)
    (1)当虚拟机启动,先初始化main方法所在的类
    (2)new 一个类的对象
    (3)调用类的静态成员(除了final常量)和静态方法
    (4)使用 java.lang.reflect 包的方法对类进行反射调用
    (5)当初始化一个类,如果其父类没有被初始化,则会先初始化它的父类

  • 类的被动引用(不会发生类的初始化)
    (1)当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量时,不会导致子类的初始化。
    (2)通过数组定义类的引用,不会触发此类的初始化
    (3)引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)

3.4、类加载器的作用:
  • 类加载器的作用:
      将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时 数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。

  • 类缓存:
      标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
    在这里插入图片描述

  • 类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下类型的类的加载器:
    在这里插入图片描述

4、反射的使用:
4.1、获取运行时类的完整结构:
  • 通过反射获取运行时类的完整结构:(Field、Method、Constructor、Superclass、Interface、Annotation)

  • 方法使用表:

功能方法
获得代表类的 Class 对象Class class1 = Class.forName(“com.kuang.reflection.User”)
实现的全部接口class1.getInterfaces()
所继承的父类class1.getSuperclass()
全部的构造器class1.getConstructors()
全部的方法class1.getDeclaredMethods()
全部的Field(包括私有的)class1.getDeclaredFields()
注解getAnnotations()
其他
  • 小结:
    (1). 在实际的操作中,取得类的信息的操作代码,并不会经常开发。
    (2). 一定要熟悉 java.lang.reflect 包的作用,反射机制。
    (3). 如何取得属性、方法、构造器名称,修饰符等等。
4.2、有了Class对象,能做什么?:
  • 创建类的对象:调用 Class 对象的newInstance()方法

    (1). 类必须有一个无参数的构造器。
    (2). 类的构造器的访问权限需要足够。

  • 问题:难道没有无参构造器就不能通过反射创建对象了吗?
    回答:当然不是这样。上述的 newInstance() 方法仅适用于创建无参构造的对象。创建带有构造参数的对象只要在操作的时候明确调用类中的构造器,并将参数传递进去之后,才可以实例化操作。

  • 步骤如下:
    (0). 通过 Class 类的 getConstructors()方法取得所有的构造器。
    (1). 通过 Class 类的 getDeclaredConstructor(Class …parameterTypes) 取得本类的指定形参类型的构造器
    (2). 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
    (3). 通过Constructor 实例化对象。

    示例:

    public class Test6 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            Class class1 = Class.forName("com.kuang.reflection.User"); //user 的 Class 对象,镜子,反射
            //获得构造器 : 创建对象  newInstance()
            System.out.println("===========================================");
            Constructor[] constructors = class1.getConstructors();  //获得所有public的构造器
            System.out.println(constructors.length);
            for (Constructor constructor : constructors) {
                System.out.println(constructor);
            }
    
            Constructor[] constructors2 = class1.getDeclaredConstructors();  //获得所有public的构造器
            System.out.println(constructors2.length);
            for (Constructor constructor : constructors2) {
                System.out.println(constructor);
            }
    
            System.out.println("====================");
    
            //获得指定的构造器: 重载,只能通过参数名判断  null
            Constructor constructor = class1.getConstructor(null);
            Constructor constructor2 = class1.getConstructor(int.class,int.class,String.class);
            System.out.println(constructor);
            System.out.println(constructor2);
            
    		System.out.println("====================>>>>>");
    		// 利用获得的构造器来创建对象:
            User user1 = (User) constructor.newInstance();
            System.out.println(user1);
            User user2 = (User) constructor2.newInstance(10,25,"Test");
            System.out.println(user2);
        }
    }
    ------------
    输出:
    ===========================================
    2
    public com.kuang.reflection.User()
    public com.kuang.reflection.User(int,int,java.lang.String)
    2
    public com.kuang.reflection.User()
    public com.kuang.reflection.User(int,int,java.lang.String)
    ====================
    public com.kuang.reflection.User()
    public com.kuang.reflection.User(int,int,java.lang.String)
    ====================>>>>>
    User{id=0, age=0, name='null'}
    User{id=10, age=25, name='Test'}
    
4.3、调用指定方法:
  • 概念:
    通过反射,调用类中的方法,通过 Method 类完成:

  • 方法:
    (0). 通过 Class 类的 getMethods() 方法取得所有的方法。
    (1). 通过 Class 类的 getMethod(String name,Class … parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
    (2). 之后使用 Object invoke (Object obj, Object[] args) 进行调用,并向方法中传递设置的 obj 对象的参数信息。
    在这里插入图片描述

  • 注意:

    Object invoke (Object obj, Object[] args) 
    

    (1). Object 对应原方法的返回值,若原方法无返回值,此时返回 null。
    (2). 若原方法为静态方法,此时形式参数 Object obj 可以为 null。
    (3). 若原方法形参列表为空,则 Object[] args 为 null。
    (4). 若原方法声明为 private ,则需要在调用 invoke() 方法前,显式调用方法对象的 serAccessible(true)方法,将可访问 private 的方法。

  • 示例:

    public class Test6 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            Class class1 = Class.forName("com.kuang.reflection.User"); //user 的 Class 对象,镜子,反射
    
            //获得方法 : 执行 invoke
            System.out.println("===========================================");
    
            Method[] methods = class1.getMethods(); //返回当前类和被继承的类的public方法
            System.out.println(methods.length);
    
            for (Method method : methods) {
                System.out.println("methods: "+method);
            }
    
            Method[] declaredMethods = class1.getDeclaredMethods(); //获得当前类的所有方法
            System.out.println(declaredMethods.length);
    
            for (Method method : declaredMethods) {
                System.out.println("declaredMethods: "+method);
            }
    
            System.out.println("=====");
            //如果只获得方法的名字就会有问题: 重载!(参数类型)
            Method setName = class1.getMethod("setName",String.class);
            Method setAge = class1.getMethod("setAge",int.class);
            System.out.println(setName);
            System.out.println(setAge);
    		
    		System.out.println("=====>>>>>>>>>>>>");
            User user = (User)class1.newInstance();
            System.out.println("原名称为:"+user.getName());
            setName.invoke(user,"xiaobo!");
            System.out.println("现名称为:"+user.getName());
    
        }
    }
    -----------------------------
    输出:
    ===========================================
    15
    methods: public java.lang.String com.kuang.reflection.User.toString()
    methods: public java.lang.String com.kuang.reflection.User.getName()
    methods: public int com.kuang.reflection.User.getId()
    methods: public void com.kuang.reflection.User.setName(java.lang.String)
    methods: public void com.kuang.reflection.User.setAge(int)
    methods: public int com.kuang.reflection.User.getAge()
    methods: public void com.kuang.reflection.User.setId(int)
    methods: public final void java.lang.Object.wait() throws java.lang.InterruptedException
    methods: public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
    methods: public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
    methods: public boolean java.lang.Object.equals(java.lang.Object)
    methods: public native int java.lang.Object.hashCode()
    methods: public final native java.lang.Class java.lang.Object.getClass()
    methods: public final native void java.lang.Object.notify()
    methods: public final native void java.lang.Object.notifyAll()
    7
    declaredMethods: public java.lang.String com.kuang.reflection.User.toString()
    declaredMethods: public java.lang.String com.kuang.reflection.User.getName()
    declaredMethods: public int com.kuang.reflection.User.getId()
    declaredMethods: public void com.kuang.reflection.User.setName(java.lang.String)
    declaredMethods: public void com.kuang.reflection.User.setAge(int)
    declaredMethods: public int com.kuang.reflection.User.getAge()
    declaredMethods: public void com.kuang.reflection.User.setId(int)
    =====
    public void com.kuang.reflection.User.setName(java.lang.String)
    public void com.kuang.reflection.User.setAge(int)
    =====>>>>>>>>>>>>
    原名称为:null
    xiaobo!
    现名称为:xiaobo!
    
4.4、setAccessible:
  • 概念:Method和Field、Constructor 对象都有 setAccessible()方法。

  • 作用:setAccessible 作用是启动和禁用访问安全检查的开关。

  • 参数:
    a. 参数值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
    (1). 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
    (2). 使得原本无法访问的私有成员也可以访问。
    b. 参数值为:false 则指示反射的对象应该实施 Java 语言访问检查。

  • 示例:

    //通过反射创建对象!执行方法,修改字段!  容器拿 bean
    public class Test7 {
        public static void main(String[] args) throws Exception {
    
            Class c1 = Class.forName("com.kuang.reflection.User");
            //创建对象,new  无参,有参!
            System.out.println("==========================================");
            User user1 = (User) c1.newInstance(); //创建对象!
            System.out.println(user1); //默认调用的是无参构造器,你们可以删掉无参试试!  new User();
    
            System.out.println("==========================================");
            //通过指定构造器创建对象! new User(1, 3, "kuangshen");
            Constructor declaredConstructor = c1.getDeclaredConstructor(int.class, int.class, String.class);
            User user2 = (User) declaredConstructor.newInstance(1, 3, "小丽");
            System.out.println(user2);
    
            System.out.println("==========================================");
            User user3 = (User) c1.newInstance();
             // user3.setName("秦疆"); 正常操作
            //1. 获得你要操作的方法
            Method setName = c1.getDeclaredMethod("setName", String.class);
            //2. 通过invoke方法执行方法
            //一个class可能存在多个对象, 这个方法需要找到是那个对象使用的,给他赋值;
            setName.invoke(user3,"小明");
            System.out.println(user3.getName());
    
            //获得字段
            System.out.println("==========================================");
            User user4  = (User) c1.newInstance();
            Field name = c1.getDeclaredField("name"); //反射无法直接破坏私有的
    
            //显示调用setAccessible为true,则可以访问private方法!
            name.setAccessible(true);
            name.set(user4,"小刚");
            System.out.println(user4.getName());
    
        }
    }
    -------------
    输出:
    ==========================================
    User{id=0, age=0, name='null'}
    ==========================================
    User{id=1, age=3, name='小丽'}
    ==========================================
    小明
    ==========================================
    小刚
    
  • Java 反射操作的速度差异化示例:

    //分析性能问题
    public class Test8 {
    
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
            test3();
        }
    
        public static void test1(){
            User user = new User();
            long start = System.currentTimeMillis();
    
            for (int i = 0; i < 1000000000L ; i++) {
                user.getName();
            }
    
            long end = System.currentTimeMillis();
            System.out.println("普通方法调用执行10亿次:"+(end-start)+"ms"); //558ms
        }
    
        public static void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            User user = new User();
            Class class1 = user.getClass();
            Method getName = class1.getDeclaredMethod("getName", null);
    
            //开启检测的! false
            long start = System.currentTimeMillis();
    
            for (int i = 0; i < 1000000000L ; i++) {
                getName.invoke(user,null);
            }
    
            long end = System.currentTimeMillis();
            System.out.println("反射方法调用执行10亿次:"+(end-start)+"ms"); //3883ms
        }
    
        public static void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            User user = new User();
            Class class1 = user.getClass();
            Method getName = class1.getDeclaredMethod("getName", null);
    
            getName.setAccessible(true); //关闭检查
    
            long start = System.currentTimeMillis();
    
            for (int i = 0; i < 1000000000L ; i++) {
                getName.invoke(user,null);
            }
    
            long end = System.currentTimeMillis();
            System.out.println("反射方法调用执行10亿次:"+(end-start)+"ms"); //2400ms
        }
    }
    

    小结:说明了在利用反射机制的时候,类型安全检查与否是很影响速度的,关闭类型安全检查会提升反射操作的运行速度。但是,关闭了安全检查还是没有直接使用来的快,我个人怀疑是跟动态编译和静态编译的速度有关!

4.5、反射操作泛型:(了解)
  • Java 泛型的概念:
     Java 采用泛型擦除机制来引入泛型,Java 中的泛型仅仅是编译器 javac 使用的,确保数据的安全性和免去强制类型转换问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除。

  • 如何通过反射来操作泛型?
     为了通过反射操作这些类型,Java 新增了 ParameterizedType,GenericArrayType,TypeVariable 和 WildcardType 几种类型来代表不能被归一化到 Class 类中的类型但是又和原始类型齐名的类型。

  • 反射泛型类型一览表:

类型作用
ParamerizedType表示一种参数化类型,比如:Collection
GenericArrayType表示一种元素类型是参数化类型或者类型变量的数组类型
TypeVariable是各种类型变量的公共父接口
WildcardType代表一种通配符类型表达式
  • 示例:
    public class Test9 {
    
        public void test(Map<String,User> map, List<User> list){
            System.out.println("test01");
        }
    
        public Map<Integer,User> test02(){
            System.out.println("test02");
            return null;
        }
    
        public static void main(String[] args) throws NoSuchMethodException {
            //获得指定的方法的参数泛型信息
            Method method = Test9.class.getMethod("test", Map.class, List.class);
            //获得方法的参数
            Type[] genericParameterTypes = method.getGenericParameterTypes();
            //遍历
            for (Type genericParameterType : genericParameterTypes) {
                System.out.println("#"+ genericParameterType);
                if (genericParameterType instanceof ParameterizedType){ //如果这个参数的类型是泛型
                    Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                    for (Type actualTypeArgument : actualTypeArguments) {
                        System.out.println("泛型类型:" + actualTypeArgument);
                    }
                }
            }
    
            //获得指定的方法的返回值泛型信息
            Method test02 = Test9.class.getDeclaredMethod("test02", null);
            Type genericReturnType = test02.getGenericReturnType();  //获得返回值类型
    
            //获得返回值的类型!
            if (genericReturnType instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println("返回值泛型类型="+ actualTypeArgument);
                }
            }
    
        }
    
    }
    ---------
    输出:
    #java.util.Map<java.lang.String, com.kuang.reflection.User>
    泛型类型:class java.lang.String
    泛型类型:class com.kuang.reflection.User
    #java.util.List<com.kuang.reflection.User>
    泛型类型:class com.kuang.reflection.User
    返回值泛型类型=class java.lang.Integer
    返回值泛型类型=class com.kuang.reflection.User
    
4.6、反射操作注解:
  • getAnnotations:获取所有的注解!

  • getAnnotation:获取某一个注解!

  • 示例:(自定义注解并获取!)

    //自定义注解
    
    import java.lang.annotation.*;
    import java.lang.reflect.Method;
    
    public class Test3 {
    
        private int age;
    
        @MyAnnotation3("aaa")
        public int getAge() {
            return age;
        }
    
        @MyAnnotation3("bbbb")
        @MyAnnotation2(schools = "NWU")
        public int AnnotationTest(){
            return 0;
        }
    
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
            Class class1 = Class.forName("com.kuang.annotation.Test3");
            Method getAge = class1.getMethod("getAge");
            Annotation[] annotations = getAge.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(annotation);
            }
    
            System.out.println("===============>");
    
            Annotation annotation = getAge.getAnnotation(MyAnnotation3.class);
            String[] value = ((MyAnnotation3) annotation).value();
            for (String s : value) {
                System.out.println(s);
            }
    
            System.out.println("=================>");
            Method annotationTest = class1.getMethod("AnnotationTest");
            Annotation[] annotations1 = annotationTest.getAnnotations();
            for (Annotation annotation1 : annotations1) {
                System.out.println(annotation1);
            }
    
            System.out.println("=============>>>>>>>>");
            MyAnnotation2 annotation1 = annotationTest.getAnnotation(MyAnnotation2.class);
            System.out.println(annotation1.name());
            System.out.println(annotation1.age());
            System.out.println(annotation1.id());
            String[] schools = annotation1.schools();
            for (String school : schools) {
                System.out.println(school);
            }
    
        }
    }
    
    
    @Target(value={ElementType.METHOD})
    @Retention(value = RetentionPolicy.RUNTIME)
    @interface MyAnnotation3{
    
        String[] value(); //只有一个参数的一般名字叫做value, 可以省略!
    
    }
    
    @Target(value={ElementType.METHOD})
    @Retention(value = RetentionPolicy.RUNTIME)
    @interface MyAnnotation2{
    
        String name() default "";
        int age() default 0;
        int id() default -1; // String indexOf("abc")   -1, 找不到,不存在
    
        String[] schools();
    
    }
    -------
    输出:
    @com.kuang.annotation.MyAnnotation3(value=[aaa])
    ===============>
    aaa
    =================>
    @com.kuang.annotation.MyAnnotation3(value=[bbbb])
    @com.kuang.annotation.MyAnnotation2(name=, age=0, id=-1, schools=[NWU])
    =============>>>>>>>>
    
    0
    -1
    NWU
    

    小结:
    (1). 注解的获取可以针对某个字段,类,或者方法。
    (2). 通过反射获得的注解值的类型取决于注解的定义,当定义为数组时,则以数组的形式获得结果。
    (3). 在不清楚有什么注解修饰该 Field/Method 的情况下,利用getAnnotations() 获取所有的返回值是一个不错的方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值