注解和反射

注解 Annotation

本质:

注解本质上就是一个接口,该接口默认继承 Annotation 接口

属性:接口中的抽象方法

  • 属性的返回值类型有下列取值
    • 基本数据类型
    • string
    • 枚举
    • 注解
    • 以上类型的数组
  • 定义了属性,在使用时需要给属性赋值
    • 如果定义属性时,使用 default 关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
    • 如果只有一个属性需要赋值,并且属性的名称是 value ,则 value 可以省略,直接定义值即可。
    • 数组赋值时,值使用 { } 包裹。如果数组中只有一个值,则 { } 省略。

Annotation的作用:

不是程序本身,可以对程序作出解释。(这一点和注释(comment)没什么区别)可以被其他程序(比如:编译器等)读取。

Annotation的格式:

注解是以“@注释名”在代码中存在的,还可以添加一些参数值,例如:@SuppressWarnings(value="unchecked")。

Annotation在哪里使用?

可以附加在package,class,method,field等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问。

内置注解 test01.java

  • @Override 重写
  • @Deprecated 不推荐使用,但是可以运行
  • @SuppressWarnings("all") 镇压警告

@override:定义在java.lang.Override中,此注释只适用于修辞方法,表示一个方法声明打算
重写超类中的另一个方法声明.

@Deprecated:定义在java.lang.Deprecated中,此注释可以用于修辞方法,属性,类,表示不
鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择.

@SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息.
口与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数都是已经定义好了的,
我们选择性的使用就好了

  • @SuppressWarnings("all")
  • @SuppressWarnings("unchecked")
  • @SuppressWarnings(value={"unchecked","deprecation"})
  • 等等……

元注解(meta-annotation) test02.java

负责注解其他注解

@Target 用于描述注解的适用范围(描述的注解可以用在什么地方)

@Retention 表示需要在什么级别保存该注释信息,用于描述注释的生命周期 (SOURCE < CLASS < RUNTIME)

@Documented 说明该注解将被包含在javadoc 中

@Inherited 说明子类可以继承父类中的该注解

// @Target
@Target(value = {ElementType.TYPE})					// TYPE:可作用于类上
@Target(value = {ElementType.FIELD})				// METHOD:可以作用于方法上
@Target(value = {ElementType.METHOD})				// FIELD:可以作用于成员变量上
@Target(value = {ElementType.PARAMETER})			// 
@Target(value = {ElementType.CONSTRUCTOR})
@Target(value = {ElementType.LOCAL_VARIABLE})

// @Retention 
// SOURCE < CLASS < RUNTIME
@Retention(RetentionPolicy.RUNTIME)			// 当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
@Retention(RetentionPolicy.CLASS)			// 当前被描述的注解,会保留到class字节码文件中
@Retention(RetentionPolicy.SOURCE)			// 当前被描述的注解
// @MyAnnotation    // 测试 @Target ,要加在此处,需要在Target value 中增加 ElementType.TYPE
public class Test02 {
    // 测试 @Target
    @MyAnnotation
    public void test(){
    }
}
// @Target  用于描述注解的适用范围(描述的注解可以用在什么地方)
@Target(value = {ElementType.METHOD})  // METHOD 可以加在方法上

// @Retention  表示需要在什么级别保存该注释信息,用于描述注释的生命周期
@Retention(value = RetentionPolicy.RUNTIME)     // RUNTIME > class > source

// @Documented  说明该注解将被包含在javadoc 中
@Documented

// @Inherited  说明子类可以继承父类中的该注解
@Inherited

// @interface 自定义注解
@interface MyAnnotation{    // 上述注解使用哪一个都需要加这里
}

@interface 自定义注解 test03.java

  • @interface用来声明一个注解,格式:public @ interface注解名{定义内容}
  • 其中的每一个方法实际上是声明了一个配置参数
  • 方法的名称就是参数的名称
  • 返回值类型就是参数的类型(返回值只能是基本类型,Class,String,enum)
  • 可以通过default来声明参数的默认值
  • 如果只有一个参数成员,一般参数名为value
  • 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值
package annotate;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/*
    自定义注解
 */
public class Test03 {
    // 注解可以显示赋值,如果没有默认值,就必须赋值
    @MyAnnotation2(age = 24, name = "Lee")
    public void test() {
    }
}
//----------------------------------------------------------------------------------
@Target(value = {ElementType.METHOD, ElementType.TYPE})     // "value=" 可省略, 若为其他则不可省略
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2 {
    // 注解参数形式: 参数类型 参数名 ()
    String name() default "Kite";
    int age();
    int id () default -1;   // 如果默认值为 -1 代表不存在
    String[] schools() default {"SDUST", "DMU"};     // []
}

@Target(value = {ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3 {
    String value();
}

案例

public class Demo01 {
    public void show() {
        System.out.println("Demo01...show...");
    }
}
package practice;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 描述需要执行的类名和方法名
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
    String className();
    String methodName();
}
package practice;

@Pro(className = "practice.Demo01",methodName = "show")
public class ReflectTest {
    public static void main(String[] args) {
        /*
            前提: 不能改变该类的任何代码,可以创建任意类的对象,可以执行任意方法
         */
        // 获取该类的字节码文件对象
        Class<ReflectTest> reflectTestClass = ReflectTest.class;
        // 获取上边的注解对象
        // 其实就是在内存中生成一个该注解接口的子类实现对象

        /* 相当于

            public class ProImpl implements Pro {
                public String className() {
                    return "practice.Demo01";
                }
                public String methodName() {
                    return "show";
                }
            }
         */
        Pro an = reflectTestClass.getAnnotation(Pro.class);
        // 调用注解对象中定义的抽象方法,获取返回值
        String className = an.className();
        String methodName = an.methodName();
        System.out.println(className);
        System.out.println(methodName);
    }
}

简单的测试框架案例

package practice.demo02;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}
package practice.demo02;
public class Calculator {
    //加法
    @Check
    public void add() {
        System.out.println("1 + 0 =" + (1 + 0));
    }
    //减法
    @Check
    public void sub() {
        System.out.println("1 - 0 =" + (1 - 0));
    }
    //乘法
    @Check
    public void mul() {
        System.out.println("1 * 0 =" + (1 * 0));
    }
    //除法
    @Check
    public void div() {
        System.out.println("1 / 0 =" + (1 / 0));
    }
    public void show(){
        System.out.println("永无bug...");
    }
}
package practice.demo02;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 简单的测试框架
 *
 * 当主方法执行后,会自动执行被检测的所有方法(加 @Check 注解的方法)
 */
public class TestCheck {

    public static void main(String[] args) throws IOException {

        // 创建对象
        Calculator calculator = new Calculator();
        // 获取字节码文件对象
        Class cls = calculator.getClass();
        // 获取所有方法
        Method[] methods = cls.getMethods();

        int number = 0; // 出现异常次数
        BufferedWriter bw = new BufferedWriter(new FileWriter("src/practice/demo02/bug.txt"));

        for (Method method : methods) {
            // 判断方法上是否有 @Check 注解
            if (method.isAnnotationPresent(Check.class)) {
                try {
                    // 执行
                    method.invoke(calculator);
                } catch (Exception e) {
                    // 捕获异常
                    number++;
                    bw.write(method.getName() + "方法出异常了");
                    bw.newLine();
                    bw.write("异常的名称: " + e.getCause().getClass().getSimpleName());
                    bw.newLine();
                    bw.write("异常的原因: " + e.getCause().getMessage());
                    bw.newLine();
                    bw.newLine();
                }
            }
        }
        bw.write("本次共出现了" + number + "次异常");
        bw.flush();
        bw.close();
    }
}
div方法出异常了
异常的名称: ArithmeticException
异常的原因: / by zero

本次共出现了1次异常

反射

Java反射机制概述

  • 动态语言 在运行时可以改变自身结构(如 JavaScript)

  • 静态语言 运行时结构不可变

Java不是动态语言,但 Java 可以称之为“准 动态语言”(利用反射机制)

Reflection 是 Java 被视为动态语言的关键,反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

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

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。

image-20220126220922518

Java反射机制提供的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理

优点:

​ 可以实现动态创建对象和编译,体现出很大的灵活性

缺点:

对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作。

反射相关的主要API

  • java.lang.Class // 代表一个类
  • java.lang.reflect.Method // 代表类的方法
  • java.lang.reflect.Field // 代表类的成员变量
  • java.lang.reflect.Constructo // 代表类的构造器

理解Class类并获取Class实例

对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构(class/interface/enum/annotation/primitive type / void / [ ])的有关信息。

  • Class 本身也是一个类
  • Class 对象只能由系统建立对象
  • 一个加载的类在JVM中只会有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的一个 .class文件
  • 每个类的实例都会记得自己是由哪个Class实例所生成
  • 通过Class 可以完整地得到一个类中的所有被加载的结构
  • Class类是 Reflection 的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象
方法名功能说明
static ClassforName(String name)返回指定类名name的Class对象
Object newInstance()调用缺省构造函数,返回Class对象的一个实例
getName()返回此Class对象所表示的实体(类,接口,数组类或void)的名称。
Class getSuperClass()返回当前Class对象的父类的Class对象
Class[] getinterfaces()获取当前Class对象的接口
ClassLoader getClassLoader()返回类的类加载器
Constructor[] getConstructors()返回一个包含某些Constructor对象的数组
Method getMothed(String name,Class..T)返回一个Method对象,此对象的形参类型为paramType
Field[] getDeclaredFields()返回Field对象的一个数组

a)若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高。

Class c1= Person.class;

b)已知某个类的实例,调用该实例的getClass()方法获取Class对象

Class c2 = person.getClass();

c)已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException

Class c3 = Class.forName("demo01.Student");

d)内置基本数据类型可以直接用类名.Type

Class c4 = Integer.TYPE;

e)还可以利用ClassLoader我们之后讲解

package reflect;


// 测试 Class 类的创建方式有哪些
public class Test02 {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person = new Student();
        System.out.println("这个人是:" + person.name);
        
        // 方式一:通过对象获得
        Class c1 = person.getClass();

        // 方式二:forname 获得
        Class c2 = Class.forName("reflect.Student");

        // 方式三: 通过类名 .class
        Class c3 = Student.class;

        // 方式四:基本内置类型的包装类都有一个 type 属性
        Class c4 = Integer.TYPE;

        // 获得父类类型
        Class c5 = c1.getSuperclass();

        System.out.println(c1.getSimpleName());
        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());
        System.out.println(c4.hashCode());
        System.out.println("c4:" + c4);
        System.out.println("c5:" + c5);

    }
}

class Person {

    public String name;

    public Person() {}

    public Person(String name) {
        this.name = name;

    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}

class Student extends Person {
    public Student(){
        this.name = "学生";
    }
}

class Teacher extends Person {
    public Teacher() {
        this.name = "老师";
    }
}
public static void main(String[] args) {
    // 类
    Class c1 = Object.class;
    System.out.println(c1.getSimpleName());         // Object
    // 接口
    Class c2 = Comparable.class;
    System.out.println(c2.getSimpleName());         // Comparable
    // 一维数组
    Class c3 = int[].class;
    System.out.println(c3.getSimpleName());         // int[]
    // 二维数组
    Class c4 = int[][].class;
    System.out.println(c4.getSimpleName());         // int[][]
    // 注解
    Class c5 = Override.class;
    System.out.println(c5.getSimpleName());         // Override
     // 枚举
    Class c6 = ElementType.class;
    System.out.println(c6.getSimpleName());         // ElementType
    // 基本数据类型
    Class c7 = Integer.class;
    System.out.println(c7.getSimpleName());         // Integer
    // void
    Class c8 = void.class;
    System.out.println(c8.getSimpleName());         // void
    // Class
    Class c9 = Class.class;
    System.out.println(c9.getSimpleName());         // Class
    // 基本数据类型
    Class c10 = int.class;
    System.out.println(c10.getSimpleName());        // int
   
    

    // 只要元素类型与维度一样,就是同一个 Class
    int[] a = new int[10];
    int[] b = new int[100];
    System.out.println(a.getClass().hashCode());
    System.out.println(b.getClass().hashCode());
    System.out.println(c3.hashCode());
哪些类型可以有 Class 对象?
  • class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类。
  • interface:接口
  • []:数组
  • enum:枚举
  • annotation: 注解@interface
  • primitive type: 基本数据类型
  • void

类的加载与ClassLoader

Java内存

  • 存放new的对象和数组

  • 可以被所有的线程共享,不会存放别的对象引用

  • 存放基本变量类型(会包含这个基本类型的具体数值)

  • 引用对象的变量(会存放这个引用在堆里面的具体地址)

  • 方法区

  • 可以被所有的线程共享

  • 包含了所有的class和static变量

image-20220310211714001

加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象。

链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。

  • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
  • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
  • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

初始化:

  • 执行类构造器 ()方法的过程。类构造器 ()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
  • 虚拟机会保证一个类的 ()方法在多线程环境中被正确加锁和同步。
package reflect;

public class Test04 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(A.m);
        // System.out.println(a.m);
        
        /*
            1. 加载到内存,会产生一个类对应的 Class 对象
            2. 链接, 链接结束后 m = 0;
            3. 初始化
                <clinit>() {
                     System.out.println("A 类静态代码块初始化");
                     m = 300;
                     m = 100;
                }
         */
    }
}
class A {
    static {
        System.out.println("A 类静态代码块初始化");
        m = 300;
    }
    static int m = 100;
    public A() {
        System.out.println("A类的无参初始化");
    }
//    static {
//        System.out.println("A 类静态代码块初始化");
//        m = 300;
//    }
//    // 输出 m = 300
}
什么时候会发生类初始化?
  • 类的主动引用(一定会发生类的初始化)
    • 当虚拟机启动,先初始化main方法所在的类
    • new一个类的对象
    • 调用类的静态成员(除了final常量)和静态方法
    • 使用java.lang.reflect包的方法对类进行反射调用
    • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
  • 类的被动引用(不会发生类的初始化) test05.java 2. 3. 4.
    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
    • 通过 数组定义 类引用,不会触发此类的初始化
    • 引用常量 不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
package reflect;


// 测试类什么时候会初始化
public class Test05 {
    static {
        System.out.println("main 方法被加载...");
    }
    public static void main(String[] args) throws ClassNotFoundException {
        // 1. 主动引用
        Son son = new Son();   // main 方法被加载...    父类被加载...    子类被加载...
        // 反射也会产生主动引用
        Class.forName("reflect.Son");  // main 方法被加载...    父类被加载...    子类被加载...
        // 2. 不会产生类的引用的方法
        System.out.println(Son.b);  // main 方法被加载...    父类被加载...   2
        // 3. 通过数组定义类引用,不会触发此类的初始化
        Son[] array = new Son[5];   // main 方法被加载...
        // 4. 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
        System.out.println(Son.M);  // main 方法被加载...    1
        
        System.out.println(Son.m);  // main 方法被加载...    父类被加载...    子类被加载...    1
    }
}
class Father {
    static int b = 2;
    static {
        System.out.println("父类被加载...");
    }
}
class Son extends Father {
    static {
        System.out.println("子类被加载...");
        m = 300;
    }
    static int m = 100;
    static final int M = 1;
}
类加载器的作用

类加载的作用:

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

类缓存:

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

image-20220311084027539

类加载器的作用

类加载器作用是用来把类(class)装载进内存的。JVM规范定义了如下类型的类的加载器。

引导类加载器:用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取

扩展类加载器:负责jre/lib/ext目录下的jar包或 -D java.ext.dirs 指定目录下的 jar 包装入工作库

系统类加载器:负责 java-classpath 或 -D java.class.path 所指的目录下的类与 jar 包装入工作,是最常用的加载器

image-20220311084251182

public class Test06 {

    public static void main(String[] args) throws ClassNotFoundException {

        // 获取系统类的加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);  // sun.misc.Launcher$AppClassLoader@18b4aac2

        // 获取系统类加载器的父类加载器 --> 扩展类的加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);         // sun.misc.Launcher$AppClassLoader@18b4aac2

        // 获取扩展类的加载器 --> 根加载器 (c/c++)
        ClassLoader grandParent = parent.getParent();
        System.out.println(grandParent);    // null  java 读取不到

        // 测试当前类是哪个加载器加载的
        ClassLoader classLoader = Class.forName("reflect.Test06").getClassLoader();
        System.out.println(classLoader);    // sun.misc.Launcher$AppClassLoader@18b4aac2

        // 测试 JDK 内置的类是谁加载的
        classLoader = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classLoader);    // null

        // 如何获得系统类加载器可以加载的路径
        System.out.println(System.getProperty("java.class.path"));

        /*
            D:\Program_Files\java\jdk8.0\jre\lib\charsets.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\deploy.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\ext\access-bridge-64.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\ext\cldrdata.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\ext\dnsns.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\ext\jaccess.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\ext\jfxrt.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\ext\localedata.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\ext\nashorn.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\ext\sunec.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\ext\sunjce_provider.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\ext\sunmscapi.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\ext\sunpkcs11.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\ext\zipfs.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\javaws.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\jce.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\jfr.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\jfxswt.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\jsse.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\management-agent.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\plugin.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\resources.jar;
            D:\Program_Files\java\jdk8.0\jre\lib\rt.jar;
            E:\Documents\IDEA_projects\annotate_reflect\out\production\annotate_reflect;
            D:\Program_Files\IntelliJ IDEA 2021.3.1\lib\idea_rt.jar
         */
    }
}
双亲委派机制

image-20220321150112213

20201217213314510

创建运行时类的对象

获取运行时类的完整结构

class User {
    private String name;
    private int id;
    private int age;

    public User() {}

    public User (String name, int id, int age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }

    public String getName() {return name;}
    public void setName(String name) { 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;}

    // 为检验 Test07 获得类的方法(私有)
    private void test() {}
    
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", age=" + age +
                '}';
    }
}
  • class 类
    • getClass() // 返回 class + Class对象的包名 + 类/接口/数组类/void的名称 (class reflect.User)
      • 多用于对象的获取字节码方式
    • getSuperClass() // 返回当前Class对象父类的包名 + 类/接口/数组类/void的名称
    • ClassforName(String name) // 返回 class + Class对象的包名 + 类/接口/数组类/void的名称 (class reflect.User)
      • 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
    • Person.class // 返回类的包名 + 类/接口/数组类/void的名称
      • 多用于参数的传递
    • getName() // 返回Class对象的包名 + 类/接口/数组类/void的名称 (reflect.User)
    • getSimpleName() // 返回Class对象的类/接口/数组类/void的名称 (User)
  • // field 属性
    • getFields() // 返回对象的一个 public 属性的数组
    • getField("name") // 返回对象的一个指定名称的public属性
    • getDeclaredFields() // 返回对象的一个全部属性的数组
    • getDeclaredField("name") // 返回对象的一个指定名称的属性
    • **// method 方法
    • getMotheds() // 返回本类及其父类的所有方法(public)
    • getMothed(String name,Class..T) // 获得本类及其父类的指定名称和参数类型的方法(public)
    • getDeclaredMotheds() // 获得本类的所有方法(包括 private)
    • getDeclaredMothed(String name,Class..T) // 获得本类指定名称和参数类型的方法(包括 private)
  • // constructor 构造器
    • getConstructors() // 返回一个包含public Constructor 对象的数组
    • getConstructor(String.class,int.class, ...) // 返回一个指定参数类型的public Constructor对象
    • getDeclaredConstructors() // 返回一个包含Constructors 对象的数组(包括 private)
    • getDeclaredConstructor(String.class,int.class, ...) // 返回一个指定参数类型的Constructor对象(包括 private)
  • // interface 接口
    • getinterfaces() // 获取当前Class对象的接口
package reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
// 获得类的信息
public class Test07 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class c1 = Class.forName("reflect.User");

        // 获得类的名字1
        System.out.println(c1.getName());       // reflect.User
        System.out.println(c1.getSimpleName()); // User


        // 获得类的名字2
        User user = new User();
        Class c2 = user.getClass();
        System.out.println(c2.getName());       // reflect.User
        System.out.println(c2.getSimpleName()); // User

        // 获得类的属性
        Field[] fields = c1.getFields();    // 只能找到 public 属性 (打印为空)
        // for (Field field : fields) {
        //     System.out.println(field);
        // }

        fields = c1.getDeclaredFields();    // 可以找到全部属性
        for (Field field : fields) {
            System.out.println(field);
        }
        /*
            private java.lang.String reflect.User.name
            private int reflect.User.id
            private int reflect.User.age
         */

        // 获得指定属性的值
        // Field name = c1.getField("name");
        Field name = c1.getDeclaredField("name");
        System.out.println("-----------------------------------------");
        System.out.println(name);   // private java.lang.String reflect.User.name

        // 获得类的方法
        System.out.println("===============================================");
        Method[] methods = c1.getMethods();     // 获得本类及其父类的所有方法(public)
        for (Method method : methods) {
            System.out.println("正常的:" + method);
        }
        /*
        正常的:public java.lang.String reflect.User.toString()
        正常的:public java.lang.String reflect.User.getName()
        正常的:public int reflect.User.getId()
        正常的:public void reflect.User.setName(java.lang.String)
        正常的:public int reflect.User.getAge()
        正常的:public void reflect.User.setId(int)
        正常的:public void reflect.User.setAge(int)
        正常的:public final void java.lang.Object.wait() throws java.lang.InterruptedException
        正常的:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
        正常的:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
        正常的:public boolean java.lang.Object.equals(java.lang.Object)
        正常的:public native int java.lang.Object.hashCode()
        正常的:public final native java.lang.Class java.lang.Object.getClass()
        正常的:public final native void java.lang.Object.notify()
        正常的:public final native void java.lang.Object.notifyAll()
         */

        methods = c1.getDeclaredMethods();      // 获得本类的所有方法(包括 private)
        for (Method method : methods) {
            System.out.println("getDeclaredMethods:" + method);
        }

        /*
        getDeclaredMethods:public java.lang.String reflect.User.toString()
        getDeclaredMethods:public java.lang.String reflect.User.getName()
        getDeclaredMethods:public int reflect.User.getId()
        getDeclaredMethods:public void reflect.User.setName(java.lang.String)
        getDeclaredMethods:private void reflect.User.test()
        getDeclaredMethods:public int reflect.User.getAge()
        getDeclaredMethods:public void reflect.User.setId(int)
        getDeclaredMethods:public void reflect.User.setAge(int)
          */
        // 获得指定方法
        System.out.println("-=-=-=-=-=-=-=-=-=-=-=");

        Method getName = c1.getMethod("getName", null);
        Method setName = c1.getMethod("setName", String.class);
        System.out.println(getName);    // public java.lang.String reflect.User.getName()
        System.out.println(setName);    // public void reflect.User.setName(java.lang.String)
        
        Method wait = c1.getMethod("wait", null);	// 父类方法
		System.out.println(wait);  
        // public final void java.lang.Object.wait() throws java.lang.InterruptedException

        // 获得指定的构造器
        Constructor[] constructors = c1.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        /*
        public reflect.User()
        public reflect.User(java.lang.String,int,int)
         */
        constructors = c1.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            System.out.println("Declared: " + constructor);
        }
        /*
        Declared: public reflect.User()
        Declared: public reflect.User(java.lang.String,int,int)
         */
        // 获得指定的构造器
        Constructor declaredConstructorsconstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
        System.out.println("指定:" + declaredConstructorsconstructor);    
        // 指定:public reflect.User(java.lang.String,int,int)
    }
}

通过反射获取运行时类的完整结构

Field, Method、 Constructor, Superclass、 Interface、 Annotation

  • 实现的全部接口
  • 所继承的父类
  • 全部的构造器
  • 全部的方法
  • 全部的Field
  • 注解
  • ...
有了Class对象,能做什么?

创建类的对象:调用Class对象的newlnstance()方法

  • 类必须有一个无参数的构造器。
  • 类的构造器的访问权限需要足够

思考?难道没有无参的构造器就不能创建对象了吗?只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。步骤如下:

  • 通过Class类的getDeclaredConstructor(Class parameterTypes, ...)取得本类的指定形参类型的构造器
  • 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
  • 通过Constructor实例化对象
newInstance()           // 调用构造函数,返回Class对象的一个实例   
    
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, 			   IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        
    // 获得 Class 对象
    Class c1 = Class.forName("reflect.User");

    // 构造一个对象
    User user = (User) c1.newInstance();    // 本质上是调用了类的无参构造器(删掉User中的无参构造会报错)
    System.out.println(user);

    // 通过构造器创建对象(有参构造)
    Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
    User user2 = (User) declaredConstructor.newInstance("Kite", 1, 23);
    System.out.println(user2);

    
    // 通过反射调用普通方法
    User user3 = (User) c1.newInstance();
    // 通过反射获取一个方法
    Method setName = c1.getDeclaredMethod("setName", String.class);
    // invoke : 激活
    // (对象,"方法的值")
    setName.invoke(user3,"Lee");
    System.out.println(user3.getName());

    // 通过反射操作属性
    User user4 = (User) c1.newInstance();
    Field name = c1.getDeclaredField("name");

    // 不能直接操作私有属性 , --> setAccessible(true)	(关闭程序的安全检测)
    name.setAccessible(true);
    name.set(user4, "ErMu");
    System.out.println(user4.getName());

}
调用指定的方法

通过反射,调用类中的方法,通过Method类完成。

①通过Class类的getMethod(String name,Class...parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。

②之后使用Object invoke(Object obj,Object[]args)进行调用,并向方法中传递要设置的obj对象的参数信息。

image-20220311111015896

Object invoke(Object obj, Object ... args)

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

setAccessible

  • Method和Field、Constructor对象都有setAccessible()方法。
  • setAccessible作用是启动和禁用访问安全检查的开关。
  • 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
    • 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
    • 使得原本无法访问的私有成员也可以访问
  • 参数值为false则指示反射的对象应该实施Java语言访问检查
package reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

// 分析性能问题
public class Test09 {
    // 普通方式调用
    public static void test001() {
        User user = new User();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1_0000_0000; i++) {
            user.getName();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("普通方式执行1亿次:" + (endTime - startTime) + " ms");
    }
    // 反射方式调用
    public static void test002() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName", null);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1_0000_0000; i++) {
            getName.invoke(user, null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式执行1亿次:" + (endTime - startTime) + " ms");
    }
    // 反射方式调用  关闭检测
    public static void test003() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName", null);
        getName.setAccessible(true);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1_0000_0000; i++) {
            getName.invoke(user, null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式关闭检测执行1亿次:" + (endTime - startTime) + " ms");
    }
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        test001();
        test002();
        test003();
    }
}
// 普通方式执行1亿次:5 ms
// 反射方式执行1亿次:357 ms
// 反射方式关闭检测执行1亿次:183 ms
反射操作泛型

Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除。

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

  • ParameterizedType:表示一种参数化类型,比如Collection
  • GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
  • TypeVariable:是各种类型变量的公共父接口
  • WildcardType:代表一种通配符类型表达式
// 通过反射获取泛型
public class Test10 {
    public void test01(Map<String, User> map, List<User> list) {
        System.out.println("I'm test01");
    }
    public Map<String, User> test02() {
        System.out.println("I'm test02");
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Method method = Test10.class.getMethod("test01", Map.class, List.class);
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println(genericParameterType);   
                // 1. java.util.Map<java.lang.String, reflect.User>
                // 2. java.util.List<reflect.User>            
            if (genericParameterType instanceof ParameterizedType) {
                Type[] actualTypeArguments = 
                    ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                        // 1. class java.lang.String   class reflect.User
                        // 2. class reflect.User
                }
            }
        }
                                                                                                           
        System.out.println("------------------------------------------");
        method = Test10.class.getMethod("test02", null);
        Type genericReturnType = method.getGenericReturnType();
        if (genericReturnType instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
                    // class java.lang.String   class reflect.User
            }
        }
    }
}
反射操作注解
package reflect;
import java.lang.annotation.*;
import java.lang.reflect.Field;

public class Test11 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class<?> c1 = Class.forName("reflect.Student2");
        // 通过反射获得注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        // 获得注解的 value 的值
        TableKite tableKite = (TableKite) c1.getAnnotation(TableKite.class);
        String value = tableKite.value();
        System.out.println(value);

        // 获得类指定的注解
        Field f = c1.getDeclaredField("id");
        FieldKite annotation = f.getAnnotation(FieldKite.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());

    }
}

@TableKite("db_student")
class Student2 {
    @FieldKite(columnName = "db_id", type = "int", length = 10)
    private int id;
    
    @FieldKite(columnName = "db_age", type = "int", length = 10)
    private int age;
    
    @FieldKite(columnName = "db_name", type = "varchar", length = 3)
    private String name;
    
    public Student2() { }
    public Student2(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 "Student2{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

// 类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableKite {
    String value();
}

// 属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldKite {
    String columnName();
    String type();
    int length();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值