关于javascript:小白都能学会的Java注解与反射机制

4次阅读

共计 13095 个字符,预计需要花费 33 分钟才能阅读完成。

前言

Java 注解和反射是很根底的 Java 常识了,为何还要讲它呢?因为我在面试应聘者的过程中,发现不少面试者很少应用过注解和反射,甚至有人只能说出 @Override 这一个注解。我倡议大家还是尽量能在开发中应用注解和反射,有时候应用它们能让你事倍功半,简化代码进步编码的效率。很多优良的框架都根本应用了注解和反射,在 Spring AOP 中,就把注解和反射用得酣畅淋漓。

什么是注解

Java 注解(Annotation)亦叫 Java 标注,是 JDK5.0 开始引入的一种正文机制。注解能够用在类、接口,办法、变量、参数以及包等之上。注解能够设置存在于不同的生命周期中,例如 SOURCE(源码中),CLASS(Class 文件中,默认是此保留级别),RUNTIME(运行期中)。

注解以 @注解名 的模式存在于代码中,Java 中内置了一些注解,例如@Override,当然咱们也能够自定义注解。注解也能够有参数,例如 @MyAnnotation(value = “ 陈皮 ”)。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {} 

那注解有什么作用呢?其一是作为一种辅助信息,能够对程序做出一些解释,例如 @Override 注解作用于办法上,示意此办法是重写了父类的办法。其二,注解能够被其余程序读取,例如编译器,例如编译器会对被 @Override 注解的办法检测判断办法名和参数等是否与父类雷同,否则会编译报错;而且在运行期能够通过反射机制拜访某些注解信息。

内置注解

Java 中有 10 个内置注解,其中 6 个注解是作用在代码上的,4 个注解是负责注解其余注解的(即元注解),元注解提供对其余注解的类型阐明。

注解

作用

作用范畴

@Override

查看该办法是否是重写办法。如果其继承的父类或者实现的接口中并没有该办法时,会报编译谬误。

作用在代码上

@Deprecated

标记示意过期的,不举荐应用。能够用于润饰办法,属性,类。如果应用被此注解润饰的办法,属性或类,会报编译正告。

作用在代码上

@SuppressWarnings

通知编译器疏忽注解中申明的正告。

作用在代码上

@SafeVarargs

Java 7 开始反对,疏忽任何应用参数为泛型变量的办法或结构函数调用产生的正告。

作用在代码上

@FunctionalInterface

Java 8 开始反对,标识一个匿名函数或函数式接口。

作用在代码上

@Repeatable

Java 8 开始反对,标识某注解能够在同一个申明上应用屡次。

作用在代码上

@Retention

标识这个注解的保留级别,是只在代码中,还是编入 class 文件中,或者是在运行时能够通过反射拜访。蕴含关系 runtime>class>source。

作用在其余注解上,即元注解

@Documented

标记这些注解是否蕴含在用户文档中 javadoc。

作用在其余注解上,即元注解

@Target

标记某个注解的应用范畴,例如作用办法上,类上,属性上等等。如果注解未应用 @Target,则注解能够用于任何元素上。

作用在其余注解上,即元注解

@Inherited

阐明子类能够继承父类中的此注解,但这不是真的继承,而是能够让子类 Class 对象应用 getAnnotations()获取父类被 @Inherited 润饰的注解

作用在其余注解上,即元注解

自定义注解

应用 @interface 关键字自定义注解,其实底层就是定义了一个接口,而且主动继承 java.lang.annotation.Annotation 接口。

咱们自定义一个注解如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {String value();
} 

咱们应用命令 javap 反编译咱们定义的 MyAnnotation 注解的 class 文件,结果显示如下。尽管注解隐式继承了 Annotation 接口,然而 Java 不容许咱们显示通过 extends 关键字继承 Annotation 接口甚至其余接口,否则编译报错。

D:>javap MyAnnotation.class
Compiled from "MyAnnotation.java"
public interface com.nobody.MyAnnotation extends java.lang.annotation.Annotation {public abstract java.lang.String value();
} 

注解的定义内容如下:

  • 格局为 public @interface 注解名 {定义内容}
  • 外部的每一个办法理论是申明了一个参数,办法的名称就是参数的名称。
  • 返回值类型就是参数的类型,而且返回值类型只能是根本类型(int,float,long,short,boolean,byte,double,char),Class,String,enum,Annotation 以及上述类型的数组模式。
  • 如果定义了参数,可通过 default 关键字申明参数的默认值,若不指定默认值,应用时就肯定要显示赋值,而且不容许应用 null 值,个别会应用空字符串或者 0。
  • 如果只有一个参数,个别参数名为 value,因为应用注解时,赋值能够不显示写出参数名,间接写参数值。
import java.lang.annotation.*;

/**
 * @Description 自定义注解
 * @Author Mr.nobody
 * @Date 2021/3/30
 * @Version 1.0
 */
@Target(ElementType.METHOD) // 此注解只能用在办法上。@Retention(RetentionPolicy.RUNTIME) // 此注解保留在运行期间,能够通过反射拜访。@Inherited // 阐明子类能够继承此类的此注解。@Documented // 此注解蕴含在用户文档中。public @interface CustomAnnotation {String value(); // 应用时须要显示赋值
    int id() default 0; // 有默认值,应用时能够不赋值} 
/**
 * @Description 测试注解
 * @Author Mr.nobody
 * @Date 2021/3/30
 * @Version 1.0
 */
public class TestAnnotation {// @CustomAnnotation(value = "test") 只能注解在办法上,这里会报错
    private String str = "Hello World!";

    @CustomAnnotation(value = "test")
    public static void main(String[] args) {System.out.println(str);
    }
} 

Java8 注解

在这里解说下 Java8 之后的几个注解和新个性,其中一个注解是 @FunctionalInterface,它作用在接口上,标识是一个函数式接口,即只有有一个形象办法,然而能够有默认办法。

@FunctionalInterface
public interface Callback<P,R> {public R call(P param);
} 

还有一个注解是 @Repeatable,它容许在同一个地位应用多个雷同的注解,而在 Java8 之前是不容许的。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(OperTypes.class)
public @interface OperType {String[] value();} 
// 能够了解 @OperTypes 注解作为接管同一个类型上反复 @OperType 注解的容器
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperTypes {OperType[] value();} 
@OperType("add")
@OperType("update")
public class MyClass {} 

留神,对于反复注解,不能再通过 clz.getAnnotation(Class annotationClass)办法来获取反复注解,Java8 之后,提供了新的办法来获取反复注解,即 clz.getAnnotationsByType(Class annotationClass)办法。

package com.nobody;

import java.lang.annotation.Annotation;

/**
 * @Description
 * @Author Mr.nobody
 * @Date 2021/3/31
 * @Version 1.0
 */
@OperType("add")
@OperType("update")
public class MyClass {public static void main(String[] args) {
        Class<MyClass> clz = MyClass.class;
        Annotation[] annotations = clz.getAnnotations();
        for (Annotation annotation : annotations) {System.out.println(annotation.toString());
        }

        OperType operType = clz.getAnnotation(OperType.class);
        System.out.println(operType);

        OperType[] operTypes = clz.getAnnotationsByType(OperType.class);
        for (OperType type : operTypes) {System.out.println(type.toString());
        }
    }

}

// 输入后果为
@com.nobody.OperTypes(value=[@com.nobody.OperType(value=[add]), @com.nobody.OperType(value=[update])])
null
@com.nobody.OperType(value=[add])
@com.nobody.OperType(value=[update]) 

在 Java8 中,ElementType 枚举新增了两个枚举成员,别离为 TYPE_PARAMETER 和 TYPE_USE,TYPE_PARAMETER 标识注解能够作用于类型参数,TYPE_USE 标识注解能够作用于标注任意类型(除了 Class)。

Java 反射机制

咱们先理解下什么是动态语言和动静语言。动静语言是指在运行时能够扭转其本身构造的语言。例如新的函数,对象,甚至代码能够被引进,已有的函数能够被删除或者构造上的一些变动。简略说即是在运行时代码能够依据某些条件扭转本身构造。动静语言次要有 C#,Object-C,JavaScript,PHP,Python 等。动态语言是指运行时构造不可扭转的语言,例如 Java,C,C++ 等。

Java 不是动静语言,然而它能够称为准动静语言,因为 Java 能够利用反射机制取得相似动静语言的个性,Java 的动态性让它在编程时更加灵便。

反射机制容许程序在执行期借助于 Reflection API 获得任何类的外部信息,并能间接操作任意对象的外部属性以及办法等。类在被加载完之后,会在堆内存的办法区中生成一个 Class 类型的对象,一个类只有一个 Class 对象,这个对象蕴含了类的构造信息。咱们能够通过这个对象看到类的构造。

比方咱们能够通过 Class clz = Class.forName("java.lang.String"); 取得 String 类的 Class 对象。咱们晓得每个类都隐式继承 Object 类,Object 类有个 getClass() 办法也能获取 Class 对象。

Java 反射机制提供的性能

  1. 在运行时判断任意一个对象所属的类
  2. 在运行时结构任意一个类的对象
  3. 在运行时判断任意一个类具备的成员变量和办法
  4. 在运行时获取泛型信息
  5. 在运行时调用任意一个对象的成员变量和办法
  6. 在运行时获取注解
  7. 生成动静代理

Java 反射机制的优缺点

  • 长处:实现动静创建对象和编译,有更加的灵活性。
  • 毛病:对性能有影响。应用反射其实是一种解释操作,即通知 JVM 咱们想要做什么,而后它满足咱们的要求,所以总是慢于间接执行雷同的操作。

Java 反射相干的次要 API

  • java.lang.Class:代表一个类
  • java.lang.reflect.Method:代表类的办法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的结构器

咱们晓得在运行时通过反射能够精确获取到注解信息,其实以上类(Class,Method,Field,Constructor 等)都间接或间接实现了 AnnotatedElement 接口,并实现了它定义的办法,AnnotatedElement 接口的作用次要用于示意正在 JVM 中运行的程序中已应用注解的元素,通过该接口提供的办法能够获取到注解信息。

java.lang.Class 类

在 Java 反射中,最重要的是 Class 这个类了。Class 自身也是一个类。当程序想要应用某个类时,如果此类还未被加载到内存中,首先会将类的 class 文件字节码加载到内存中,并将这些静态数据转换为办法区的运行时数据结构,而后生成一个 Class 类型的对象(Class 对象只能由零碎创立),一个类只有一个 Class 对象,这个对象蕴含了类的构造信息。咱们能够通过这个对象看到类的构造。每个类的实例都会记得本人是由哪个 Class 实例所生成的。

通过 Class 对象能够晓得某个类的属性,办法,结构器,注解,以及实现了哪些接口等信息。留神,只有 class,interface,enum,annotation,primitive type,void,[] 等才有 Class 对象。

package com.nobody;

import java.lang.annotation.ElementType;
import java.util.Map;

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

        // 类
        Class<MyClass> myClassClass = MyClass.class;
        // 接口
        Class<Map> mapClass = Map.class;
        // 枚举
        Class<ElementType> elementTypeClass = ElementType.class;
        // 注解
        Class<Override> overrideClass = Override.class;
        // 原生类型
        Class<Integer> integerClass = Integer.class;
        // 空类型
        Class<Void> voidClass = void.class;
        // 一维数组
        Class<String[]> aClass = String[].class;
        // 二维数组
        Class<String[][]> aClass1 = String[][].class;
        // Class 类也有 Class 对象
        Class<Class> classClass = Class.class;

        System.out.println(myClassClass);
        System.out.println(mapClass);
        System.out.println(elementTypeClass);
        System.out.println(overrideClass);
        System.out.println(integerClass);
        System.out.println(voidClass);
        System.out.println(aClass);
        System.out.println(aClass1);
        System.out.println(classClass);
    }
}

// 输入后果
class com.nobody.MyClass
interface java.util.Map
class java.lang.annotation.ElementType
interface java.lang.Override
class java.lang.Integer
void
class [Ljava.lang.String;
class [[Ljava.lang.String;
class java.lang.Class 

获取 Class 对象的办法

  1. 如果晓得具体的类,可通过类的 class 属性获取,这种办法最安全可靠并且性能最高。Class clz = User.class;
  2. 通过类的实例的 getClass()办法获取。Class clz = user.getClass();
  3. 如果晓得一个类的全限定类名,并且在类门路下,可通过 Class.forName()办法获取,然而可能会抛出 ClassNotFoundException。Class clz = Class.forName("com.nobody.User");
  4. 内置的根本数据类型能够间接通过类名.Type 获取。Class<Integer> clz = Integer.TYPE;
  5. 通过类加载器 ClassLoader 获取

Class 类的罕用办法

  • public static Class<?> forName(String className):创立一个指定全限定类名的 Class 对象
  • public T newInstance():调用 Class 对象所代表的类的无参构造方法,创立一个实例
  • public String getName():返回 Class 对象所代表的类的全限定名称。
  • public String getSimpleName():返回 Class 对象所代表的类的简略名称。
  • public native Class<? super T> getSuperclass():返回 Class 对象所代表的类的父类的 Class 对象,这是一个本地办法
  • public Class<?>[] getInterfaces():返回 Class 对象的接口
  • public Field[] getFields():返回 Class 对象所代表的实体的 public 属性 Field 对象数组
  • public Field[] getDeclaredFields():返回 Class 对象所代表的实体的所有属性 Field 对象数组
  • public Field getDeclaredField(String name):获取指定属性名的 Field 对象
  • public Method[] getDeclaredMethods():返回 Class 对象所代表的实体的所有 Method 对象数组
  • public Method getDeclaredMethod(String name, Class<?>… parameterTypes):返回指定名称和参数类型的 Method 对象
  • myClassClass.getDeclaredConstructors();:返回所有 Constructor 对象的数组
  • public ClassLoader getClassLoader():返回以后类的类加载器

在反射中常常会应用到 Method 的 invoke 办法,即public Object invoke(Object obj, Object... args),咱们简略阐明下:

  • 第一个 Object 对应原办法的返回值,若原办法没有返回值,则返回 null。
  • 第二个 Object 对象对应调用办法的实例,若原办法为静态方法,则参数 obj 可为 null。
  • 第二个 Object 对应若原办法形参列表,若参数为空,则参数 args 为 null。
  • 若原办法申明为 private 润饰,则调用 invoke 办法前,须要显示调用办法对象的 method.setAccessible(true)办法,才可拜访 private 办法。

反射操作泛型

泛型是 JDK 1.5 的一项新个性,它的实质是参数化类型(Parameterized Type)的利用,也就是说所操作的数据类型被指定为一个参数,在用到的时候再指定具体的类型。这种参数类型能够用在类、接口和办法的创立中,别离称为泛型类、泛型接口和泛型办法。

在 Java 中,采纳泛型擦除的机制来引入泛型,泛型能编译器应用 javac 时确保数据的安全性和免去强制类型转换问题,泛型提供了编译时类型平安检测机制,该机制容许程序员在编译时检测到非法的类型。并且一旦编译实现,所有和泛型无关的类型会被全副擦除。

Java 新增了 ParameterizedTypeGenericArrayTypeTypeVariableWildcardType等几种类型,能让咱们通过反射操作这些类型。

  • ParameterizedType:示意一种参数化类型,比方 Collection<String>
  • GenericArrayType:示意种元素类型是参数化类型或者类型变量的数组类型
  • TypeVariable:是各种类型变量的公共父接口
  • WildcardType:代表种通配符类型表达式
package com.nobody;

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

public class TestReflectGenerics {public Map<String, Person> test(Map<String, Integer> map, Person person) {return null;}

    public static void main(String[] args) throws NoSuchMethodException {
        // 获取 test 办法对象
        Method test = TestReflectGenerics.class.getDeclaredMethod("test", Map.class, Person.class);
        // 获取办法 test 的参数类型
        Type[] genericParameterTypes = test.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);
                }
            }
        }

        // 获取办法 test 的返回值类型
        Type genericReturnType = test.getGenericReturnType();
        System.out.println("返回值类型:" + genericReturnType);
        // 如果参数类型等于参数化类型
        if (genericReturnType instanceof ParameterizedType) {
            // 取得实在参数类型
            Type[] actualTypeArguments =
                    ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {System.out.println(" " + actualTypeArgument);
            }
        }

    }
}

class Person {}

// 输入后果
办法参数类型:java.util.Map<java.lang.String, java.lang.Integer>
    class java.lang.String
    class java.lang.Integer
办法参数类型:class com.nobody.Person
返回值类型:java.util.Map<java.lang.String, com.nobody.Person>
    class java.lang.String
    class com.nobody.Person 

反射操作注解

在 Java 运行时,通过反射获取代码中的注解是比拟罕用的伎俩了,获取到了注解之后,就能晓得注解的所有信息了,而后依据信息进行相应的操作。上面通过一个例子,获取类和属性的注解,解析映射为数据库中的表信息。

package com.nobody;

import java.lang.annotation.*;

public class AnalysisAnnotation {public static void main(String[] args) throws Exception {Class<?> aClass = Class.forName("com.nobody.Book");
        // 获取类的指定注解,并且获取注解的值
        Table annotation = aClass.getAnnotation(Table.class);
        String value = annotation.value();
        System.out.println("Book 类映射的数据库表名:" + value);

        java.lang.reflect.Field bookName = aClass.getDeclaredField("bookName");
        TableField annotation1 = bookName.getAnnotation(TableField.class);
        System.out.println("bookName 属性映射的数据库字段属性 - 列名:" + annotation1.colName() + ", 类型:"
                + annotation1.type() + ", 长度:" + annotation1.length());
        java.lang.reflect.Field price = aClass.getDeclaredField("price");
        TableField annotation2 = price.getAnnotation(TableField.class);
        System.out.println("price 属性映射的数据库字段属性 - 列名:" + annotation2.colName() + ", 类型:"
                + annotation2.type() + ", 长度:" + annotation2.length());
    }
}

// 作用于类的注解,用于解析表数据
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table {
    // 表名
    String value();}

// 作用于字段,用于解析表列
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface TableField {
    // 列名
    String colName();

    // 列类型
    String type();

    // 长度
    int length();}

@Table("t_book")
class Book {@TableField(colName = "name", type = "varchar", length = 15)
    String bookName;
    @TableField(colName = "price", type = "int", length = 10)
    int price;
}

// 输入后果
Book 类映射的数据库表名:t_book
bookName 属性映射的数据库字段属性 - 列名:name, 类型:varchar, 长度:15
price 属性映射的数据库字段属性 - 列名:price, 类型:int, 长度:10 

性能剖析

后面咱们说过,反射对性能有肯定影响。因为反射是一种解释操作,它总是慢于间接执行雷同的操作。而且 Method,Field,Constructor 都有 setAccessible()办法,它的作用是开启或禁用拜访安全检查。如果咱们程序代码中用到了反射,而且此代码被频繁调用,为了进步反射效率,则最好禁用拜访安全检查,即设置为 true。

package com.nobody;

import java.lang.reflect.Method;

public class TestReflectSpeed {

    // 10 亿次
    private static int times = 1000000000;

    public static void main(String[] args) throws Exception {test01();
        test02();
        test03();}

    public static void test01() {Teacher t = new Teacher();
        long start = System.currentTimeMillis();
        for (int i = 0; i < times; i++) {t.getName();
        }
        long end = System.currentTimeMillis();
        System.out.println("一般形式执行 10 亿次耗费:" + (end - start) + "ms");
    }

    public static void test02() throws Exception {Teacher teacher = new Teacher();
        Class<?> aClass = Class.forName("com.nobody.Teacher");
        Method getName = aClass.getDeclaredMethod("getName");
        long start = System.currentTimeMillis();
        for (int i = 0; i < times; i++) {getName.invoke(teacher);
        }
        long end = System.currentTimeMillis();
        System.out.println("反射形式执行 10 亿次耗费:" + (end - start) + "ms");
    }

    public static void test03() throws Exception {Teacher teacher = new Teacher();
        Class<?> aClass = Class.forName("com.nobody.Teacher");
        Method getName = aClass.getDeclaredMethod("getName");
        getName.setAccessible(true);
        long start = System.currentTimeMillis();
        for (int i = 0; i < times; i++) {getName.invoke(teacher);
        }
        long end = System.currentTimeMillis();
        System.out.println("敞开安全检查反射形式执行 10 亿次耗费:" + (end - start) + "ms");
    }

}

class Teacher {

    private String name;

    public String getName() {return name;}

    public void setName(String name) {this.name = name;}
}

// 输入后果
一般形式执行 10 亿次耗费:13ms
反射形式执行 10 亿次耗费:20141ms
敞开安全检查反射形式执行 10 亿次耗费:8233ms 

通过试验可知,反射比间接执行雷同的办法慢了很多,特地是当反射的操作被频繁调用时成果更显著,当然通过敞开安全检查能够进步一些速度。所以,喷射也不应该泛滥成灾的,而是适度应用能力施展最大作用。

正文完
 0