11.1 注解

11.1.1 注解的理解

下面说的话可能不是人话,如果想更通俗的理解注解,跳转链接文章,个人觉得讲的5星赞。主要用标签类比了注解

  1. 注解也称为元数据,用于修饰解释包、类、方法、属性、构造器、局部变量等信息
  2. 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息
  3. JavaSE中,注解的功能比较简单,例如标记过时方法,忽略警告等。但在JavaEE中十分重要

11.1.2 Java内置注解

  • Deprecated:用来标记过时的方法、类或者成员变量
  • Override:提示子类要重写父类的方法
  • SuppressWarnings:抑制警告
  • SafeVarargs:参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作
  • FunctionalInterface:函数式接口注解

11.1.3 注解的定义

注解使用@interface关键字进行定义。如下代码定义了一个注解类TestAnnotation。它的形式和接口很像,注意区分。

  1. public @interface TestAnnotation{
  2. }

11.1.4 元注解

元注解就是用来注解注解的注解(有点绕)。有5种元注解下面一一介绍(具体解释及案例见链接):

  • Retention:用来描述注解的生命周期
  • Documented:将注解中的元素包含到 Javadoc
  • Target:指定了注解运用的地方
  • Inherited:说明注解可继承
  • Repeatable:说明注解可以多次使用

11.1.5 注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface TestAnnotation {
  4. int id();
  5. String msg();
  6. }
  7. 代码分析: 对于自定义注解类来说,它拥有两个成员变量: idmsg,类型分别为intString。此外根据元注解可知,该注解可以给一个类型进行注解,比如类、接口、枚举;此外,该注解可以保留到程序运行的时候,它会被加载进入到JVM中,所以在程序运行时可以获取到它们。

当注解有成员变量时,在使用注解时就需要对成员变量进行赋值,赋值方式为在注解括号内以value=""形式,多个属性间用逗号隔开。比如:TestAnnotation(id=3, msg="hello, annotation")

在注解中定义属性时它的类型必须是 8 种基本数据类型外加类、接口、注解及它们的数组。

也可以在定义注解时给成员变量设置默认值,此时在使用注解时,就可以不用赋值。

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface TestAnnotation {
  4. public int id() default -1;
  5. public String msg() default "Hi";
  6. }
  7. 代码分析: idmsg的默认值分别为-1"Hi",所以在使用注解时可以无需赋值,如下所示
  8. @TestAnnotation()
  9. public class Test{}

此外还有一种特殊情况,即如果一个注解内只有一个名为value的属性时,应用这个注解可以直接将属性值填写在括号内。

public @interface Check {
    String value();
}
---cut----
@Check("hi")
int a;

最后一种情况即注解没有任何属性,此时应用注解时无需括号。

11.1.6 注解与反射

当一个注解可以保留到程序运行过程中时,就可以利用反射机制去对注解进行操作。很多框架的底层都使用了这种机制。由于还没有介绍到反射,所以这里简单介绍反射和注解如何关联。

注解通过反射获取。首先可以通过Class对象的isAnnotationPresent()方法判断它是否应用了某个注解。如果应用了注解,就可以通过方法getAnnotation()getAnnotation()来获取Annotation对象。如果获取到的Annotation对象不为空,就可以调用它们的属性方法了。

@TestAnnotation()
public class Test {
    public static void main(String[] args) {
        boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
        if ( hasAnnotation ) {
            TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
            System.out.println("id:"+testAnnotation.id());
            System.out.println("msg:"+testAnnotation.msg());
        }
    }
}

代码分析: 第7、8行分别调用了注解类TestAnnotation对象的id和msg属性。

11.2 反射

11.2.1 Java Reflection Introduction

  1. 反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息(比如成员变量,构造器,成员方法等),并能操作对象的属性和方法
  2. 加载完类后,在堆中产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息,通过这个对象得到类的结构。这个Class对象就像一面镜子,透过这个镜子可以看到类的结构,所以形象的称为反射。

11.2.2 反射机制原理图

图片.pngJava反射机制可以完成以下功能:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时得到任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的成员变量和方法

11.2.3 反射相关的主要类

  1. java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
  2. java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
  3. java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
  4. java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示构造器

11.2.4 反射的优点和缺点

  • 优点:可以动态的创建和使用对象,使用灵活
  • 缺点:使用反射基本是解释执行,对执行速度有影响

有一种方式可以优化速度,但是效果很有限,即关闭访问检查。

MethodFieldConstructor对象都有setAccessible(),该方法用于启动或禁用访问安全检查的开关,当参数为true时,表示取消访问检查,可以一定程度上提高反射的执行速度。

11.2.5 Class

Class类的基本介绍

Class类也是一种类,它的继承关系如下图所示:
图片.png
该类具有如下特点:

  1. Class不是new出来的,而是系统创建的,即在类加载时,就会创建一个Class类对象在堆上
  2. 每个类的Class类对象,在内存中只有一份,因为类只加载一次
  3. 每个类的实例都会记得自己是由哪个Class实例所生成的
  4. 通过Class类对象可以完整的得到一个类的结构
  5. Class对象是放在堆中的
  6. 类的字节码二进制数据,是放在方法区的

Class类的常用方法
  • forName:根据给定的类名返回一个Class对象
  • newInstance:创建一个类的实例
  • getMethod:获取方法
  • getField:获取属性
  • getFields:获取所有属性
  • getConstructor:获取构造器
  • getPackage:获取类所属的包
    public class ClassMethod {
      @SuppressWarnings("all")
      public static void main(String[] args)
          throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
          String classAllPath = "com.reflection_.Car";
          // 1. 获取Car类对应的Class对象
          Class cls = Class.forName(classAllPath);
          // 2. 输出cls
          System.out.println(cls);
          System.out.println(cls.getClass());
          // 3. 得到包名
          System.out.println(cls.getPackage().getName());
          // 4. 得到全类名
          System.out.println(cls.getName());
          // 5. 通过cls创建对象实例
          Car car = (Car) cls.newInstance();
          System.out.println(car.toString());
          // 6. 通过反射获取属性
          Field field = cls.getField("brand");
          System.out.println(field);
          System.out.println(field.get(car));
          // 7. 通过反射给属性赋值
          field.set(car, "奔驰");
          System.out.println(field.get(car));
          // 8. 获取所有的属性
          System.out.println("=====输出所有的属性=====");
          Field[] fields = cls.getFields();
          for (Field f : fields) {
              System.out.println(f.getName());
          }
      }
    }
    

获取Class对象的6种方式
  • Class.forName()
  • 类名.class
  • 对象.getClass()
  • classloader.loadClass("类的全类名")
  • 基本数据类型.class
  • 包装类.TYPE

哪些类型有Class对象
图片.png

11.2.6 类加载

静态加载与动态加载

静态加载指编译时加载相关的类,如果没有则报错,依赖性强;动态加载指运行时加载需要的类,如果运行时不用该类,就不会加载,及时不存在也不会报错,降低了依赖性。

进行静态加载的时机:

  • new一个对象时
  • 当子类被加载时,父类也被加载
  • 调用类中的静态成员时

进行动态加载的时机:

  • 通过反射

以下用一段简单的代码说明两种加载概念:

public class LoadDemo {

    public static void main(String[] args)
        throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
        Scanner sc = new Scanner(System.in);
        String key = sc.next();
        switch (key) {
            case "1":
                Cat cat = new Cat();
                cat.eat();
                break;
            case "2":
                Class cls = Class.forName("com.reflection_.Person");
                Object o = cls.newInstance();
                Method method = cls.getMethod("study");
                method.invoke(o);
                break;
        }
    }
}

//代码分析: 这段代码在编译时,由于Cat类属于静态加载,所以在编译时就会加载该类,由于该类不存在,所以编译不通过;而Person类通过反射进行加载,不会在编译时进行加载,所以即使没有Person类,编译也不会受影响,但是运行时,会检查到没有Person类而报错

类加载的过程图

图片.png

  1. 加载阶段

将类的class文件读入内存,并为之创建一个java.lang.Class对象,此过程由类加载器完成。

  1. 链接阶段-验证

class文件进行安全性检查,包括文件格式验证,元数据验证,字节码验证和符号引用验证。

  1. 链接阶段-准备

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

  1. 链接阶段-解析

JVM将常量池中的符号引用替换为直接引用的过程

  1. 初始化
  • 此阶段开始真正开始执行类中定义的代码,是执行<clinit>()的过程
  • <clinit>()是由编译器按语句在源代码中出现的顺序,依次自动收集类中所有静态变量的赋值动作和静态代码块中的语句,并进行合并
  • 虚拟机保证一个类的<clinit>()方法在多线程环境能被正常的加锁

11.2.7 通过反射获取类的结构(纯介绍API,上图)

java.lang.Class类的相关方法

图片.png

java.lang.reflect.Field类的相关方法

图片.png

java.lang.reflect.Method类的相关方法

图片.png

java.lang.reflect.Constructor类的相关方法

图片.png

11.2.8 通过反射创建对象

方式一:调用类中的无参构造器
方式二:调用类中的指定的构造器

利用反射创建对象的步骤:

  1. 获取类对象cls
  2. 如果要调用类的无参构造器,直接使用cls.newInstance()即可
  3. 如果要调用其他有参构造器,需要使用cls.getConstructor()cls.getDeclaredConstructor(),前者只能获取public修饰的构造器,后者可以获取所有声明的构造器
  4. 获取到Constructor对象后,调用其newInstance()方法,在小括号中声明构造函数参数类型的Class对象,即可实现通过指定构造器创建对象,以下代码简单演示了调用不同构造器的方法
public class ReflectCreateInstance {
    @SuppressWarnings("all")
    public static void main(String[] args)
        throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class cls = Class.forName("com.reflection_.User");
        // 通过无参构造器创建实例
        Object o = cls.newInstance();
        System.out.println(o);
        // 获取第一个有参构造器
        Constructor constructor = cls.getConstructor(String.class);
        Object tianyichen = constructor.newInstance("tianyichen");
        System.out.println(tianyichen);
        // 获取第二个有参构造器
        Constructor constructor1 = cls.getDeclaredConstructor(int.class, String.class);
        constructor1.setAccessible(true); // 如果没有这句, 会报IllegalAccessException
        Object tanke = constructor1.newInstance(20, "tanke");
        System.out.println(tanke);

    }
}

class User { //User类
    private int age = 10;
    private String name = "韩顺平教育";

    public User() {//无参 public
    }

    public User(String name) {//public的有参构造器
        this.name = name;
    }

    private User(int age, String name) {//private 有参构造器
        this.age = age;
        this.name = name;
    }

    public String toString() {
        return "User [age=" + age + ", name=" + name + "]";
    }
}

11.2.9 通过反射访问类中成员

访问属性
  1. 根据属性名获取Field对象:Field field = cls.getDeclaredField(属性名)
  2. 爆破:field.setAccessible(true)
  3. 访问
    1. field.set(o, value)
    2. f.get(o),如果获取的是静态属性,则访问时o可以写成null。 ```java public class ReflectAccessField { @SuppressWarnings(“all”) public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException { // 1. 得到Class类对象 Class cls = Class.forName(“com.reflection_.Student”); Object o = cls.newInstance(); // 2. 获取public属性 Field age = cls.getField(“age”); System.out.println(age.get(o)); System.out.println(o); age.set(o, 25); System.out.println(o); // 3. 获取private属性 Field name = cls.getDeclaredField(“name”); name.setAccessible(true); System.out.println(o); name.set(o, “tianyichen”); System.out.println(o); // 4. 获取所有的属性 Field[] fields = cls.getDeclaredFields(); for (Field f : fields) {
        System.out.println(f.getName());
      
      } }

}

class Student { public int age; private static String name;

public Student() {//构造器
}

public String toString() {
    return "Student [age=" + age + ", name=" + name + "]";
}

}


<a name="PB2y3"></a>
##### 访问方法

1. 根据方法名和参数列表获取`Method`对象:`Method method = cls.getDeclaredMethod(方法名,xx.class)`
1. 暴破:`method.setAccessible(true)`
1. 调用:`method.invoke(o, 实参列表)`,如果是静态方法,`o`写成`null`
```java
public class ReflectInvoleMethod {
    @SuppressWarnings("all")
    public static void main(String[] args)
        throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class cls = Class.forName("com.reflection_.Boss");
        Object o = cls.newInstance();
        // 调用public类型的方法
        Method hi = cls.getMethod("hi", String.class);
        hi.invoke(o, "reflection");

        // 调用private static方法
        Method say = cls.getDeclaredMethod("say", int.class, String.class, char.class);
        say.setAccessible(true);
        System.out.println(say.invoke(null, 5, "amazing", 'G'));

        // 在反射中,如果方法有返回值,统一返回Object , 但是他运行类型和方法定义的返回类型一致
        Object returnVal = say.invoke(null, 10, "BIG", 'C');
        System.out.println(returnVal);
        System.out.println(returnVal.getClass());
    }

}

class Monster {}
class Boss {
    public int age;
    private static String name;

    public Boss() {//构造器
    }

    public Monster m1() {
        return new Monster();
    }

    private static String say(int n, String s, char c) {//静态方法
        return n + " " + s + " " + c;
    }

    public void hi(String s) {//普通public方法
        System.out.println("hi " + s);
    }
}