infinite-war'sBLOG
CARD
Mitchell
issue, func, flow, std, solve
30 | 15 | 22
SKIN
SETTINGS
Night Shift
Disable Canvas
简体中文
中文
|
English
|
日本語
PAGE CONTENT
Java 注解与反射
2022-01-18
2022-01-18
4.5k
35 min.

# Java注解与反射

参考视频:BV1p4411P7V3

CSDN:https://blog.csdn.net/INFINITE_WAR/article/details/122568162

# 注解

Java.Annotation。与注释不同,注解不仅可以给人看,还可以用于程序运行。

Annotation可以附加在package、class、method、field等之上,用于添加额外的辅助信息,可以通过反射机制编程实现对这些元数据的访问。

@注解名
//代码......

# 常见内置注解

  • @Override:定义载java.lang.Override中,此注解只适用于修辞方法,表示一个方法声明打算重载超类中的另一个方法声明。
  • @Deprecated:定义在java.lang.Deprecated中,表示不鼓励程序员使用这样的元素。
  • @SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息。需要添加参数,如@SuppressWarnings("all")@SuppressWarnings("unchecked")@SuppressWarnings(value={"unchecked","deprecation})

# 元注解

目的:用于注解其他注解

java.lang.annotation

  • @Target:用于描述注解的适用范围(即:被描述的注解可以用在什么地方)
  • @Retention:表示需要载什么级别保存该注释信息,用于描述注解的生命周期(SOURCE<CLASS<RUNTIME)
  • @Document:说明该注解将被包含在javadoc中
  • @inherited:说明子类可以继承父类中的该注解

在这里插入图片描述

在这里插入图片描述

package Test;


import java.lang.annotation.*;

@MyAnnotation
public class Test{
    
    @MyAnnotation
    public void test(){
        
    }
}

//定义一个注解
@Target(value={ElementType.METHOD,ElementType.TYPE})  //表示可以用在方法或类上
@Retention(value= RetentionPolicy.RUNTIME)
@Documented
@Inherited //子类可以继承父类的注解
@interface MyAnnotation{
    
}

# 自定义注解

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

package Test;

import java.lang.annotation.*;

@MyAnnotation(name="class")
public class Test{

    @MyAnnotation2("aaaa")  //不写复制对象时,默认对value赋值
    public void test(){

    }
}

//定义一个注解
@Target(value={ElementType.METHOD,ElementType.TYPE})  //表示可以用在方法或类上
@Retention(value= RetentionPolicy.RUNTIME)
@Documented
@Inherited //子类可以继承父类的注解
@interface MyAnnotation{
    //注解的参数:参数类型+参数名()
    //default用来设置默认值,若不设置,则必须在写注解时显式赋值
    String name() default "";
    int age() default 0;
    int id() default -1;

    String[] schools() default {"xx大学","yy大学"};
}

@Target(value={ElementType.METHOD,ElementType.TYPE})  //表示可以用在方法或类上
@Retention(value= RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
    String value();
}

# 反射

# 0.概述

java.Reflection

动态语言:C#,python,javascript等。运行时可以根据条件改变结构

静态语言:C,C++,java等。运行时结构不变

反射使java拥有了动态的特性,如运行时创建类

反射机制允许程序在执行时借助Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

Class c=Class.forName("java.lang.String")

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

在这里插入图片描述

优点:灵活性更高

缺点:对性能有影响

# 1.Class类

  • Class对象只能由系统建立
  • 一个加载的类在JVM中只会由一个Class实例
  • 一个Class对象对应的时一个加载到JVM中的一个.class文件
  • 每个类的实例都会记得自己是由哪个Class实例所生成的
  • 通过Class可以完整地得到一个类中的所有被加载的结构
  • Class类是Reflection的根源,针对任何想要动态加载、允许的类,必须先获得相应的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 getMethod(String name,Class.. T) 返回一个Method对象,此最想的形参类型为param Type
Field[] getDeclaredFields() 返回Fields对象的一个数组

# 获取Class对象

以下类型拥有Class对象(即可以使用 类名称.class 获取Class对象):

  • 类:外部类,成员类,局部内部类,匿名内部类
  • interface:接口
  • []:数组
  • enum:枚举
  • annotation:注解@interface
  • primitive type:基本数据类型
  • void
package Test;

public class Test extends Object{
    public static void main(String[] args) throws ClassNotFoundException {
        Person person=new Student();
        System.out.println("这个人是:"+person.type);

        //方法一:通过对象获得
        Class c1=person.getClass();
        System.out.println(c1.hashCode());

        //方法二:forname获得
        Class c2=Class.forName("Test.Student");
        System.out.println(c2.hashCode());

        //方法三:通过类名.class获得
        Class c3=Student.class;
        System.out.println(c3.hashCode());

        //方法四:基本内置类型的包装类都有一个Type属性
        Class c4=Integer.TYPE;
        System.out.println(c4);
        
        //获得父类类型
        Class c5 = c1.getSuperclass();
        System.out.println(c5);
    }
}


class Person{
    public String type;

    public Person() {
    }

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

class Student extends Person{
    public Student() {
        this.type="学生";
    }
}

class Teacher extends Person{
    public Teacher() {
        this.type="老师";
    }
}

在这里插入图片描述

package Test;

public class Test extends Object{
    public static void main(String[] args) throws ClassNotFoundException {
        
        //通过反射获取类的Class对象
        Class c1=Class.forName("Test.User");
        System.out.println(c1);
        
        Class c2=Class.forName("Test.User");
        Class c3=Class.forName("Test.User");
        Class c4=Class.forName("Test.User");
        //一个类在内存中只有一个Class对象
        //一个类加载后,类的整个结构都会被封装在Class对象中
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());
        System.out.println(c4.hashCode());
    }
}


//实体类
class User{
    private String name;
    private int id;
    private int age;

    //constructor
	//getter()+setter()
}

在这里插入图片描述

# 2.Java内存分析

在这里插入图片描述

# 2.1 类加载过程

在这里插入图片描述

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

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

  • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题。
  • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
  • 解析:JVM常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

初始化(JVM自动完成):

  • 执行类构造器<clinit>()方法的过程。该方法是由编译时自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(构造的是类而不是对象)
  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
  • JVM会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。
package Test;

public class Test extends Object{
    public static void main(String[] args){
        A a=new A();
        System.out.println(A.m);
        /*
        1.加载到内存,会产生一个类对应的Class对象
        2.链接,链接结束后m=0
        3.初始化
            <clinit>(){
                System.out.println(“A类静态代码块初始化”);
                m=300;
                m=100;
            }
            m最终赋值为100
        */
    }
}

class A{
    static {
        System.out.println("A类静态代码块初始化");
        m=300;
    }
    static int m=100;
    public A(){
        System.out.println("A类的无参构造初始化");
    }
}

在这里插入图片描述

在这里插入图片描述

# 2.2 类的初始化

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

  • JVM启动时,先初始化main方法所在的类
  • new一个类的对象
  • 调用类的静态成员(除了final常量)个静态方法
  • 使用java.lang.reflect包的方法对类进行反射调用
  • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
package Test;

public class Test extends Object{

    static {
        System.out.println("Main类被加载");
    }

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

        //2.通过反射产生主动调用
        Class.forName("Test.Son");
    }
}

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

class Son extends Father{
    static {
        System.out.println("子类被加载");
        m=300;
    }

    static int m=100;
    static final int M=1;
}

在这里插入图片描述

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

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
  • 通过数组定义类引用,不会触发此类的初始化
  • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
package Test;

public class Test extends Object{

    static {
        System.out.println("Main类被加载");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        //不会产生类的引用的方法
        //1.调用类内静态变量
        //System.out.println(Son.b);

        //2.声明类的数组
        //Son[] son=new Son[5];

        //3.调用类内静态常量
        System.out.println(Son.M);

    }
}

//Father、Son类定义

调用类内静态变量

在这里插入图片描述

声明类的数组

在这里插入图片描述

调用类内静态常量

在这里插入图片描述

# 2.3 类加载器

类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法去的运行时数据结构,然后再队中生成一个代表这个类的java.lang.Class对象,作为方法区中数据的访问入口。

类缓存:标准的JavaEE类加载器中可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间,不过JVM垃圾回收机制可以回收这些Class对象。

在这里插入图片描述

在这里插入图片描述

package Test;

public class Test extends Object{
    public static void main(String[] args) throws ClassNotFoundException {
        //获取系统类的加载器
        ClassLoader systemClassLoader=ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);

        //获取系统类加载器的父类加载器:扩展类加载器
        ClassLoader parent=systemClassLoader.getParent();
        System.out.println(parent);

        //获取扩展类加载器的父类加载器:根加载器
        ClassLoader parent1=parent.getParent();
        System.out.println(parent1);

        //测试当前类是哪个加载器加载的
        ClassLoader classLoader=Class.forName("Test.Test").getClassLoader();
        System.out.println(classLoader);
        //测试JDK内置的类是谁加载的
        classLoader=Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classLoader);
        
        //如何获得系统类加载器可以加载的所有路径
        System.out.println(System.getProperty("java.class.path"));
    }
}

在这里插入图片描述

双亲委派机制:加入写了一个跟已有包重名的包(如自己写了java.lang.String),系统会自动选择原有的java.lang.String包,保证安全性。

# 3.获取类的运行时结构

通过反射获取运行时类的完整结构:Field、Method、Constructor、Superclass、Interface、Annotation

package Test;

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

public class Test extends Object{
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class c1=Class.forName("Test.User");

        //获得类的名字
        System.out.println(c1.getName());  //获得包名+类名
        System.out.println(c1.getSimpleName()); //获得类名

        //获得类的属性
        System.out.println("=================================================");
        //c1.getFields()只能获取public属性
        Field[] fields=c1.getDeclaredFields(); //获取所有类型的属性
        for(Field field:fields){
            System.out.println(field);
        }

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

        //获得类的方法
        System.out.println("==============================================");
        Method[] methods=c1.getMethods(); //获取当前类及其超类的所有方法
        for(Method method:methods){
            System.out.println(method);
        }

        methods=c1.getDeclaredMethods(); //获取当前类的所有方法
        for(Method method:methods){
            System.out.println(method);
        }

        //获得特定方法
        //重载
        Method getName=c1.getMethod("getName",null);
        Method setName=c1.getMethod("setName",String.class);
        System.out.println(getName);
        System.out.println(setName);


        //获得构造器
        System.out.println("==========================================");
        Constructor[] constructors=c1.getConstructors(); //只能获得public方法
        for(Constructor constructor:constructors){
            System.out.println(constructor);
        }
        constructors=c1.getDeclaredConstructors();
        for(Constructor constructor:constructors){
            System.out.println(constructor);
        }

        //获得指定构造器
        Constructor declaredConstructor=c1.getDeclaredConstructor(String.class,int.class,int.class);
        System.out.println(declaredConstructor);
    }
}

在这里插入图片描述

# 4.动态创建对象

package Test;

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

public class Test extends Object{
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //获取Class对象
        Class c1=Class.forName("Test.User");

        //通过反射构造一个对象
        User user=(User) c1.newInstance();  //本质是调用无参构造。若类里没有实现无参构造,就会报错
        System.out.println(user.toString());

        System.out.println("\n================================\n");
        //通过构造器创建对象
        Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);//指定构造器
        User user2=(User)constructor.newInstance("张三",001,10);
        System.out.println(user2.toString());

        System.out.println("\n================================\n");
        //通过反射调用普通方法
        User user3=(User)c1.newInstance();
        Method setName=c1.getDeclaredMethod("setName",String.class);
        //invoke为激活方法
        setName.invoke(user3,"李四");
        System.out.println(user3.toString());

        System.out.println("\n================================\n");
        //通过反射操作属性
        User user4=(User)c1.newInstance();
        Field name=c1.getDeclaredField("name");

        //User中的name是private类型,通过下面方法设置为可以直接访问
        name.setAccessible(true);
        name.set(user4,"王五");
        System.out.println(user4.toString());
    }
}

在这里插入图片描述

Object invoke(Object obj,Object ... args)

  • Object对应原方法的返回值,若原方法无返回值,此时返回null
  • 若原方法为静态方法,此时形参Object obj可以为null
  • 若原方法形参列表为空,则Object[] args为null
  • 若原方法声明为private,则需要再使用invoke前,显示调用方法对象的setAccessible(true)方法,将可以访问private方法(属性同理)

setAccessible(boolean x)

  • Method、Field、Constructor对象都有setAccessible()方法
  • setAccessible作用是启动和禁用访问安全检查的开关。默认为false,说明反射的对象应该实施Java语言访问检查。true代表反射的对象在使用时应该取消Java语言访问检查

# 性能分析

package Test;

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

public class Test extends Object{
    //普通方式
    public static void test1(){
        User user=new User();
        long startTime=System.currentTimeMillis();

        for(int i=0;i<1000000000;i++){
            user.getName();
        }
        long endTime=System.currentTimeMillis();
        System.out.println("普通方式执行10亿次:"+(endTime-startTime)+"ms");
    }

    //反射方式
    public static void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user=new User();
        Class c1=user.getClass();

        Method getName=c1.getDeclaredMethod("getName",null);

        long startTime=System.currentTimeMillis();

        for(int i=0;i<1000000000;i++){
            getName.invoke(user,null);
        }
        long endTime=System.currentTimeMillis();
        System.out.println("反射方式执行10亿次:"+(endTime-startTime)+"ms");
    }

    //反射方式  关闭访问权限检测
    public static void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user=new User();
        Class c1=user.getClass();

        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 endTime=System.currentTimeMillis();
        System.out.println("反射方式(关闭检测)执行10亿次:"+(endTime-startTime)+"ms");
    }


    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        test1();
        test2();
        test3();
    }
}

在这里插入图片描述

# 5.获取泛型信息

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

为了通过反射操作这些类型,Java新增了集中类型来代表不能被归到Class类中的类型 但是 又和原始类型其名的类型:

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


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 Test extends Object{

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

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

    public static void main(String[] args) throws NoSuchMethodException {
        Method method=Test.class.getMethod("test1",Map.class,List.class);

        Type[] genericParameterTypes=method.getGenericParameterTypes();
        for(Type type:genericParameterTypes){
            System.out.println("#"+type);
            if(type instanceof ParameterizedType){
                Type[] actualTypeArguments=((ParameterizedType)type).getActualTypeArguments();
                for(Type actualTypeArgument:actualTypeArguments){
                    System.out.println(actualTypeArgument);
                }
            }
        }

        method=Test.class.getMethod("test2",null);
        Type genericReturnType=method.getGenericReturnType();

        if(genericReturnType instanceof ParameterizedType){
            Type[] actualTypeArguments=((ParameterizedType)genericReturnType).getActualTypeArguments();
            for(Type actualTypeArgument:actualTypeArguments){
                System.out.println(actualTypeArgument);
            }
        }

    }
}

在这里插入图片描述

# 6.获取注解信息

package Test;

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

public class Test{

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("Test.Student");
        //通过反射获得注解
        Annotation[] annotations=c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

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

        System.out.println("=======================================");
        //获得类指定的注解
        Field f=c1.getDeclaredField("name");
        MyField annotation=f.getAnnotation(MyField.class);
        System.out.println(annotation.coloumName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());
    }
}

//学生类
@MyTable("db_student")
class Student{
    @MyField(coloumName = "db_id",type = "int",length = 10)
    private int id;
    @MyField(coloumName = "db_age",type = "int",length = 10)
    private int age;
    @MyField(coloumName = "db_name",type = "varchar",length = 3)
    private String name;

    //constructor...
    //getter、setter...
    //toString...
}


//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTable{
    String value();
}

//属性的注解
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface MyField{
    String coloumName();
    String type();
    int length();
}

在这里插入图片描述

The post above ended Thanks for your reading
Come on! Write some comments, and your suggestions will improve the quality of my creative!
FRIEND ME
QQ
WeChat
Post Author: Mitchell
Post Link: /
Copyright Notice: All articles/posts in this website are licensed under BY-NC-SA unless stating additionally.
Enable Read Mode
COMMENTS
DEFAULT