注解与反射

4/8/2022 JavaSE

# 注解

# 理解注解

  1. **注解(Annotation)**是从 JDK5.0 开始引入的一个新技术

  2. 注解的作用

    1. 注解并不是程序本身,但却可以对程序作出解释,解释的内容也称之为元数据
    2. 注解可以被其它程序读取
  3. 注解的书写格式

    @注释名 的格式在代码中存在

  4. 注解可以在哪里使用

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

  5. JAVA 中的注解类似于 TypeScript 中的装饰器

# 内置注解

常见的内置注解有下面三种

  1. @Override

    定义在 java.lang.Override 中,此注解只能用于修饰方法。表示要重写超类的一个方法

  2. @Deprecated

    定义在 java.lang.Deprecated 中,此注解可以用于修饰属性、方法、类。表示不鼓励程序员使用被修饰的元素

  3. @SuppressWarnings

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

    与前两个注解不同,你需要添加一个参数才能正确使用,这些参数都是已经定义好了的,我们选择性地使用即可,比如:

    @SuppressWarnings("all")

    @SuppressWarnings("unchecked")

    @SuppressWarnings(value={"unchecked", "deprecated"})

# 元注解

元注解的作用就是负责注解其它注解(也称为注解的注解),Java 中定义了4个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型的说明

这些类型和它们所支持的类可以在 java.lang.annotation 包中找到

  1. @Target

    用于描述注解的使用范围

  2. @Retention

    表示需要在什么级别使用保存该注解信息,用于描述注解的生命周期

  3. @Document

    说明该注解将包含在 javadoc 中

  4. @Inherited

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

定义一个注解:[public] @interface 注解名

// 使用元注解
// 
@Target(value = ElementType.METHOD)
@interface MyAnnotation{
  
}

# 自定义注解

自定义注解格式:[public] @interface 注解名{定义内容}

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

当注解的 定义内容 中有 value 字段,在使用注解时传递的参数可以不写 value, 直接写参数值即可,例子如下

public class Demo {
  @MyAnnotation2("hello")
  @MyAnnotation(name = "lkj")
  public void test(){
    System.out.println("使用了自定义注解");
  }
}

/**
 * 定义一个注解 MyAnnotation,该注解只能在方法上使用
 * 该注解需要传递一个 name 参数,如果没有传递,默认为 ""
 */
@Target(ElementType.METHOD)
@interface MyAnnotation{
  String name() default "";
}
/**
 * 定义一个注解 MyAnnotation2,该注解只能在方法上使用
 * 该注解需要传递一个 value 参数
 */
@Target(ElementType.METHOD)
@interface MyAnnotation2{
  String value();
}

# 反射

# 理解反射

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

加载完类后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个类只有一个 Class 对象,并且 Class 对象不是我们 new 出来的,是类加载器(ClassLoader)加载出来的),这个对象就包含了完整的类的结构信息,因此我们可以通过这个对象看到类的结构。换言之,这个对象就像一面镜子,透过这个镜子可以看到类的结构,所以,我们也形象的称之为反射

反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。

正常方式

  1. 引入需要的包类名称
  2. 通过 new 实例化
  3. 取得实例化对象

反射方式

  1. 取得实例化对象
  2. 调用 getClass() 方法
  3. 取得完整的包类名称

# 优缺点

# 优点

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

# 缺点

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

# 反射相关类

# java.lang.Class

# java.lang.reflect.Field

# java.lang.reflect.Method

# java.lang.reflect.Constructor

# 获取Class类对象

  1. 前提:已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法 forName() 获取,可能抛出 ClassNotFountException 异常。

    实例:Class cls = Class.forName("java.lang.Cat");

    应用场景:多用于配置文件,读取类全路径,加载类

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

    实例:Class cls = Cat.class;

    应用场景:多用于参数传递,比如通过反射,得到对应构造器对象

  3. 前提:已知某个类的实例,调用该实例的 getClass() 方法获取 Class 对象。

    实例:Class cls = 对象.getClass();

    应用场景:通过创建好的对象,获取 Class 对象

  4. 其它方式:通过类加载器(4种)

    ClassLoader cl = 对象.getClass().getClassLoader();

    Cl.loadeClass(类的全路径);

  5. 基本数据类型的 Class 类对象

    Class cl = 基本数据类型.class;

  6. 包装数据类型的 Class 类对象

    Class cl = 包装数据类型.TYPE;

Class<?> aClass = Class.forName("basicNode.TreeNode");
System.out.println("第一种方式获取 Class 对象:" + aClass);

Class<TreeNode> treeNodeClass = TreeNode.class;
System.out.println("第二种方式获取 Class 对象:" + treeNodeClass);

TreeNode treeNode = new TreeNode(12);
Class<? extends TreeNode> aClass1 = treeNode.getClass();
System.out.println("第三种方式获取 Class 对象:" + aClass1);

ClassLoader classLoader = aClass1.getClassLoader();
Class<?> aClass2 = classLoader.loadClass("basicNode.TreeNode");
System.out.println("第四种方式获取 Class 对象:" + aClass2);

Class<Integer> integerClass = int.class;
System.out.println("基本数据类型的 Class 对象:" + integerClass);

Class<Boolean> type = Boolean.TYPE;
System.out.println("包装数据类型的 Class 对象:" + type);

# 哪些类型有Class对象

  1. 接口(interface)
  2. 数组
  3. 枚举(enum)
  4. 注解(annotation)
  5. 基本数据类型
  6. void
Class<String> stringClass = String.class;// 普通类
Class<Serializable> serializableClass = Serializable.class;// 接口
Class<Integer[]> aClass = Integer[].class;// 数组
Class<Deprecated> deprecatedClass = Deprecated.class;// 注解
Class<Thread.State> stateClass = Thread.State.class;// 枚举
Class<Void> voidClass = void.class;// void
Class<Class> classClass = Class.class;// Class

# 类加载

反射机制是 java 实现动态语言的关键,也就是通过反射来实现类的动态加载

  1. 静态加载:编译时加载相关的类,如果没有,则报错,依赖性太强
  2. 动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性。反射就是采用了这种加载方式

类加载的时机

  1. 当通过 new 的方式创建对象时。属于静态加载
  2. 当子类被加载时,父类也被加载。属于静态加载
  3. 调用类中的静态成员时。属于静态加载
  4. 通过反射。属于动态加载
Last Updated: 4/8/2022, 7:03:16 PM