Java - 注解与反射(学习笔记)

Java - 注解与反射(小结)

注解 java.Annotation

1、什么是注解

Annotation 是从JDK5.0开始引入的新技术 .

  • Annotation的作用

  • 不是程序本身 , 可以对程序作出解释.(这一点和注释(comment)没什么区别)

  • 可以被其他程序(比如:编译器等)读取. Annotation的格式

  • 注解是以"@注释名"在代码中存在的,还可以添加一些参数值 ,

例如:@SuppressWarnings(value="unchecked")

  • Annotation在哪里使用?

  • 可以附加在package , class , method , field 等上面 , 相当于给他们添加了额外的辅助信息,

  • (读取注解)我们可以通过反射机制实现对这些元数据的访问

2、内置注解

常见的三个内置注解:

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

    扫描二维码关注公众号,回复: 16052223 查看本文章
  • @Deprecated :定义在java.lang.Deprecated中 , 此注释可以用于修辞方法 , 属性 , 类 , 表示不鼓励程序员使用这样的元素 , 通常是因为它很危险或者存在更好的选择 .

  • @SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息.

  • 与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数都是已经定义好了的,我们 选择性的使用就好了 .

  • @SuppressWarnings("all")

  • @SuppressWarnings("unchecked")

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

等等 .....

3、元注解meta-annotation

  • 元注解的作用就是负责注解其他注解 。

  • Java定义了4个标准的meta-annotation类型,他们被用来提供 对其他annotation类型作说明 。

  • 这些类型和它们所支持的类在java.lang.annotation包中可以找到 .( @Target , @Retention , @Documented , @Inherited )

  • ==@Target== : 用于描述注解的使用范围(即:被描述的注解可以用在什么地方)

  • ==@Retention== : 表示需要在什么级别保存该注释信息 , 用于描述注解的生命周期。

  • 级别:SOURCE < CLASS < RUNTIME(默认)

  • @Document:说明该注解将被包含在javadoc中。

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

实践一下:

//自定义一个注解  外部类的写法
public @interface MyAnnotation1 {
  
}
//新建一个test01测试类
public class test01 {
    @MyAnnotation1
    public void test(){

    }
}
//自定义一个注解 内部类的写法 (一个类里只能有一个public class)
@interface MyAnnotation2{

}

//自定义一个注解 内部类的写法 (一个类里只能有一个public class)
// @Target定义MyAnnotation3注解可以用在什么地方
//value=ElementType.TYPE 类型 |  FIELD 字段 |  METHOD 方法 | PARAMETER  | CONSTRUCTOR   |   LOCAL_VARIABLE
@Target(value = ElementType.METHOD)//这里定义了这个注解只能 在方法上面用 若放在类删则会报错
@interface MyAnnotation3{

}

//ElementType[] 是一个数组类型 所以我们可以定义多个可使用注解的地方
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@interface MyAnnotation4{

}

查看ElementType[] : 进入@Target再进入ElementType[]

//这里声明了 关键字对 应的可以用注解的地方
public enum ElementType {
    
    TYPE,/** Class, interface (including annotation type), or enum declaration */
    FIELD,/** Field declaration (includes enum constants) */
    METHOD, /** Method declaration */
    PARAMETER,/** Formal parameter declaration */
    CONSTRUCTOR, /** Constructor declaration */
    LOCAL_VARIABLE,  /** Local variable declaration */
		ANNOTATION_TYPE,/** Annotation type declaration */
    PACKAGE, /** Package declaration */
    /**
     * Type parameter declaration
     * @since 1.8
     */
    TYPE_PARAMETER,
    /**
     * Use of a type
     * @since 1.8
     */
    TYPE_USE
}

@Retention

source:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;被编译器忽略

class:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期

runtime:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在

这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。

4、自定义注解

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

  • 分析 :

  • @ interface用来声明一个注解 , 格式 : public @ interface 注解名 { 定义内容 }

  • 其中的每一个方法实际上是声明了一个配置参数.

  • 方法的名称就是参数的名称.

  • 返回值类型就是参数的类型 ( 返回值只能是基本类型,Class , String , enum ).

  • 可以通过default来声明参数的默认值 如果只有一个参数成员 ,

  • 一般参数名为value 。(如果value只有一个值时还可以把value去掉)

  • 注解元素必须要有值 , 我们定义注解元素时 , 经常使用空字符串,0作为默认值 .

package test03;

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 = 18,name = "秦疆",id = 001,schools = {"西工大"})
    public void test() {

    }

    //显示定义值 // 不显示值就是默认值
    @MyAnnotation2(age = 18,name = "秦疆",id = 001,schools = {"西工大","西工大2","西工大3"})
    public void test3() {

    }

    //只有一个参数, 默认名字一般是value.使用可省略不写
    @MyAnnotation3("aaa")
    public void test2(){

    }

}

@Target(value = {ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
    //注解的参数: 参数类型  参数名()
    String name() default ""; //若用 default 定义了默认值 在使用时可以不写参数,自动使用默认值
    int age() default 0;
    int id() default -1; //String indexOf("abc") -1 , 不存在,找不到
    String[] schools() default {"西部开源","狂神说Java"};
}




@Target(value = {ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation3{
    // 参数类型 参数名称()
    String value();

}

反射机制 java.Reflection

1、(引入话题)动态语言 与 静态语言

  • 动态语言

  • 是一类在运行时可以改变其结构的语言:

  • 例如新的函数、对象、甚至代码可以被引进,已有的 函数可以被删除或是其他结构上的变化。

  • ==通俗点说就是在运行时代码可以根据某些条件改变自 身结构。==

  • 主要动态语言:Object-C、C#、JavaScript、PHP、Python等。

//体现动态语言的代码 
function test() {
	var x = "var a=3;var b=5;alert(a+b);";//声明一个字符串 "var a=3;var b=5;alert(a+b)"
	eval(x); //eval(X)执行这个字符串内的代码,从而改变X的值
}
/*
本来 X 时一个静态的字符串
当程序运行时 eval()方法 通过字符串X的内容 改变了x的结构 变成了int类型
*/
  • 静态语言

  • 与动态语言相对应的,运行时结构不可变的语言就是静态语言。

  • 如Java、C、C++。

  • Java不是动态语言,但Java可以称之为“准动态语言”。

  • 即Java有一定的动态性,我们可以利用 反射机制获得类似动态语言的特性。

  • Java的动态性让编程的时候更加灵活!

2、Java Reflection

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

//Class类是管理反射的一个核心类 
Class c = Class.forName("java.lang.String")

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

我们可以通过这个对象看到类的结构。这个对象就像一面镜子, 透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

  • Java反射机制提供的功能

  • 在运行时判断任意一个对象所属的类

  • 在运行时构造任意一个类的对象

  • 在运行时判断任意一个类所具有的成员变量和方法

  • 在运行时获取泛型信息

  • 在运行时调用任意一个对象的成员变量和方法

  • 在运行时处理注解

  • 生成动态代理

  • .....

  • Java反射优点和缺点

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

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

  • 反射相关的主要API

  • java.lang.Class : 代表一个类

  • java.lang.reflect.Method : 代表类的方法

  • java.lang.reflect.Field : 代表类的成员变量

  • java.lang.reflect.Constructor : 代表类的构造器

  • .......

3、Class类

java.lang.Class
package test04_reflection;

public class Test {

    public static void main(String[] args) throws ClassNotFoundException {
        //通过反射获取的Class对象
        Class c1=Class.forName("test04_reflection.User");// test04_reflection.User ==>User类的路径
        //写法2
//      Class<?> c1=Class.forName("test04_reflection.Test.User");// test04_reflection.User ==>User类的路径
        System.out.println(c1);


        Class c2=Class.forName("test04_reflection.User");
        Class c3=Class.forName("test04_reflection.User");
        Class c4=Class.forName("test04_reflection.User");
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());
        System.out.println(c4.hashCode());
        //若输出的hashCode都是一样 则说明 一个类在内存中只有一个Class对象
        //一个类被加载后 , 类的整个结构信息会被放到对应的Class对象中

    }
}

//创建一个实体类User (pojo 或 entity)
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;
    }


    //因为加了private 需要用 get set来获得或修改参数
    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;
    }

    //加上 toString() 便于查看对象的信息 便于程序的调试
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
  • 若输出的hashCode都是一样 则说明 一个类在内存中只有一个Class对象

  • 即 创建的Class类型c2、c3、c4对象 都是指向同一个User类的Class对象

  • 一个类被加载后 , 类的整个结构信息会被放到对应的Class对象中

在Object类中定义了以下的方法,此方法将被所有子类继承。

public final Class getClass();

以上的方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来 看也很好理解,即:可以通过对象反射求出类的名称。

对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。

对于每个 类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构 (class/interface/enum/annotation/primitive type/void/[])的有关信息。

  • Class 本身也是一个类

  • Class 对象只能由系统建立对象

  • 一个加载的类在 JVM 中只会有一个Class实例

  • 一个Class对象对应的是一个加载到JVM中的一个.class文件

  • 每个类的实例都会记得自己是由哪个 Class 实例所生成

  • 通过Class可以完整地得到一个类中的所有被加载的结构

  • Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

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()

返回一个包含某些Constructor对象的数组

Method getMothed(String name,Class.. T)

返回一个Method对象,此对象的形参类型为paramType

Field[] getDeclaredFields()

返回Field对象的一个数组

更多请查询文档

如何获取Class类的实例

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

 Class c1 = Person.class;

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

Class c1 = person.getClass();

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

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

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

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

package test04_reflection;

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

        //获得class办法一:通过对象获得
        Class c1 = person.getClass();
        System.out.println("c1 hashCode:"+c1.hashCode());
        //获得class办法二:通过字符串获得(包名+类名)
        Class c2= Class.forName("test04_reflection.Student");
        System.out.println("c2 hashCode:"+c2.hashCode());
        //获得class办法三:通过类的静态成员class获得
        Class c3 = Student.class;
        System.out.println("c3 hashCode:"+c3.hashCode());
        //获得class办法四:基本内置类型的包装类都有一个Type属性 (只针对内置的基本数据类型)
        Class c4 = Integer.TYPE;

        System.out.println("c4 type:"+c4);
        
        //获得父类类型
        Class c5 = c1.getSuperclass();
        System.out.println("c5 :"+c5+"     c5 hashCode:"+c3.hashCode());

    }
}

//第一步 创建一个pojo类  人
class Person{
    String name;

    public Person() {
    }

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

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

//第二步
//再创建一个pojo  学生
class Student extends Person{
    public Student() {
        this.name="学生";
    }
}
//第三步
//再创建一个pojo  老师
class Teacher extends Person{
    public Teacher() {
        this.name="老师";
    }
}

哪些类型可以有Class对象?

  • class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类。

  • interface:接口

  • []:数组 enum:

  • 枚举 annotation:

  • 注解@interface

  • primitive type:基本数据类型

```java

package test04_reflection;
import java.lang.annotation.ElementType;
//哪些类型可以有Class对象?
//会自动生成Class对象 的类型 class interface [] 枚举 注解 primitive type void
public class Test03  {
public static void main(String[] args) {
        Class c1 = Object.class;//类
        Class c2 = Comparable.class;//接口
        Class c3 = String[].class;//一维数组
        Class c4 = int[][].class;//二维数组
        Class c5 = ElementType.class;//枚举
        Class c6 = Override.class;//注解
        Class c7 = Integer.class;//基本数据类型
        Class c8 = void.class;//void
        Class c9 = Class.class;//Class本身
        Class c10 = Test03.class;


        //打印出: 类型 包名
        System.out.println(" 类型    包名");
        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);

        System.out.println(c10);

        System.out.println("对比一下");
        //验证一下 新建一个对象后,这个新对象的Class与被new对象的Class 是否为同一个?
        int[] a =new int[10];
        int[] b =new int[20];
        System.out.println(int[] .class.hashCode());
        System.out.println(a.getClass().hashCode());
        System.out.println(b.getClass().hashCode());
        //若是同一个 则hashCode应该为一样
        //结论:只要元素类型与维度一样,就是同一个Class
    }
}

```

5、类加载内存分析

java内存分析

类加载的过程

![截屏2022-10-13 08.54.37](/Users/wangjiabao/Desktop/截屏2022-10-13 08.54.37.png)

类的加载与ClassLoader的理解(了解)

  • 加载:

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

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

  • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题

  • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。

  • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

  • 初始化:

  • 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造 器)。

  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。

来段代码测一下

package test04_reflection;

public class Test05 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(A.num);
      /*流程
      1、加载到内存,会产生一个类对应Class对象
      2、链接,链接结束后 num=0
      3、初始化
      		<clinit>(){
      				 System.out.println("A类的静态代码块初始化");
       				 num=300;
       				 num=100;
      		}
      		===> num=100
      */
    }
}
class A{
    //静态代码块
    //使用这个类时 优先加载 静态代码块的内容
    static {
        System.out.println("A类的静态代码块初始化");
        num=300;
    }
    static int num=100;
    public A() {//构造时(即在new一个对象时) 会自动执行
        System.out.println("A类的无参构造");
    }
}
Main()中 new一个新的A时,执行顺序:class A 先执行 静态代码块 的内容(m=300) ,执行完后再自上而下执行下面的代码(再把m=100),new的内容跑完后;再在mian()中自上而下进行执行 sout。

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

  • 类的主动引用(一定会发生类的初始化)

  • 当虚拟机启动,先初始化main方法所在的类

  • new一个类的对象

  • 调用类的静态成员(除了final常量)和静态方法

  • 使用java.lang.reflect包的方法对类进行反射调用

  • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类

  • 类的被动引用(不会发生类的初始化)

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。 如:当通过子类引用父类的静 态变量,不会导致子类初始化。

  • 通过数组定义类引用,不会触发此类的初始化。

  • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)

package test04_reflection;

public class Test05 {
    static {
        System.out.println("main()被加载");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        //1 、主动调用
//        Son son = new Son();

        //2、反射也会产生主动引用
//        Class.forName("test04_reflection.Son");

        //不会产生引用的方法
        //3、子类直接引用父类的静态变量  ,不会导致子类初始化
//        System.out.println(Son.b);

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

        //5、引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
        System.out.println(Son.M);

    }


}

class Father {
    static {
        System.out.println("父类被加载");
    }
    static int b=100;
}

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对象

类加载器的作用

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

package test04_reflection;

public class Test06 {
    public static void main(String[] args) throws ClassNotFoundException {
        //获取系统的加载器 sun.misc.Launcher$AppClassLoader
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);

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

        //获取扩展类加载器的父类加载器 --> 根加载器 (由C++编写的 ,java直接是获取不到的,即返回null 就是由根加载器加载的)
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);

        //测试当前类是哪个加载器加载的
        ClassLoader classLoader = Class.forName("test04_reflection.Test06").getClassLoader();
        System.out.println(classLoader);

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

创建与获取运行时 类的对象

获取运行时类的完整结构

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

Field(字段)、Method(方法)、Constructor(构造器)、Superclass(父类)、Interface(接口)、Annotation(注解 )

  • 实现的全部接口 所继承的父类

  • 全部的构造器

  • 全部的方法

  • 全部的Field 注解

  • 。。。

小结

  1. 在实际的操作中,取得类的信息的操作代码,并不会经常开发。

  1. 一定要熟悉java.lang.reflect包的作用,反射机制。

  1. 如何取得属性、方法、构造器的名称,修饰符等。

6、通过反射可以用来做什么

使用反射的主要思想:

  • 我们可以通过反射,即Class对象,来获取对应类的全部信息,并通过获取的信息来操作。

  • 获取方式:通过Class类对象的方法(可查文档)返回的Method、Filed、Constructior对象来获取、调用或操作对应的类的信息、属性或方法。

通过反射(即有了Class对象),能做什么?

以User类为例
大前提:要有User类的Class对象。如:
//获得一个Class对象 //获取通过Class类 获得User类的Class对象 Class c1 = Class.forName("test04_reflection.User");//"User类的包路径"
  • 通过Class对象创建一个User类的对象(两种方式):

  • 第一种,无参构造。

通过 User的Class类对象(c1_User) 的newInstance()方法 构建一个 User对象user。
⚠️注意:1、类必须有一个无参数的构造器。2、类的构造器的访问权限要足够。
  • 第二种,有参构造。

通过Class获取User的声明的有参构造器,并通过该构造器构造一个新user
  • 通过Class对象获取方法,并方法对象反向对User类对象进行操作。

  • 通过Class对象获取属性,并通过属性对象反向对User类对象进行操作。

例如:去创建一个User类的对象
  • 正常逻辑是去new一个新对象,而现在我们可以通过反射来创建对象,即通过Class创建

  • 通过Class创建类的对象的方法:调用Class对象的newInstance()方法。

  1. 类必须有一个无参数的构造器。

  1. 类的构造器的访问权限要足够。

思考:难道没有无参的构造器就不能创建对象了吗?

只要在操作的时候明确的调用类中的构造器, 并将参数传递进去之后,才可以实例化操作。
  • 步骤如下:

  • 通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器

  • 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。

  • 通过Constructor实例化对象

例如:调用指定的方法
  • 通过反射,调用类中的方法,通过Method类完成。

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

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

  • Object 对应原方法的返回值,若原方法无返回值,此时返回null

  • 若原方法若为静态方法,此时形参Object obj可为null

  • 若原方法形参列表为空,则Object[] args为null

  • 若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的 setAccessible(true)方法,将可访问private的方法。

7、setAccessible

  • Method和Field、Constructor对象都有setAccessible()方法。

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

  • 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。

  • 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。

  • 使得原本无法访问的私有成员也可以访问

  • 参数值为false则指示反射的对象应该实施Java语言访问检查

实操一下

package test04_reflection;

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

//通过反射(通过Class类获取) ==> 动态的创建对象
public class Test08 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //获得一个Class对象
            //获取通过Class类 获得User类的Class对象
            Class c1_User = Class.forName("test04_reflection.User");

/**
 *有了Class对象,能做什么?
	*/

//一、构建一个对象(两种方式):
        System.out.println("==============================================================");
        System.out.println("构建一个对象(两种方式)");
        System.out.println("第一种,无参构造。");
        //第一种,无参构造。
        //通过 User的Class类对象(c1_User) 的newInstance()方法 构建一个 User对象user
        //c1_User.newInstance() 返回的是一个  Objet对象
      	//==> 我们知道这个 Objet对象 是 User类型的 所以可以直接强制转换为User
        //newInstance()的本质是调用了无参构造的方法  ,所以若没有无参构造的方法时,会报错
         User user = (User) c1_User.newInstance();//=> c1_User 是User的.Class对象 (通过Class的方法可以获取到User的全部信息,如:包名,字段,方法等等)
          System.out.println(user);

        //第二种,有参构造。
        System.out.println("-----------------------------------------------");
        System.out.println("第二种,有参构造。");
        //通过构造器创建对象:通过反射获取User的声明的有参构造器,并通过该构造器构造一个新user
       Constructor constructor = c1_User.getDeclaredConstructor(int.class, int.class, String.class);
      //第一步,获得声明的(有参)构造器(构造器需要传入的数据类型) ------ getDeclaredConstructor()获取声明的构造函数
       User user_zhangSan =(User) constructor.newInstance( 001, 18,"张三");//第二步,通过构造器创建一个对象
        System.out.println(user_zhangSan);

//二、通过反射获取普通方法,并对user3进行使用
            System.out.println("==============================================================");
            System.out.println("通过反射获取普通方法,并对user3进行使用");
            User user3=(User) c1_User.newInstance();//第一步,通过Class创建一个User对象
            System.out.println("创建的user3:"+user3);
            //通过Class对象获得一个方法
            Method user_Method_setName = c1_User.getDeclaredMethod("setName", String.class);//通过User类的Class对象的getDeclaredMethod(String 方法名,Class…parameterTypes)获得 User的setName方法
            user_Method_setName.invoke(user3,"李四");//  method.invoke(对象,该方法的传参)   执行,调用,援入(对象,该方法的传参)  -- 调用 user3对象的setName()
            System.out.println("被使用setName的user3:"+user3);

//通过反射获取属性,并对user4进行操作
        System.out.println("==============================================================");
        System.out.println("通过反射获取属性,并对user4进行操作");
        User user4 = (User) c1_User.newInstance();
        Field name = c1_User.getDeclaredField("name");
        name.setAccessible(true);//获得对私有属性对访问权限
        name.set(user4,"大大");
        System.out.println(user4);
        /**
         * 若报错如下:
         * Exception in thread "main" java.lang.IllegalAccessException: Class test04_reflection.Test08 can not access a member of class test04_reflection.User with modifiers "private"
         * 	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
         * 	at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
         * 	at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
         * 	at java.lang.reflect.Field.set(Field.java:761)
         * 	at test04_reflection.Test08.main(Test08.java:53)
         *
         * 	该报错是指 当前访问的属性是私有的private,我们的访问权限不够,修改对前提是要先对对象进行访问
         * 	我们可以在访问前 使用 Field。setAccessible(true) 获得权限 
         * == 关闭访问检查,从而获得执行权限 
         * == 对属性进行访问前,java有安全管理器进行访问检查,该属性是否有可访问权限,即是否为私有
         * 	true的值表示反射对象应该在使用时抑制Java语言访问检查。 false的值表示反映的对象应该强制执行Java语言访问检查。
         *
         * 	使用 Field.setAccessible()的不足点:会降低代码执行的效率
         */

    }

}
package test04_reflection;
public 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;
    }


    //因为加了private 需要用 get set来获得或修改参数
    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;
    }

    //加上 toString() 便于查看对象的信息 便于程序的调试
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

分析一下性能

package test04_reflection;

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

//分析性能问题
public class test09 {
    //普通方式调用
   public static void tset01(){
       User user = new User();
       long startTime = System.currentTimeMillis();
       for (int i = 0; i < 1000000000; i++) {
           user.getName();
       }
       long endtTime = System.currentTimeMillis();
       System.out.println("普通方式执行1000000000:"+(endtTime-startTime)+"ms");
   }
    //反射方式调用 有访问检查
    public static void tset02() throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        User user = new User();
        Class c1 = Class.forName("test04_reflection.User");
        Method getName = c1.getDeclaredMethod("getName", null);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endtTime = System.currentTimeMillis();
        System.out.println("反射方式调用且未关访问检查,执行1000000000:"+(endtTime-startTime)+"ms");
    }

    //反射方式调用 有访问检查
    public static void tset03() throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        User user = new User();
        Class c1 = Class.forName("test04_reflection.User");
        Method getName = c1.getDeclaredMethod("getName", null);
        getName.setAccessible(true);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endtTime = System.currentTimeMillis();
        System.out.println("反射方式调用且关访问检查,执行1000000000:"+(endtTime-startTime)+"ms");
    }

    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        tset01();
        tset02();
        tset03();
    }
}

正常通过new一个User对象,去调用它的方法是最省时效率最高的。

通过反射,且未关闭java的访问检查,去调用一个User对象的方法是最耗时间效率最低的。

通过反射,且关闭java的访问检查,去调用一个User对象的方法的效率至少是不关的2倍,但还是慢于正常调用。

8、反射操作泛型(用得比较少)

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

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

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

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

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

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

package test04_reflection;

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 test10 {
    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 = test10.class.getDeclaredMethod("test01", Map.class, List.class);

        Type[] genericParameterTypes = method.getGenericParameterTypes();//获取方法test01传入的的通用参数类型(即获得传入的泛型参数类型) 返回一个数组
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println("#"+genericParameterType);//
            if (genericParameterType instanceof ParameterizedType){//若返回的不是 结构化参数类型ParameterizedType
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }

            }
        }
        System.out.println("================================");
        Method method2 = test10.class.getDeclaredMethod("test02",null);
        Type genericReturnType = method2.getGenericReturnType();
        if (genericReturnType instanceof ParameterizedType){//若返回的不是 结构化参数类型ParameterizedType
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }

        }


    }
}

9、反射操作注解

  • getAnnotations

  • getAnnotation

练习:ORM

了解什么是ORM ?

Object relationship Mapping --> 对象关系映射
  • 类和表结构对应

  • 属性和字段对应

  • 对象和记录对应

要求 : 利用注解和反射完成类和表结构的映射关系

package test04_reflection;

//反射操作注解

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

/**
 * 第一步,创建一个简单的pojo类 Student2
 * 第二步,创建自定义注解,通过注解来 模拟数据库的一张表
 * 第三步,在main()中通过反射来操作注解
 */

public class test11 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("test04_reflection.Student2");
        //通过反射 获得注解
        Annotation[] annotations = c1.getAnnotations();//获得所有注解 数组类型
        for (Annotation annotation : annotations) {//打印该类的所有的注解名
            System.out.println(annotation);
        }
        //获得 指定注解的value的值
        myTable myTable_Annotation = (myTable)c1.getAnnotation(myTable.class);//通过getAnnotation()获得指定的注解
        System.out.println(myTable_Annotation.value());
        System.out.println();
        //获得类指定的注解
        Field name = c1.getDeclaredField("name");//获取声明的字段name (字段名)
        myField annotation = name.getAnnotation(myField.class);//通过字段name获取该字段的注解 (注解类型)
        System.out.println(annotation.columName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());
        System.out.println();
        Field id = c1.getDeclaredField("id");//获取声明的字段name (字段名)
        annotation = id.getAnnotation(myField.class);//通过字段name获取该字段的注解 (注解类型)
        System.out.println(annotation.columName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());
    }
}

@myTable("db_student")
class Student2{
   @myField(columName ="db_id",type = "int",length = 10)
    private int id;
    @myField(columName ="db_age",type = "int",length = 10)
    private int age;
    @myField(columName ="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 myTable{
    String value();
}

//自定义 属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface myField{
    String columName();//列名
    String type();//类型
    int length();//长度
}

在一些自动生成代码的框架中,比如通过实体类自动生成数据库表,在实体类中定义大量的注解,再通过反射框架去读取这些注解去生成相应的信息。比如说,数据库有一张表, 我们可以通过注解去定义一个与这张表相应数据类型的实体类,进在代码中可以生一些sql语言,自动创建表等等


补充

Method类的常用方法

  • 如何获得Method对象。

  • Method getMethod(String name,Class… parameterTypes.)//根据方法名和参数获得公共方法

  • Method[] getMethod();获得所有的公共方法

  • Method getDeclaredMethod(String name,Class…parameterTypes);根据方法名和参数获得方法(可以为非公共方法)

  • Method[] getDeclaredMethods();获得当前类中的所有方法。

  • 这里我们主要通过反射获得Method对象,即通过Class对象的getDeclaredMethod(String name,Class…parameterTypes)获得该方法。

  • Method的一些方法

String getName();获得方法名。

int getModifiers();获得修饰符。

Class getReturnTypes();获得返回值类型。

Class[] getParameterTypes();获得参数类型的数组。

Objec invoke(Object obj,Object…args);//执行方法

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method method = Math.class.getMethod("sqrt", double.class);//第二个参数如果是基本类型,不能用其包装类代替
        double invoke = (Double)method.invoke(null,16D);//如果方法为static第一个参数为null或者用方法的类。如果为普通方法,则需为调用该方法的实例。
        System.out.println("返回值:"+invoke);//输入4.0
        System.out.println("方法名:"+method.getName());
        System.out.println("返回值类型:"+method.getReturnType().getName());
        System.out.println("参数列表"+Arrays.toString(method.getParameterTypes()));
        System.out.println("修饰符:"+ Modifier.toString(method.getModifiers()));
    }
返回值:4.0
方法名:sqrt
返回值类型:double
参数列表[double]
修饰符:public static

Field类的常用的方法

  • 在获取一个类的属性时由两种方式:

  • 1.得到实现的接口或父类中的公共属性:public Field[] getFields()

  • 2.得到本类中的全部属性:public Field[] getDeclaredFields()

public Object get(Object obj) //得到一个对象中属性的具体内容
public void set(Object obj,Object value) //设置指定对象中属性的具体内容
public int getModifiers() //得到属性的修饰符
public String getName() //返回此属性的名称
public boolean isAccessible() //判断此属性是否可被外部访问
public void setAccesssible(boolean flag) //设置一个属性是否可被外部访问
public static void setAccessible(AccessibleObject[] array,boolean flag) //设置一组属性是否可以被外部访问
public String toString() //返回此Field类的信息

Java动态性 详解

动态语言

程序运行时可以改变程序结构或变量类型。典型动态语言:Python、ruby、javascript等

C/C++、Java不是动态语言,但Java可称为“准动态语言”,它有一定动态性,Java的动态性让编程更加灵活。

反射机制

  • 反射机制 -- 指的是可以于运行时加载、探知、使用编译期间完全未知的类。

  • 程序在运行状态中,可以动态加载一个只有名称的类,对于任意一个已加载的类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;

Class c = Class.forName("com.bjsxt.test.User");

  • 加载完类之后,在堆内存中,就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。

  • 这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

Class类

java.lang.Class类十分特殊,用来表示java中类型(class/interface/enum/annotation/primitive type/void)本身。

  • Class类的对象包含了某个被加载类的结构。一个被加载的类对应一个Class对象。

  • 当一个class被加载,或当加载器(class loader)的defineClass()被 JVM调用,JVM便自动产生一个Class对象。

  • Class类是Reflection的根源。针对任何您想动态加载、运行的类,唯有先获得相应的Class对象

Class类获取

Class clazz = 对象引用.getClass();
Class clazz = 类名.class;
Class clazz = Class.forName("包名.类名");

一个类被加载后,JVM会加载一个对应该类的Class对象,类的整个结构信息会放到该Class对象中。一个类只对应一个反射对象,加载多次也只是那一个。

Class类成员获取

  • 获取成员变量

import java.lang.reflect.Field;//获取成员变量需导入该包

Field[] fileds = clazz.getFields();//该方法只能获取public属性
Field[] fileds = clazz.getDeclareFields();//该方法能获取所有属性
Field[] filed = clazz.getDeclareField("属性名");//该方法能获取指定属性
  • 获取类方法

import java.lang.reflect.Method;//获取类方法需导入该包

Method methods = clazz.getMethods();//该方法只能获取public类方法
Method methods = clazz.getDeclareMethods();//该方法能获取所有类方法
Method method = clazz.getDeclareMethod("方法名", 参数类型.class);//该方法能获取指定类方法
  • 获取构造器

import java.lang.reflect.Constructor;//获取构造器需导入该包

Constructor constructors = clazz.getMethods();//该方法只能获取public构造器
Constructor constructors = clazz.getDeclareMethods();//该方法能获取所有构造器
Constructor constructor = clazz.getDeclareMethod(参数类型.class);//该方法能获取指定构造器

动态操作

  • 动态实例化类对象

Class<Test> clazz = (Class<Test>)Class.forName("kiang.test.Test");//如果在此处不写泛型,则下面每次实例化都需要强制类型转换
Test t = clazz.newInstance();//实际上是调用了类的无参构造器(此方法已弃用)
Test t = clazz.getDeclaredConstructor(int.class, int.class, String.class).newInstance(1,2,"kiang");//现在一般这么用
  • 这里有个问题,至今无解

  • 动态调用普通方法

Test t = clazz.getDeclaredConstructor(int.class, int.class, String.class).newInstance(1,2,"kiang");
Method m = clazz.getDeclaredMethod("setName", String.class);
m.invoke(t, "fuck");
  • 动态操作属性

Test t = clazz.getDeclaredConstructor(int.class, int.class, String.class).newInstance(1,2,"kiang");
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);//禁止执行访问安全检查
field.set(t, "fuck");
  • 反射调用会比普通调用更加耗时,禁止执行访问安全检查可以提速几倍,但仍比不上普通调用。

动态编译

Java 6.0引入了动态编译机制。

动态编译的应用场景:

  • 可以做一个浏览端编写Java代码,上传服务器编译和运行的在线测评系统。

  • 服务器动态加载某些类文件。

动态编译的两种做法:

  • 通过Runtime调用javac,启动新的进程去操作。

  • 通过JavaCompiler动态编译。

通过JavaCompiler动态编译

public static int compileFiles(String sourceFile){
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    int result = compiler.run(null, null, null, sourceFile);
    System.out.println(result == 0 ? "编译成功" : "编译失败");
    return result;
}

run方法的四个参数:

  1. 为Java编译器提供参数

  1. 得到Java编译器的输出信息

  1. 接收编译器的错误信息

  1. 可变参数能传入一个或多个(使用String数组)Java源文件

  1. 返回值:0表示编译成功,非0表示编译失败。

猜你喜欢

转载自blog.csdn.net/weixin_41606115/article/details/129031017