您的当前位置:首页正文

Java的注解和反射

2024-11-30 来源:个人技术集锦

参考文献:

参考文献:

 

类的加载过程:

Java的内存分析

    1. 存放基本变量类型(会包含这个基本类型的具体数值)
    2. 引用对象的变量(会存放这个引用在堆里边的地址)
  • 方法区(Java 1.8 中已经没有这个概念了)

    1. 可以被所有的县成功向
    2. 包含了所有的Class和static变量
  • 类加载过程

    1. 当程序主动使用某个类时,如果该类还没有被加载到内存中,系统会通过如下三个步骤来对该类进行初始化。
    2. 类的加载 Load     ->   类的链接 Link   ->  类的初始化 initialize

 

  • 双亲委派机制

              类在加载过程中,是有很多个加载器的,顺序依次是:

              CustomClassLoader < AppClassLoader < ExtClassLoader < BootStrapClassLoader

              BootStrapClassLoader为根加载器,使用C的最基础的加载功能。

              通常在使用一个类的时候,会启动最底层的加载器,然后查看子类是否加载过,一层一层传递到顶层,如果都没有加载过,在由顶层判断是否可以加载,一层层传递到底层。

 

 

注解

  • 注解Annotation的概念

        1. 注解不是程序本身,可以对程序做解释,并且可以通过JDT来生成代码影响程序。

                如:@override 不会去影响原始代码的功能,但是会在编译时生成代码检测是否为覆盖父类方法的写法。

        2. 注解的使用范围:

                package,class,filed,method

        3. 注解有一定的约束,所以必须按照一定的格式使用

                内置注解

                        Override:定义在java.lang.Overrride中,此注解表示一个方法声明打算重写父类中的一个方法声名。

                        Deprecated:定义在java.lang.Deprecated中,表示不推荐使用这个field,class,method,其他程序调用该注解修饰的元素时会给与不推荐使用的提示。

                        SuppressWarnings:定义在java.lang.SuppressWarnings中,写在class,method中用来抑制编译时的警告信息。需要添加部分参数才可以使用:

                        SuppressWarning("all")

                        SuppressWarning("unchecked")

                        SuppressWarning(value={"unchecked","deprecation"})

                        ....

                元注解

                        元注解的作用是负责注解其他注解,自定义注解时需要使用元注解。

                        Java定义了4个标准的meta-annotation类型:

                                @Target:描述注解的使用范围

                                @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期( Source < Class < Runtime )

                                @Document:说明该注解将被包含在javaDoc中

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

                实例代码:

//自定义一个注解,类前需要使用@interface表示这个是一个注解
//使用位置:类方法
@Target(value={ElementType.TYPE,ElementType.METHOD})
//使用生效范围:运行时注解生效runtime>class>sources
@Retention(RetentionPolicy.RUNTIME)
//该注解被写入到JavaDoc中
@Documented
//改注解可以被子类继承
@Inherited
@interface MyAnnotation{

}

@Target(value={ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@interface MyAnnotation2{
    //注解的参数:参数类型+参数名()
    //如果增加了default表示可以不写,后边自动给默认值
    Stringname() default"";
    intage();
    String[] schools() default{"school1","school2"};
}

 

反射

  • 反射的概念

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

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

  • ​​​​​类的信息

          类加载完成之后,内存中会出现一个Class类型的对象,这个对象里包含了预置类型的所有基本信息。我们可以通过这个对象看到类内的结构,可以获得如下信息:

  1. 运行时判断一个对象所属的类
  2. 运行时判断一个类所具有的成员变量和方法
  3. 运行时获取泛型信息
  4. 运行时调用一个对象的成员变量和方法
  5. 运行时处理注解
  6. 生成动态代理(AOP中常用)
  • 类的equal和==

一个类,在JVM中只能生成一个.class类,无论被实现了多少次。这个Class相当于是一个模板。

    public static void main(String[] args) {
        String a = "abc";
        String b = "xyz";
        Class class1 = a.getClass();
        Class class2 = b.getClass();
        if (class1.equals(class2)) {
            System.out.println("class1 equals class2");
        }
        if (class1 == class2) {
            System.out.println("class1 == class2");
        }
    }

      会输出class1和class2一样

 

  • 获取Class的方式
  1. 通过已经实例化后的对象,使用getClass方法获得
  2. 通过类的全路径,使用forname获得

              注意:如果出现多态情况,要根据实际开启内存的类型来确定Class获取的类型

public class ReflectTest2{
public static void main(String[]args) throws ClassNotFoundException{
        Personperson=newStudent();
        //方式一:通过对象获得
        Classc1=person.getClass();
        System.out.println(c1.hashCode());
    
        //方式二:通过forname方式获得
        Classc2=Class.forName("reflection.Person");
        System.out.println(c2.hashCode());

        //方式三:通过.class获得
        Classc3=Student.class;
        System.out.println(c3.hashCode());

        //方式四:通过内置类型的包装类都有一个Type属性获得(得提前设定)
        Classc4=Integer.TYPE;
        System.out.println(c4.hashCode());

        //方式五:在获取的class基础上,获得父类的类型
        Classc5=c1.getSuperclass();
        System.out.println(c5.hashCode());
    }    
}

class Person{
    public String name;
    public Person(){
    
    }
}

class Student extends Person{
    public Student(){
        this.name="student";
    }
}

 

 
  • 动态创建对象(使用反射创建对象)
  1. 创建对象:使用newInstance()方法进行类实例化
  2. 执行方法:通过getMethod得到Method类型的方法,之后通过invoke对象进行调用(private方法需要显示调用setAccessible(true)方法)
  3. 调用常量:通过调用getDeclaredField()获取常量信息,之后使用get()和set()方法就可以获取和设置属性的值
/**
*通过反射构建一个类,并使用类里的数据
*
*@paramargs
*@throwsClassNotFoundException
*/
@SneakyThrows
public static void main(String[] args) throws ClassNotFoundException{
    Classc1=Class.forName("reflection.User");
    
    //通过反射创建一个类
    Useruser=(User)c1.newInstance();

    //通过反射,获得一个方法invoke激活这个方法,并且只调用这个方法,
    MethodsetName=c1.getDeclaredMethod("setName",String.class);
    setName.invoke(user,"peter");

    //通过反射,获得一个field
    Fieldname=c1.getDeclaredField("name");    
    name.set(user,"tony");
}

注意,操作的时候不能对private field进行操作。如果必须要进行操作,需要将field的属性设置成true

name.setAccessible(true),开启对私有类型的检测的权限。

增加这个访问权限,本质是减少了检测method和field的权限类型,所以增加了可访问性,可以提高效率。

 

  • 反射操作泛型(软件分析,通过反射来获取方法中泛型中的类型)

通过反射的方式获取方法中的泛型的类型。

需要将方法提取出来,获取genericParameterTypes,之后将genericParameterTypes转换为ParameterTypes,调用ActualTypeArguments获取实际的类型。

其实和软件分析取类型是一样的方式。

// 参数的类型
public void method1 (Map<String,User>map, List<User>list){
    System.out.println("method1");
}

public void getmethodParamType() throws NoSuchMethodException{
    Methodmethod=ReflectTest4.class.getDeclaredMethod("method1",Map.class,List.class);
    Type[] genericParameterTypes=method.getGenericParameterTypes();//获取方法的参数类型
    for(TypegenericParameterType:genericParameterTypes){
        System.out.println("#"+genericParameterType);
        if(genericParameterTypeinstanceofParameterizedType){
            Type[] types=((ParameterizedType)genericParameterType).getActualTypeArguments();
            for(TypetypeResult:types){
                System.out.println("@"+typeResult);
            }
        }
    }
}

// 返回类型的查询
public Map<String,User>method2(){
    System.out.println("method2");
    return null;    
}

public void getMethodReturnType() throws NoSuchMethodException{
    Methodmethod=ReflectTest4.class.getDeclaredMethod("method2");
    Typegeneric ReturnType=method.getGenericReturnType();
    if(genericReturnTypeinstanceofParameterizedType){
        Type[] types=((ParameterizedType)genericReturnType).getActualTypeArguments();
        for(Typetype:types){
            System.out.println("ReturnType:"+type);
        }
    }
}
  • 反射操作注解

    类似于获取method参数类型

  1. 使用Class class = Class.forName()获取类型
  2. 调用class的getAnnotation,来获取注解信息
  3. 获取注解之后在继续根据实际需要的东西取具体的值

    注解可以通过反射操作源代码,程序可以通过反射获取注解内的信息从而实现数据库相关的操作。

 

  • 反射的效率

        使用反射方法时,需要注意反射调用的效率,通常情况下直接调用方法的效率最高,反射调用(去除检查)的效率其次,直接调用反射方法的效率最低。

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();
        long startTime1 = System.currentTimeMillis();
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            user.getAge();
        }
        System.out.println("直接调用时间:" + (System.currentTimeMillis() - startTime1) + "ms");


        Method method = c1.getDeclaredMethod("getAge");
        long startTime2 = System.currentTimeMillis();
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            method.invoke(user, null);
        }
        System.out.println("反射取方法的时间:" + (System.currentTimeMillis() - startTime2) + "ms");


        Method method3 = c1.getDeclaredMethod("getAge");
        method3.setAccessible(true);
        long startTime3 = System.currentTimeMillis();
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            method3.invoke(user, null);
        }
        System.out.println("反射取方法的时间(取消检测):" + (System.currentTimeMillis() - startTime3) + "ms");
    }

直接调用时间:3ms
反射取方法的时间:2656ms
反射取方法的时间(取消检测):2524ms

反射效率低的原因:method invoke方法会对参数做封装和解封;检查方法的可见性; 校验参数;

反射取消检测效率高是因为少了检查方法的可见性一个环节。

 

 

 

 

 

 

 

显示全文