[Java]注解和反射

一、注解

1、什么是注解?

  • Annotation是从JDK5.0开始引入的新技术。
  • 不是程序本身,可以对程序作出解释。
  • 可以被其他程序读取。
  • 注解是以@注解名在代码中存在的,还可以添加一些参数值。

2、内置注解

  • @Overide:表示一个方法声明打算重写超类中的一个方法,只适用修饰方法。
  • @Deprecated:表示不鼓励程序员使用这样的元素,可以修饰方法、属性、类。
  • @SuppressWarnings:用来抑制编译时的警告信息,需要添加一个参数使用,可以修饰方法和类。
  • @SuppressWarnings("all")
  • @SuppressWarnings("unchecked")
  • @SuppressWarnings(value={"unchecked","deprecation"})
  • ……

3、元注解

  • 元注解的作用就是负责注解其他注解,Java定义了四个标准的meta-annotation类型。
  • @Target:用于描述注解的适用范围。
  • @Retention:表示在什么级别保存该注解信息,用于描述注解的生命周期(SOURCE<CLASS<RUNTIME)。
  • @Documented:说明该注解将被包含在javadoc中。
  • @Inherited:说明子类可以继承父类中的该注解。

4、自定义注解

  • 使用@interface自定义注解。
  • 格式:public @interface 注解名{定义内容}。
  • 其中的每一个方法实际上是声明了一个配置参数。
  • 方法的名称就是参数名称。
  • 返回值类型就是参数的类型(返回值类型只能是基本类型,Class,String,enum)。
  • 可以通过default来声明参数的默认值。
  • 如果只有一个参数成员,一般参数名为value。
  • 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值。
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

    String name() default "";
    int age() default 0;
    int id() default -1;//默认为-1代表不存在

    String[] schools() default {"MIT","清华大学"};
}

二、反射

1、反射概述

  • 反射是Java被视为准动态语言的关键,反射允许程序执行期间借助Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
  • 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息
  • 通过Class.forName()获取类的反射对象。
  • 有Class对象的类型:
  • class:外部类、成员内部类、静态内部类、局部内部类、匿名内部类。
  • interface:接口。
  • []:数组。
  • enum:枚举。
  • annotation:注解@interface。
  • primitive type:基本数据类型。
  • void

2、获取Class类实例

  • 已知具体的类,通过类的class属性获取,这种方法最为安全可靠。,程序性能最高。
    Class clazz = Person.class;
  • 已知某个类的实例,调用该实例的getClass()方法获取Class对象。
    Class clazz = person.getClass();
  • 已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取。
    Class clazz = Class.forName("com.dance.entity");
  • 内置基本数据类型可以直接使用类名.Type。
  • 还可以使用ClassLoader。
  • 获取父类的class对象Class clazz = studnet.getSuperclass();

3、类的加载过程

  • 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象。
  • 链接:将Java类的二进制代码合并到JVM的运行状态职中的过程。
  • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题。
  • 准备:正式为类变量分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
  • 解析:JVM常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
  • 初始化:
  • 执行类构造器()方法的过程。类构造器()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
  • 当初始化一个类的时候,如果发现其父类还没有被初始化,则需要先触发其父类的初始化。
  • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
  • 代码测试:
public class Human {
    
    private static int num = 100;
    
    static {
        System.out.println("我是静态代码块!");
        num = 300;
    }
    
    public Human(){
        System.out.println("我是构造该类对象的构造器!");
    }

    public static void main(String[] args) {
        Human human = new Human();
        System.out.println(Human.num);
    }
}

结果:

我是静态代码块!
我是构造该类对象的构造器!
300

注意:假如将静态代码块和类变量交换顺序,num最后的结果为100,由此可知,静态代码块和类变量的执行顺序取决于代码编写顺序

4、类的初始化

  • 类的主动引用(一定会发生类的初始化)
  • 当虚拟机启动,先初始化main方法所在的类。
  • new一个类的对象。
  • 调用类的静态成员(除了final常量)和静态方法。
  • 使用java.lang.reflect包的方法进行反射调用。
  • 当初始化一个类的时候,如果其父类没有被初始化,则会先初始化它的父类。
  • 类的被动引用(不会发生类的初始化)
  • 当访问一个静态域的时候,只有声明这个域的类才会被初始化。例如:当通过子类引用父类的静态变量,不会导致子类初始化。
  • 通过数组定义类引用,不会触发类的初始化。
  • 引用常量不会触发此类的初始化(常量在链接阶段就已经存入调用类的常量池中了)。

5、类加载器

  • 类加载器的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象。
  • 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
  • JVM规范定义了如下类型的类加载器:
  • 引导类加载器(Bootstrap Classloader):用C++编写的,是JVM自带的加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取。
  • 扩展类加载器(Extension Classloader):负责jre/lib/ext目录下的jar包或-D java.ext.dirs指定目录下的jar包装入工作库。
  • 系统类加载器(System Classloader):负责java -classpath或-D java.class.path所指的目录下的类与jar包装入工作,是最常用的加载器。
    测试代码:
public class TestClassloader {


    public static void main(String[] args) {

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

        //获得拓展类加载器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);

        //获得引导类加载器
        ClassLoader bootstrapClassLoader  = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader);

        //系统类加载器可以加载的路径 
        System.out.println(System.getProperty("java.class.path"));
    }
}

6、获取运行时类的完整结构

public class Test01 {
    private int id;
    private String name;
    private boolean flag;

    public Test01(int id, String name, boolean flag) {
        this.id = id;
        this.name = name;
        this.flag = flag;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
        Test01 t1 = new Test01(1,"ddd",true);

        Class c1 = t1.getClass();
        
        System.out.println(c1.getName());//获取包名+类名
        System.out.println(c1.getSimpleName());//获取类名

        //Field[] fields = c1.getFields();找到public属性
        Field[] fields = c1.getDeclaredFields();//找到全部属性
        for (Field field:fields){
            System.out.println(field);
        }

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

        //Method[] methods = c1.getDeclaredMethods();获取本类和父类全部public方法
        Method[] methods = c1.getDeclaredMethods();//获取本类的全部方法
        for (Method method : methods) {
            System.out.println(method);
        }

        Method getName = c1.getDeclaredMethod("getName",null);//获得指定方法
        Method setName = c1.getDeclaredMethod("setName",String.class);
        System.out.println(getName);
        System.out.println(setName);

        //Constructor[] constructors = c1.getConstructors();获得public构造方法
        Constructor[] constructors = c1.getDeclaredConstructors();//获得全部构造方法
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
		
        //获得指定的构造器
        Constructor constructor = c1.getDeclaredConstructor(int.class, String.class, boolean.class);
        System.out.println(constructor);

    }
}

7、动态创建对象

Class c1 = User.class;

  • 通过反射对象创建一个实例对象。
    User user = (User)c1.newInstance();//本质上是调用类的无参构造
  • 通过构造器创建一个实例对象。
Constructor constructor = c1.getDeclaredConstructor(String.class,int.class,int.class);
User user = (User)constructor.newInstance("大傻",1,19);
  • 通过反射调用方法。
Method setName = c1.getDeclaredMethod("setName",String.class);
setName.invoke(user,"小傻");
  • 通过反射操作属性。
Field name = c1.getDeclaredField("name");
name.setAccessible(true);//破坏属性的私有性,也能提高效率;Method、Constructor也有此方法
name.set(user,"中傻");

8、反射操作泛型

  • ParameterizedType:表示一种参数化类型,比如Collection。
  • GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型。
  • TypeVariable:是各种类型变量的公共父接口。
  • WildcardType:代表一种通配符类型表达式。
public class Test02 {

    public void test01(Map<String,Test02> map, List<Test02> list){
        System.out.println("test01");
    }

    public Map<String,Test02> test02(){
        System.out.println("test02");
        return null;
    }


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

        Method method = Test02.class.getDeclaredMethod("test01", Map.class, List.class);
        Type[] genericParameterTypes = method.getGenericParameterTypes();//参数
        for (Type genericParameterType : genericParameterTypes) {
            if(genericParameterType instanceof ParameterizedType){
          Type[] actualTypeArguments = ((ParameterizedType)genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
        }

        Method method2 = Test02.class.getDeclaredMethod("test02", null);
        Type genericReturnType = method2.getGenericReturnType();//返回值
        if(genericReturnType instanceof ParameterizedType){
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }
    }

}

9、反射操作注解

public class Test03 {

    public static void main(String[] args) throws NoSuchFieldException {
        Class<Student> studentClass = Student.class;
        Annotation[] annotations = studentClass.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        //获取注解value的值
        Table table = studentClass.getAnnotation(Table.class);
        String value = table.value();
        System.out.println(value);

        //获得类指定注解
        Field name = studentClass.getDeclaredField("name");
        MyField annotation = name.getAnnotation(MyField.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());


    }
    
}

@Table("db_student")
class Student{

    @MyField(columnName = "db_id",type = "int",length = 10)
    private int id;

    @MyField(columnName = "db_age",type = "int",length = 10)
    private int age;

    @MyField(columnName = "db_name",type = "varchar",length = 3)
    private String name;

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table{
    String value();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyField{
    String columnName();
    String type();
    int length();
}