第三十一讲——注解与反射

第三十一讲——注解与反射

框架底层实现机制就是注解和反射

1——注解(Annotation)

1.1——什么是注解

  • Annotation的作用:

    • 不是程序本身,可以对程序作出解释,(这一点和注释(Comment)很像 )
    • 可以被其他程序(比如 : 编译器)读取
  • Annotation 的格式

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

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

Application

public class AnnotationStudy01 {
    
    @Override // 这就是一个注解 : 重写注解  可以起到检查代码的作用
    public String toString() {
        return super.toString();
    }
}

1.2——内置注解

三个常见注解

@Override Java.lang.Override 包中 重写注解
@Deprecated java.lang.Deprecated 包中 不推荐用(弃用)注解
@SuppressWarnings java.lang.SuppressWarnings 包中 抑制注解,用来抑制警告信息,需要输入参数,参数已经定义,查文档使用即可

Application

// 三种常见注解
public class AnnotationStudy01 {

    @SuppressWarnings("all")  // 抑制注解 可以抑制一些警告 是需要写入参数的 而参数是定义好的
    /**
     *    "all" // 全部警告
     *    "unchecked"
     *    value= {"unchecked","deprecation"} // 取消警告显示,的警告集
     */
    public static void main(String[] args) {

        test01();
    }

    @Override  // 重写的注解
    public String toString() {
        return super.toString();
    }

    @Deprecated  // 弃用不推荐的注解   不推荐使用,但是可以使用,  或者说有更好的其他方法
    public static void test01(){
        System.out.println("Deprecated");
    }



}

Print

F:\Application\out\production\Application;F:\Application\src\Lib\commons-io-2.11.0.jar AnnotationStudy.AnnotationStudy01
Deprecated

Process finished with exit code 0


1.3——元注解

作用

  • 元注解的作用是注解其他注解, java定义了四个标准的 meta-annotation (元注解)类型,他们被用来提供对其他 annotation 类型作说明

  • meta-Annotation 在 Java.lang.annotation 包中可以找到.(@Target、@Retention、@Documeented、@Inherited)

@Target 用于描述注解的使用范围 (即: 被描述的注解可以用在什么地方)
@Retention 表示需要在什么级别保存该注释信息,用于描述注解的生命周期 (SOURCE < CLASS < RUNTIME(最大最常用)) ( 源代码 < 类 < 运行时 )
@Document 说明该注解将被包含在 javadoc 中
@Inherited 说明子类可以继承父类中的该注解


Application

import java.lang.annotation.*;

// 测试元注解
public class Meta_Annotation {
    
    
    @MyAnnotation
    public void test(){

    }
}

// 定义一个注解 一个类只能有一个 public

// @Target  表示注解可以用在什么地方,例如 在类上、方法上、等等、、、
@Target( value  = {ElementType.METHOD,ElementType.TYPE} ) // @Target (value = ElementType.Method) 因为它是一个数组,可以提供多种作用域

// @Retention  表示我们的注解在什么地方还有效   source < class < runtime 一般使用 runtime
@Retention( value = RetentionPolicy.RUNTIME)

// @Documented 表示是否将注解生成在 JAVAdoc 中
@Documented

// @Inherited 子类可以继承父类的注解
@Inherited
@interface  MyAnnotation {
    // @interface 定义声明为一个注解 
}

1.4——自定义注解

  • 使用 @interface 自定义注解时, 自动继承了 Java.lang.annotation.Annotation 接口

  • 分析 :

    • @interface 用来声明一个注解,格式 :public @interface 注解名
    • 其中的方法其实是声明了一个配置参数 例如 :( value = RetentionPolicy. RUNTIME )
    • 返回值类型就是参数的名称, 返回值类型就是参数的类型 (返回值只能是基本类型,Class,String,enum)
    • 可以通过Default 来声明参数的默认值
    • 如果只有一个参数成员,一般参数名为 value, value也可以省略不写 (@SupperssWarning (“all”))
    • 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值

    Application

    package AnnotationStudy;
    
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    // 自定义注解
    public class Test03 {
    
        // 注解可以显示赋值, 如果没有默认值,就必须赋值
        @MyAnnotation03(name = "项晓忠")
        public void test(){
    
        }
    
    
        @MyAnnotation04("项晓忠")
        public void test01(){
    
        }
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(value = {ElementType.TYPE,ElementType.METHOD})
    @interface MyAnnotation03{
        // 注解的参数 : 参数类型 + 参数名 + (); 它不是方法 , 是一个注解的写法
        String name() ;
    
        int age() default 18;
    
        int id() default -1; // 如果默认值为 -1,代表不存在
    
        String[] schools() default {"浙江杭州","浙江丽水"};
       
    }
    
    @interface MyAnnotation04{
        // 注解参数如果只有一个,那么参数名可以用value(其他参数名就不行,其他的不能省略) 来命名,提供参数时可以省略 value 不写  (一个不成文的规范)
        String value();
    }
    
    /**
     *  一般情况下,不会定义太复杂的注解,一般用到 ( @Target、@Retention ),我们知道用:
     * @interface 定义注解 ,会设置注解内参数 ,用 default 来表示默认值 就可以
     *
     * */
    

graph LR 1(注解)-->2(内置注解) 1-->3(元注解) 1-->4(自定义注解) 2-->5("@Override"<br/>重写<br/>) 2-->6("@Deprecated"<br/>弃用<br/>) 2-->7("@SuppressWarnings"<br/>抑制警告<br/>) 3-->8("@Target"<br/>目标<br/>) 8-->19(ElementType.Method) 3-->9("@Retention"<br/>保留<br/>) 9-->18(RetentionPolicy.class<br/>Source > Class > Runtime<br/>) 3-->10("@Document"<br/>保存到文档<br/>) 3-->11("@Inherited"<br/>是否继承<br/>) 4-->12(default?) 4-->13(Field?) 4-->14(value?) 7-->15(all) 7-->16(unchecked) 7-->17(value = ! #unchecked# , #deprecation# !)

2——反射

在学习反射前先了解一下什么 是静态语言和动态语言

  • 动态语言

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

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

Reflection (反射)

  • Reflection (反射) 是java被视为 ”准动态语言“ 的关键,反射机制允许程序在执行期间借助于 ReflectionAPI 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法(包括 Private)。

  • Class c = Class.forName("java.lang.String")
    
  • 加载类之后,在堆内存方法区中就产生了一个Class 类型的对象 (一个类只有一个Class对象),这个对象就包含了完整的类的结构信息(构造器,方法,属性,包括 Private,全部信息),我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过镜子看到类的结构,所以,我们形象的称之为: ”反射“

# 正常方式: 导入需要的”包类“名称———> 通过new实例化--->取得实例化对象
# 反射方式: 实例化对象---> getClass()方法---> 得到完整的"包类"名称

JAVA反射机制提供的功能

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

优点:

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

缺点:

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


反射主要的 API

  • java.lang.Class : 代表一个类
  • java.lang.reflection.Method : 代表类的方法
  • java.lang.reflection.Field : 代表类的成员变量
  • java.lang.reflection.Constructor:代表类的构造器
  • 、、、、

2.1——获得反射对象

  • 初步认为 反射就是在程序运行时,能进行一些操作,读取内部信息,改变内部结构

Application

package AnnotationStudy;

// 什么叫反射
public class Test04 {

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

        // 通过反射获取类的Class 对象  会抛出异常
        Class c1 = Class.forName("AnnotationStudy.User");
        Class c2 = Class.forName("AnnotationStudy.User");
        Class c3 = Class.forName("AnnotationStudy.User");

        System.out.println(c1);

        // 一个类在内存中只有一个Class 对象
        // 一个类被加载后,类的整个结构都会被封装在 Class 对象中

        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());
        
    }
}

// 实体类  如果一个类中只有一些属性,就叫实体类
// 一般用 pojo entity  表示
class User{
    // 题外话:属性会和数据库映射就是真正的实体类
    private String name;
    private int id;


    public User() {

    }

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

    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;
    }


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

}

Print

F:\Application\out\production\Application;F:\Application\src\Lib\commons-io-2.11.0.jar AnnotationStudy.Test04
class AnnotationStudy.User
668386784
668386784
668386784

Process finished with exit code 0

反射就像从对象,返回到类上,从而获取类的信息


2.2——得到 Class 类的几种方式

  • Class 类

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

    • Class 本身也是一个类
    • Class 对象只能由系统建立对象
    • 一个加载的类在 JVM 中只会有一个Class 实例
    • 一个Class 对象对应的是一个加载到JVM 中的一个 Class 文件
    • 每个类的实例都会记得自己是由哪个 Class 实例所生成
    • 通过 Class 可以完整的得到一个类中所有被加载的结构
    • Class 类是Reflection 的根源,针对任何你想动态加载,运行的类,唯有先获得相应的Class 对象
  • Class类的常用方法


2.3——获取 Class 类的实例

  • 若已知具体的类,通过类的Class 属性获取,该方法最为安全可靠,程序性能较高
Class class = Person.class;
  • 已知某个类的实例,调用该实例的getClass()方法获取Class对象
Class class = person.getClass();
  • 已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法,forName()获取,可能抛出 ClassNotFoundException
Class class = Class.forName("demo01.Student")
  • 内置基本数据类型可以直接用类名.Type
  • 还可以利用ClassLoder,,(后面说)

Application

package AnnotationStudy;

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

        // 方式一 : 通过对象获得
        Class c1 = person.getClass();
        System.out.println(c1.hashCode());

        // 方式二 : forName获取
        Class c2 = Class.forName("AnnotationStudy.Student"); // 需要抛出 找不到类异常 ClassNotFoundException
        System.out.println(c2.hashCode());

        // 方式三 : 通过类名.class 获得
        Class c3 = Student.class;
        System.out.println(c3.hashCode());

        System.out.println("---------");
        // 方法四 :基本内置类型的包装类都有一个Type属性 (作为了解)
        Class c4 = Integer.TYPE;
        System.out.println(c4);

        System.out.println("---------------");
        // 获得父类
        Class c5 = c1.getSuperclass();
        System.out.println(c5);
        

    }
}

class Person{
   public String name;

    public Person() {
    }

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


class Student extends Person{

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

}

class Teacher extends Person{
    public Teacher(){
        this.name = "老师";
    }
}

Print

F:\Application\out\production\Application;F:\Application\src\Lib\commons-io-2.11.0.jar AnnotationStudy.Test05
这个人是:学生
1329552164
1329552164
1329552164
---------
int
---------------
class AnnotationStudy.Person

Process finished with exit code 0


2.4——那些类型可以有 Class 对象?

  • Class : 外部类、成员(成员内部类、静态内部类)、局部内部类、匿名内部类
  • interface:
  • [] :
  • enum: 枚举
  • annotation:注解 @interface
  • primitive type: 基本数据类型
  • void

Application

package AnnotationStudy;

import org.w3c.dom.ls.LSOutput;

import java.lang.annotation.ElementType;

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


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

        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); // 按住 Ctrl + 鼠标右键 (我这里用鼠标中键,面向块操作)

        System.out.println("------------");
        // 只要元素类型与维度一样,就是同一个Class
        int[] a = new int[10];
        int[] b = new int[100];

        System.out.println(a.getClass().hashCode());
        System.out.println(b.getClass().hashCode());
        
    }
}

Print

F:\Application\out\production\Application;F:\Application\src\Lib\commons-io-2.11.0.jar AnnotationStudy.Test06
class java.lang.Object
interface java.lang.Comparable
class [Ljava.lang.String;
class [[I
interface java.lang.Override
class java.lang.annotation.ElementType
class java.lang.Integer
void
class java.lang.Class
------------
668386784
668386784
 // 只要元素类型与维度一样,就是同一个Class
Process finished with exit code 0


2.5——类加载内存分析

  • java 内存分析
graph LR; 1(Java内存)-->2(堆) 1-->3(栈) 1-->4(方法区) 2-->5("存放new的对象和数组"<br/>可以被所有线程共享,不会存放到别的对象引用<br/>) 3-->6("存放基本变量类型(会包含这个基本类型的具体数值)"<br/>引用对象的变量< 会存放这个引用在堆里面的具体地址 ><br/>) 4-->7("可以被所有线程共享"<br/>包含了所有的 calss 和 static 变量<br/>)

  • 了解: 类的加载过程

    当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化

详细

  • 加载

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

  • 链接

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

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

Application

package AnnotationStudy;

public class Test07 {

    public static void main(String[] args) {
        A a = new A();
        System.out.println(a.m);

        /**
         *  1. 加载到内存,会产生一个类对应的Class 对象
         *  2. 链接 , 链接结束后  m = 0
         *  3. 初始化
         *       <clinit>(){
         *                 System.out.println("A类静态代码块初始化")
         *                 m = 300;
         *                 m = 100;
         *
         *               合并代码,变100
         *       }
         *       m = 100;
         *
         */
    }
}

class A {

    static {
        System.out.println("A类静态代码块初始化");
        m = 300;// 因为是 static 的属性,是最先加载的,因此可以先在"声明"前
    }

    static int m = 100;
    // 最后打印出的是 100  因为先赋值的 300 后赋值的 100 显示的是最新数据 所以是100
    public A() {
        System.out.println("A类构造器启动");
    }



}


Print

F:\Application\out\production\Application;F:\Application\src\Lib\commons-io-2.11.0.jar AnnotationStudy.Test07
A类静态代码块初始化
A类构造器启动
100

Process finished with exit code 0


2.6——分析类初始化

  • 什么时候会发生类初始化

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

    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化.如 : 通过子类引用父类的静态变量,不会导致子类初始化 (因为在链接的时候已经 存在了,不用初始化)
    • 通过数组定义类引用,不会触发此类的初始化 (因为 只是开辟了一个空间,并命名而已)
    • 引用常量不会触发此类的初始化 (常量在链接阶段就存入调用类的常量池了 )

Application

package AnnotationStudy;

// 测试类什么时候会被初始化
public class Test08 {

    static {
        System.out.println("main类 被加载");
    }

    public static void main(String[] args) throws ClassNotFoundException{
        // 1. 主动引用
        // 当初始化一个类,如果其父类没有被初始化, 则先会初始化它的父类
        // Son son = new Son();

        // 2.  反射也会产生主动引用
        // Class.forName("AnnotationStudy.Son"); // 注意这里直接跳开 Test08 直接指向 外部类Son


        // 不会产生类的引用的方法
        // System.out.println(Son.b); // 通过子类调用父类的静态变量 不会导致子类父类初始化,因为常量已经在常量池中

        // 通过数组定义类引用,不会触发此类的初始化
        // Son[] array = new Son[10];

        // 引用常量不会触发此类的初始化 因为 类的常量和 静态变量在链接阶段就已经赋了默认值
        System.out.println(Son.M);


    }

}

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;
}

Print

F:\Application\out\production\Application;F:\Application\src\Lib\commons-io-2.11.0.jar AnnotationStudy.Test08
main类 被加载
1

Process finished with exit code 0


2.7——类加载器的作用

  • 类加载的作用 : 将class 文件字节码内容加载到内存中,并将这些静态数据转化成方法区的运行时数据结构,然后在堆中生成一个代表这个类的 java.lang.Class 对象,作为方法区中类数据的访问入口
  • 类缓存: 标准的javaSE 类加载器可以按要求查找类, 但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间.不过 JVM 垃圾回收机制可以回收这些 Class 对象


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

系统类加载器 也叫 AppClassPath

Application

package AnnotationStudy;

public class Test09 {
    // 获取类的加载器
    public static void main(String[] args) throws ClassNotFoundException{

        // 获取系统类的加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);

        // 获取系统类加载器的父类加载器 --> 拓展类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);


        // 获取拓展类加载器的父类加载器 -- >根加载器 (用C/C++编写 java拿不到 所以null)
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);
        
        // 测试当前类是哪一个加载器加载的
        ClassLoader classLoader = Class.forName("AnnotationStudy.Test09").getClassLoader();
        System.out.println(classLoader);

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

        // 如何获得系统类加载器可以加载的路径  程序不是凭空运行的 它是由类加载器,一步一步加载出来的
        System.out.println(System.getProperty("java.class.path"));

        // 双亲委派机制(了解) 比如定义了一个 java.lang.String 类 它会一级一级往上推,查找有没有这个类,如果有自己定义的包就不能使用
               // java.lang.String-->


    }
}

Print

F:\Application\out\production\Application;F:\Application\src\Lib\commons-io-2.11.0.jar AnnotationStudy.Test09
jdk.internal.loader.ClassLoaders$AppClassLoader@78308db1
jdk.internal.loader.ClassLoaders$PlatformClassLoader@27d6c5e0
null
jdk.internal.loader.ClassLoaders$AppClassLoader@78308db1
null
F:\Application\out\production\Application;F:\Application\src\Lib\commons-io-2.11.0.jar// 不知道为啥 我这个路径很小

Process finished with exit code 0


2.8——创建运行时类的对象

获取运行时的完整结构

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

Field、Method、Constructor、Superclass、Interface、Annotation、、、都能被获取


Application

  • 加Declared 表示本类的所有 ( 包括 private )
package AnnotationStudy;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Test10 {

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

        Class c1 = Class.forName("AnnotationStudy.User");
        
      /*
           User user = new User();
           c1 = user.getClass();
       */

        // 获得类的名字
        System.out.println("---1. 获得类的名字------------------");
        System.out.println(c1.getName()); // 获得包名+ 类名
        System.out.println(c1.getSimpleName()); // 获得类名

        // 获得类的属性
        System.out.println("---2. 获得类的属性------------------");
        Field[] field = c1.getFields(); // 只能找到 public 属性
        field = c1.getDeclaredFields(); // 能找到全部的属性
        for (Field field1 : field) {
            System.out.println(field1);
        }


        // 获得指定属性的值
        System.out.println("---3. 获得指定属性的值------------------");
        Field name = c1.getDeclaredField("name");
        System.out.println(name);


        // 获得类的方法
        System.out.println("---4. 获得类的方法------------------");
        Method[] methods = c1.getMethods(); // 获得本类及其父类 全部 public 方法
        for (Method method : methods) {
            System.out.println("正常的: "+method);
        }

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

        // 获得指定方法  因为java里面有重载,同一个方法名的时候,需要丢入参数来准确的定位一个方法
        System.out.println("---5. 获得指定方法------------------");
        Method getName = c1.getMethod("getName", null);
        Method setName = c1.getMethod("setName", String.class);

        System.out.println(getName);
        System.out.println(setName);


        // 获得构造器
        System.out.println("---6. 获得指定的构造器------------------");
        Constructor[] constructors = c1.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        constructors = c1.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            System.out.println("#"+constructor);
        }


        // 获得指定的构造器
        System.out.println("---7. 获得指定的构造器------------------");
        Constructor constructor = c1.getConstructor(String.class, int.class);
        System.out.println("指定的构造器: "+constructor);

    }

}

Print

"D:\java\IDEA—2019\IntelliJ IDEA 2019.3.3\jbr\bin\java.exe" "-javaagent:D:\java\IDEA—2019\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar=63330:D:\java\IDEA—2019\IntelliJ IDEA 2019.3.3\bin" -Dfile.encoding=UTF-8 -classpath F:\Application\out\production\Application;F:\Application\src\Lib\commons-io-2.11.0.jar AnnotationStudy.Test10
---1. 获得类的名字------------------
AnnotationStudy.User
User
---2. 获得类的属性------------------
private java.lang.String AnnotationStudy.User.name
private int AnnotationStudy.User.id
---3. 获得指定属性的值------------------
private java.lang.String AnnotationStudy.User.name
---4. 获得类的方法------------------
正常的: public java.lang.String AnnotationStudy.User.toString()
正常的: public java.lang.String AnnotationStudy.User.getName()
正常的: public void AnnotationStudy.User.setName(java.lang.String)
正常的: public int AnnotationStudy.User.getId()
正常的: public void AnnotationStudy.User.setId(int)
正常的: public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
正常的: public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
正常的: public final void java.lang.Object.wait() 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()
getDeclaredMethodspublic java.lang.String AnnotationStudy.User.toString()
getDeclaredMethodspublic java.lang.String AnnotationStudy.User.getName()
getDeclaredMethodsprivate void AnnotationStudy.User.test()
getDeclaredMethodspublic void AnnotationStudy.User.setName(java.lang.String)
getDeclaredMethodspublic int AnnotationStudy.User.getId()
getDeclaredMethodspublic void AnnotationStudy.User.setId(int)
---5. 获得指定方法------------------
public java.lang.String AnnotationStudy.User.getName()
public void AnnotationStudy.User.setName(java.lang.String)
---6. 获得指定的构造器------------------
public AnnotationStudy.User()
public AnnotationStudy.User(java.lang.String,int)
#public AnnotationStudy.User()
#public AnnotationStudy.User(java.lang.String,int)
---7. 获得指定的构造器------------------
指定的构造器: public AnnotationStudy.User(java.lang.String,int)

Process finished with exit code 0


2.9——动态创建对象执行方法

  • 在实际操作中,取得类的信息的操作代码,并不会经常开发
  • 一定要熟悉 Java.lang.reflect 包的作用,反射机制
  • 如何取得属性、方法、构造器的名称、修饰符等

有了Class 对象,能做什么

  • 创建类的对象; 调用 class 对象的 new Instance()方法

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

    没有无参构造也能创建对象,只要在操作的时候明确调用类中的构造器并将参数传递进去之后,才可以实例化操作

  • 步骤如下:

    1. 通过 Class 类的 getDeclaredConstructor() 取得本类的指定形参类型的构造器
    2. 向构造器的形参传递一个对象数组进去,里面包含了构造器中所需的各个参数
    3. 通过 Constructor 实例化对象

Application

package AnnotationStudy;

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

public class Test11 {

    // 动态的创建对象,通过反射
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {

        // 获得 Class 对象
        Class c1 = Class.forName("AnnotationStudy.User");

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

        // 通过构造器创建对象
//        Constructor constructors = c1.getDeclaredConstructor(String.class, int.class);
//        User user2 = (User) constructors.newInstance("晓忠",18);
//        System.out.println(user2);


        // 通过反射调用普通方法
//        User user3 = (User)c1.newInstance();

        // 通过反射获得一个方法
//        Method setName = c1.getDeclaredMethod("setName", String.class);
//        setName.invoke(user3,"朝阳");
//        System.out.println(user3.getName());


        //  通过反射操作属性
        User user4 = (User)c1.newInstance();
        Field name = c1.getDeclaredField("name" ); // field 在这里是属性的意思

        // 不能直接操作私有属性().我们需要关闭程序安全检测,属性活着方法的 setAccessible
        name.setAccessible(true); // 这里不设置 true 的话 会弹出IllegalAccessException 非法获取异常警告
        name.set(user4,"浮云");
        System.out.println(user4.getName());
        
    }

}

Print

F:\Application\out\production\Application;F:\Application\src\Lib\commons-io-2.11.0.jar AnnotationStudy.Test11
浮云

Process finished with exit code 0


  • 调用指定的方法

    1. 通过 Class 类的getMethod() 方法取得一个 Method 对象,并设置此方法操作时所需要的参数类型
    2. 之后使用 Object invoke(Object obj,Object[] agrs) 进行调用,并向方法中传递要设置的 obj 对象的参数信息


  • 调用指定的方法

    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 语言检查(即不能接收外界的访问私有成员)

2.10——性能对比分析

Application

package AnnotationStudy;

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

// 分析性能问题
public class Test12 {


    public static void main(String[] args) throws Exception {
        // 异常要记得抛出,一级一级往上抛

        test01();
        test02();
        test03();

    }


    // 普通方式调用
    public static void test01(){
         User user = new User();
        long start = System.currentTimeMillis();

        for (int i = 0; i < 10000000; i++) {
            user.getName();
        }
        long end = System.currentTimeMillis();


        System.out.println("执行一百万次所需时间: "+ (end -start)+" -MS");

    }


    // 反射方式调用
    public static void test02() throws Exception {
        // 1. 获取 class 对象
        Class c1 = Class.forName("AnnotationStudy.User");

        User user = (User) c1.newInstance();

        // 2. 获取指定的方法
        Method getName = c1.getDeclaredMethod("getName", null);// 初步理解: 该方法是不是需要提供参数类型 getName  并且 getMethod() 并不能查找 private 方法


        long start = System.currentTimeMillis();

        for (int i = 0; i < 10000000; i++) {

            // 3. 使用 invoke 调用
            getName.invoke(user, null); // (在哪个对象上用,该方法调用的参数若null为null)

        }
        long end = System.currentTimeMillis();


        System.out.println("执行一百万次所需时间: "+ (end -start)+" -MS");

    }

    // 反射方式调用 关闭检测
    // 反射方式调用
    public static void test03() throws Exception {
        // 1. 获取 class 对象
        Class c1 = Class.forName("AnnotationStudy.User");

        User user = (User) c1.newInstance();

        // 2. 获取指定的方法
        Method getName = c1.getDeclaredMethod("getName", null);// 初步理解: 该方法是不是需要提供参数类型 getName  并且 getMethod() 并不能查找 private 方法

        getName.setAccessible(true);
        long start = System.currentTimeMillis();

        for (int i = 0; i < 10000000; i++) {

            // 3. 使用 invoke 调用
            getName.invoke(user, null); // (在哪个对象上用,该方法调用的参数若null为null)

        }
        long end = System.currentTimeMillis();


        System.out.println("执行一百万次所需时间: "+ (end -start)+" -MS");

    }



}

Print

F:\Application\out\production\Application;F:\Application\src\Lib\commons-io-2.11.0.jar AnnotationStudy.Test12
执行一百万次所需时间: 3 -MS
执行一百万次所需时间: 54 -MS
执行一百万次所需时间: 25 -MS

Process finished with exit code 0


2.11——反射操作泛型(了解)

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

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

  • ParameterizedType : 表示一种参数化类型,比如Collection< String >

  • GenericArrayType : 表示一种元素类型是参数化类型活着类型变量的数组类型

  • TypeVariable : 是各种类型变量的公共父接口

  • WidcardType: 代表一种通配符类型表达式


Application

package AnnotationStudy;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

// 通过反射获取泛型
public class Test13 {

    // 没返回值
    public void test01(Map<String ,User> map,  List<User> list){
        System.out.println("test01");
    }

    // 有返回值
    public Map<String,User> test02(){
        System.out.println("test02");
        return null;
    }

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

        Method method = Test13.class.getMethod("test01", 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);
                }
            }

        }

        System.out.println("---------------------"+"\n");

        method = Test13.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);

            }

        }

    }


}

Print

F:\Application\out\production\Application;F:\Application\src\Lib\commons-io-2.11.0.jar AnnotationStudy.Test13
#java.util.Map<java.lang.String, AnnotationStudy.User>
class java.lang.String
class AnnotationStudy.User
#java.util.List<AnnotationStudy.User>
class AnnotationStudy.User
---------------------

class java.lang.String
class AnnotationStudy.User

Process finished with exit code 0


3——反射操作注解

练习: ORM

  • 什么是ORM

    Object relationshipr Mapping --> 对象关系映射



  • 映射关系: 可以利用注解和反射可以完成类和表结构的映射关系

Application

package AnnotationStudy;

import java.lang.annotation	.*;
import java.lang.reflect.Field;

// 练习反射操作注解

public class Test14 {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("AnnotationStudy.Student01");

        System.out.println("---通过反射获得注解----------------");
        // 通过反射获得注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }


        System.out.println("---获得注解的 value 值----------------");
        // 获得注解的 value 值
        TableXxz tableXxz = (TableXxz) c1.getAnnotation(TableXxz.class);
        String value = tableXxz.value();
        System.out.println(value);


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

    }

}

@TableXxz("db-Student") // 假设这是表的名字
class Student01 {

    @FieldXzz(columnName = "db-id",type = "int",length = 10)
    private int id;
    @FieldXzz(columnName = "db-age",type = "int",length = 10)
    private int age;
    @FieldXzz(columnName = "db-name",type = "varchar",length = 3)
    private String name;

    public Student01() {
    }

    public Student01(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 "Student01{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

// 类名的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface TableXxz{
    String value();
    // 假设这是表的名字
}

// 属性的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface FieldXzz{
    String columnName(); // 列名称
    String type();
    int length();
}

Print

F:\Application\out\production\Application;F:\Application\src\Lib\commons-io-2.11.0.jar AnnotationStudy.Test14
---通过反射获得注解----------------
@AnnotationStudy.TableXxz(value="db-Student")
---获得注解的 value 值----------------
db-Student
---获得类指定的注解----------------
db-name
varchar
3

Process finished with exit code 0


新增单词

0 metaAnnotation 元注解 吗特—唉诺忒寻 Annotation
1 Comment 注释
2 Deprecated 弃用的 打噗cate的
3 deprecation 强烈反对的 打噗k寻
4 SuppressWarnings 抑制警告 丝噗瑞丝沃宁丝~ 参数 all 全部| unchecked 为加抑制的 | Deprecation 强烈反对的
5 Retention 保留 瑞疼寻 用于描述注解的生命周期 Runtime > Class > Source
6 RetentionPolicy 保留策略 抛了瑟
7 Documented 文档 剁q门特的 doc
8 inherited 是否继承 in 喝儿 特的
9 runtime 运行时
10 school 学校
21 Reflection 反射 瑞f莱寻
22 pojo 实体类
23 entity 实体类
24 primitive 基本 噗么特无 primitive type 基本类型
25 initialize 初始化(全拼) in 腻特 莱兹
26 Father 父亲
27 Parent 父母
28 Declared 已经声明了的 第可累尔的
29 invoke 调用 | 启用 in 沃可
20 Accessible 可以取用的 阿克塞丝博 不设置 true 的话 会弹出IllegalAccessException 非法获取异常警告
21 parameterTypes 参数类型 噗乱门的
22 illegalArgumentException 路径无效 亿里狗熬隔门te 非法路径异常
23 columnName 行名字 com
24 varchar 文本类型 威char 和String 类似
26 unchecked 为加抑制的 恩恰ct 是 @SuppressWarnings 的参数
27 parameter 参数 噗乱没得
28 Table 表格 | 桌子
posted @ 2021-12-14 18:55  项晓忠  阅读(33)  评论(0编辑  收藏  举报