关于注解和反射

关于注解和反射

本博客中的所有内容都是来自b站狂神说视频教程,如有侵权联系我删除。

下面是视频链接:b站狂神说

什么是注解

annotation,注解,Java通过注解来让编译器读取,注解不影响代码

Java内置注解

Java四大元注解

@Target:解释其他注解的注解,表示注解可以用到哪些地方

@Retention: 表示什么时候这个注解有用,一般是RUNTIME运行时。

source:源码时有效。

class:编译后有效。

runtime:运行时有效。

source < class < runtime

代码示例

/**
 * 元注解
 *
 * @Date 16:14 2020/8/5
 */
public class Test01 {

  @mmyAnnotation
  public void Test1(){

  }
}
//定义一个注解:

/**
 * 有了type之后可以把注解放到类上,而不是方法上
 */
@Target(value = {ElementType.METHOD,ElementType.TYPE})
/**
 * Retention表示注解在什么时候有效
 */
@Retention(value = RetentionPolicy.RUNTIME)
//表示是否把注解生成在javadoc中。
@Documented
//子类是否可以继承父类的注解
@Inherited
@interface mmyAnnotation{

}

自定义注解

代码

通过一段代码来分析:

/**
 * 自定义注解
 *
 * @Date 15:59 2020/8/6
 */
public class Test02 {

  //注解可以显示赋值,如果没有赋值,就必须给注解默认值
  @myannotation2(name = "abc")
  public void test(){
  }

  @myAnnotation3("avc")
  public void test2(){
  }
}

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface myannotation2{
  //定义注解的参数  参数类型+ 参数名 ();
  //default添加默认值
  String name() default "";
  int age() default 12;
  int id() default -1;//如果默认值为-1,就表示不存在。
  String[] schools() default {"1,2,3"};
}

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

注解的定义方式:参数类型+ 参数名 ();

注解中只有value属性可以在注解使用时的括号内直接赋值,其他的不可以。

一个比较重要的:如果注解使用时不符值,就必须要提前有默认值。

反射机制

运用反射会影响效率,所以能不用就不用。

什么是反射

Reflection(反射),动态语言的关键,借助Reflection API取得任何类的内部信息,直接操作任何对象的内部属性和方法。(就牛逼的不行!)

hashcode相同,他们都指向同一个类。

getClass()方法是object的一个方法,Class本身也是一个类,由于所有类都继承object类,所以可以通过对象来找到类。

Class类的常用方法:

Class类的创建方式:

package com.grss.reflection;

/**
 * 测试class类的创建方式
 *
 * @Date 17:32 2020/8/6
 */
public class Test02 {
  public static void main(String[] args) throws ClassNotFoundException {
    Person person = new Student();
    System.out.println("这个人是:"+person.name);

    //方式一,通过对象获得
    Class<? extends Person> c1 = person.getClass();
    System.out.println(c1.hashCode());

    //方式二,通过forname获取
    Class<?> c2 = Class.forName("com.grss.reflection.Student");
    System.out.println(c2.hashCode());

    //方式三,通过类名来获得
    Class<Student> c3 = Student.class;
    System.out.println(c3.hashCode());

    //方式四,基本内置类型的包装类都有一个Type属性
    Class<Integer> c4 = Integer.TYPE;
    System.out.println(c4);

    //获得一个类的父类
    Class<?> c5 = c1.getSuperclass();
    System.out.println(c5);
  }
}

class Person{
  public String name;

  public Person() {
  }

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

  @Override
  public String toString() {
    return "Person{" +
            "name='" + name + '\'' +
            '}';
  }
}

class Student extends Person{
  public Student() {
    this.name="学生";
  }
}
class Teacher extends Person{
  public Teacher(){
    this.name="老师";
  }
}

效果图:

上面介绍了三种常规的获得类对象的方法,和一种只有内置类的包装类的方式获得类对象的方法。

.getSuperclass():获得一个类的父类。

所有类的Class对象

package com.grss.reflection;

import java.lang.annotation.ElementType;

/**
 * 所有类的Class
 *
 * @Date 18:00 2020/8/6
 */
public class Test03 {
  public static void main(String[] args) {
    Class c1 = Object.class;          //类
    Class c2 = Comparable.class;   //接口
    Class c3 = String[].class;      //一维数组
    Class c4 = int[][].class;        //二维数组
    Class c5 = Override.class;      //注解
    Class c6 = ElementType.class; //枚举类型
    Class c7 = Integer.class;        //基本数据类型
    Class c8 = void.class;              //void
    Class c9 = Class.class;            //Class

    System.out.println(c1);
    System.out.println(c2);
    System.out.println(c3);
    System.out.println(c4);
    System.out.println(c5);
    System.out.println(c6);
    System.out.println(c7);
    System.out.println(c8);
    System.out.println(c9);

    //只要元素类型和维度一样,就都指向同一个类
    int[] a= new int[10];
    int[] b= new int[100];
    int[][] d=new int[0][10];
    System.out.println(a.getClass().hashCode());
    System.out.println(b.getClass().hashCode());
    System.out.println(d.getClass().hashCode());
  }
}

效果图:

前几个是各种类的Class对象

后三个是一个测试,一维数组和二维数组,只要维度相同,class对象都相同,维度不同,class对象不同。

java类加载内存分析

package com.grss.reflection;

/**
 * 类加载内存分析
 *
 * @Date 10:21 2020/8/7
 */
public class Testt04 {
  public static void main(String[] args) {
    A a = new A();
    System.out.println(A.m);
    /**
     * 1,加载到内存会产生一个类的class对象
     * 2,链接,链接结束后m=0
     * 3,初始化(new)
     *   <clinit>(){
     *     System.out.println("A类静态代码块初始化");
     *     m=300;
     *     m=100;
     *   }
     *  类的静态的代码块会按顺序执行,上面先赋值300,下面再赋值100
     */
  }
}
class A{
  static {
    System.out.println("A类静态代码块初始化");
    m=300;
  }
  static int m=100;

  public A() {
    System.out.println("A类的无参构造器加载");
  }
}

效果图:

类内存分析图:

类的内存加载,先在方法区中,加载时先在堆中生成类的Class对象,Class对象中有类的所有数据。初始化时(new时)就会在栈中把静态代码块中的数据合并,这个合并过程是按顺序执行的,上面的代码中,m先是300后是100所以结果为100.若换下位置,结果就是300.

顺便一提,加载时,是先加载静态代码块,后加载无参构造初始化类,然后才是方法。

换位置后的效果图:

什么时候会发生类的初始化

类的主动引用时会发生类的初始化,类被动引用时不会发生类的初始化。

package com.grss.reflection;

/**
 * 类在什么时候初始化
 *
 * @Date 11:10 2020/8/7
 */
public class Test05 {
  static {
    System.out.println("main类被加载");
  }

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

    //反射也会产生主动引用
//    Class.forName("com.grss.reflection.Son");

    //非主动引用
//    System.out.println(Son.b);

//    Son[] array=new Son[5];

    System.out.println(Son.n);
  }
}

class Fathsr{
  static int b=2;
  static {
    System.out.println("父类被加载");
  }
}
class Son extends Fathsr{
  static {
    System.out.println("子类被加载");
    m=300;
  }
  static int m =100;
  static final int n=1;
}

类只要被加载,main类就会被加载。

主动引用时,子类被加载,父类也会被加载:

别动引用时,用子类调父类方法,子类不会被加载。

创造一个与定义的类无关的数组时,只有main类被加载:

用子类调一个静态常量,子类和父类都不会被加载,只有main类被加载:

通过反射创建对象

先通过前面说过的三种方式来获得class对象,通过class对象来获得对象。

package com.grss.reflection;

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

/**
 * 通过反射创造对象
 *
 * @Date 14:32 2020/8/9
 */
public class Test07 {
  public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
    Class<?> c1 = Class.forName("com.grss.reflection.User");
    //调用无参构造器来创建对象
    User user = (User) c1.newInstance();

    //通过构造器创建对象
    User user1 = (User) c1.getConstructor(String.class, int.class, int.class).newInstance("雷怡",1,18);

    //通过反射获得普通方法
    User user2 = (User) c1.newInstance();
    //通过反射获得一个方法
    Method setName = c1.getDeclaredMethod("setName", String.class);
    //调用上面获取到的方法。
    //invoke:激活
    //invoke(对象,“方法的值”)
    setName.invoke(user2, "怡宝");

    User user3 = (User) c1.newInstance();
    Field name = c1.getDeclaredField("name");

    //关闭权限开关
    name.setAccessible(true);
    name.set(user3,"怡宝");
    System.out.println(user3.getName());
  }
}

获得对象主要通过无参构造和有参构造两种。

获得方法需要用到getDeclaredMethod(),参数一填方法名,参数2:数据类型、

想要获得一个对象的私有属性的时候,要记得关闭权限开关:setAccessible(true);

getConstructor/newInstance:通过构造获得类。

getDeclaredMethod:获得方法

getDeclaredField:获得属性

setAccessible(true):关闭权限开关,调用私有(private)时需要用到。

关于反射的性能分析

package com.grss.reflection;

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

/**
 * 性能分析
 *
 * @Date 17:37 2020/8/10
 */
public class Test08 {

  public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException {
    test1();
  }
  //普通
  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("用时:"+(endTime-startTime)+"ms");
  }
  //反射创建对象的
  public static void test2() throws IllegalAccessException, InstantiationException {
    long startTime=System.currentTimeMillis();
    for (int i = 0; i < 1000000000; i++) {
      User user = User.class.newInstance();
      user.getName();
    }
    long endTime= System.currentTimeMillis();
    System.out.println("用时:"+(endTime-startTime)+"ms");
  }
  //反射获取方法的
  public static void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    User user = new User();
    long startTime=System.currentTimeMillis();
    Method getName = User.class.getDeclaredMethod("getName");

    for (int i = 0; i < 1000000000; i++) {
      getName.invoke(user,null);
    }
    long endTime= System.currentTimeMillis();
    System.out.println("用时:"+(endTime-startTime)+"ms");
  }

  //获得属性
  public static void test4() throws NoSuchFieldException {
    long startTime=System.currentTimeMillis();
    for (int i = 0; i < 100000000; i++) {
      Field name = User.class.getField("name");
      name.setAccessible(true);
    }
    long endTime= System.currentTimeMillis();
    System.out.println("用时:"+(endTime-startTime)+"ms");

  }
  //反射,关闭权限检测后
  public static void test5() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    User user = new User();
    long startTime=System.currentTimeMillis();
    Method getName = User.class.getDeclaredMethod("getName");
    getName.setAccessible(true);
    for (int i = 0; i < 1000000000; i++) {
      getName.invoke(user,null);
    }
    long endTime= System.currentTimeMillis();
    System.out.println("用时:"+(endTime-startTime)+"ms");
  }
}

上面对比了用反射来创建对象,和用new来创建对象用时区别。new完爆反射。

直接调用getName方法和用反射,关闭检测的反射。

直接最快,关闭权限比不关要快三倍左右。

posted @ 2020-08-11 10:17  伟大的勇士2  阅读(162)  评论(0)    收藏  举报