反射和注解

一、反射介绍

1.反射介绍

反射(Reflect):Java中提供一种可以在运行时操作任意类中属性和方法的技术。

反射在运行之前是不需要类中结构的,运行过程中,只要能够获取该类的字节码文件,就可以随意修改类中属性的值,随意调用类中的方法。让程序变得更灵活。

2.反射的优缺点

优点:灵活性和扩展性

缺点:性能问题。因为反射是在运行过程中操作字节码文件,要要比直接使用代码操作内容慢得多。

总结:反射主要使用在对灵活性和扩展性要求比较高的框架中。普通代码不建议使用。

我们暂时也不需要写灵活性非常高的框架,为什么还要学习反射?

1. 反射是Java中非常重要特性。

2. 虽然我们暂时不写灵活度非常高的框架。但是我们要学一些框架。这些框架大部分使用了反射技术。所以学好反射对以后学习框架底层有非常大的帮助。

二、Class类介绍

java.lang.Class 表示类的类型。一个Class类对象就代表了某个类的字节码对象。获取到这个类的字节码对象后该类中所有的内容都会被知道,然后就可以对这个类中的内容进行操作。

Class类是Java 反射机制的起源和入口 ,用于获取与类相关的各种信息,提供了获取类信息的相关方法,Class类继承自Object类。

 1.数据准备

1.Person

package com.bjsxt.pojo;
public class Person {
    public String personName;
    private String personSex;

    public void personEat(){}
    private void personPlay(){}
    //省略有参,无惨构造方法,get/set方法,toString方法
}

2.Man

public class Man extends Person{
    public String manName;
    private String manSex;

    public void manEat(){}
    private void manPlay(){}
    //省略有参,无惨构造方法,get/set方法,toString方法
}

2.获取Class对象

1.通过class关键字

可以通过类名.class获取到这个类的字节码对象。

Class<People> peopleClass = People.class;

2.通过forName()方法

可以通过Class类中的静态方法forName()获取到类的字节码对象。

forName方法抛出了Checked异常ClassNotFoundException。如果加载类时,没有发现这个类的字节码文件,就会出现这个异常。

public static Class<?> forName(String className) throws ClassNotFoundException {
  //...
}

代码示例:

forName(String)参数是类的全限定路径(包名+类名)

Class<?> aClass = Class.forName("com.bjsxt.pojo.Person");

3.通过getClass()方法

Person person = new Person();
Class<? extends Person> aClass = person.getClass();

4.Class对象常用方法

//1.获取一个类的结构信息(类对象 Class对象)
Class clazz = Class.forName("com.bjsxt.pojo.Person");
//2.从类对象中获取类的各种结构信息
//2.1 获取包名+类名
System.out.println(clazz.getName());
//2.2 获取类名
System.out.println(clazz.getSimpleName());
//2.3 获取父类
System.out.println(clazz.getSuperclass());
//2.4 获取实现的接口
System.out.println(Arrays.toString(clazz.getInterfaces()));

3.获取属性对象

1.getField(String name)

解释:根据属性名获取本类中和父类中的public修饰的属性的属性对象,有访问权限修饰符的限制。

代码实例:

Class<Man> aClass = Man.class;
Field field1 = aClass.getField("manName");
Field field2 = aClass.getField("personName");

2.getFields()

解释:获取本类中和父类中的被public修饰的属性的属性对象,有访问权限修饰符的限制。

代码实例;

Class<Man> aClass = Man.class;
Field[] fields = aClass.getFields();

3.getDeclaredField(String name)

解释:根据属性名获取本类中属性的属性对象,没有访问权限修饰符的限制。

代码示例:

Class<Man> aClass = Man.class;
Field field = aClass.getDeclaredField("manSex");

4.getDeclaredFields()

解释:根据属性名获取本类中所有属性的属性对象,没有访问权限修饰符的限制。

代码示例:

Class<Man> aClass = Man.class;
Field[] fields = aClass.getDeclaredFields();

4.获取方法对象

1.getMethod(String name,Class<?>...parameterTypes)

解释:根据方法名,参数类型获取本类中和父类中public修饰的方法对象,有访问权限修饰符的限制。

代码实例:

Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Method manEat = aClass.getMethod("manEat");
Method personEat = aClass.getMethod("personEat");

2.getMethods()

解释:获取本类中和父类中public修饰的所有方法的对象,有访问权限修饰符的限制。

代码示例:

Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Method[] methods = aClass.getMethods();

3.getDeclaredMethod(String name.Class<?>...parameterTypes)

解释:根据方法名,参数类型获取本类中方法的方法对象,没有访问权限修饰符的限制。

代码实例:

Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Method manPlay = aClass.getDeclaredMethod("manPlay");

4.getDeclaredMethods()

解释:获取本类中所有方法的方法对象,没有访问权限修饰符的限制。

代码实例:

Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Method[] manPlay = aClass.getDeclaredMethods();

5.获取构造方法对象

注意:只能获取本类中的构造方法。

1.getConstructor(Class<?>...parameterTypes)

解释:获取本类中无参数|有参数构造方法,有访问权限修饰符的限制。

Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Constructor<?> constructor = aClass.getConstructor();
Constructor<?> constructor1 = aClass.getConstructor(String.class, String.class);

2.getConstructors()

解释:获取本类中所有无参数|有参数构造方法,有访问权限修饰符的限制。

Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Constructor<?>[] constructors = aClass.getConstructors();

3.getDeclaredConstructor(Class<?>...parameterTypes)

解释:获取本类中无参数|有参数构造方法,没有访问权限修饰符的限制。

Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Constructor<?> constructor = aClass.getDeclaredConstructor();
Constructor<?> constructor1 = aClass.getDeclaredConstructor(String.class, String.class);

4.getDeclaredConstructors()

解释:获取本类中所有无参数|有参数构造方法,没有访问权限修饰符的限制。

Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();

 5.通过Class对象实例化对象

1.通过Class的newInstance()方法创建对象

类必须有无参构造方法

执行newInstance()执行无参构造方法来创建该类的实例化对象

//正常创建对象
Man man = new Man();
//使用反射创建对象
Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Object o = aClass.newInstance();

2.通过数构造方法对象创建对象

通过Constructor对象的newInstance()方法创建对象

先使用Class 对象获取Constructor对象

再调用Constructor对象的newInstance()创建Class对象对应类的对象

Class<?> aClass = Class.forName("com.bjsxt.pojo.Man");
Object o = aClass.getConstructor().newInstance();

Object o = aClass.getConstructor(String.class, String.class).newInstance("zs", "男");

三、操作属性对象(Field)

1.介绍

  1. java中提供的对反射支持的类都在java.lang.reflet包中。

  2. java.lang.reflect.Field 表示类中属性的属性对象。类中每一个属性对应一个属性对象。

  3. 属性对象是无法被外部实例化的。它只提供了默认访问权限修饰符的构造方法。所以基本上都是通过Class对象获取Field对象。

2.常用方法

1.set(Object,Object)

set(Object,Object)参数一:为哪个对象的属性赋值。参数二:属性值。

Class<People> aClass = People.class;
//获取对象
People people = aClass.getConstructor().newInstance();
//获取属性对象
Field field = aClass.getDeclaredField("name");
//设置属性值
field.set(people, "zs");

注意:

 2.setAccessible(boolean)

setAccessible(booolean)如果一个属性是private,即使获得到了这个属性对象,也不允许被赋值的。在赋值之前,设置setAccessible(true)。表示即使为private,也能赋值。

这点着实很变态,完全破坏了Java中封装特性。也就是说对于反射,类中没有任何秘密而言。

Class<People> aClass = People.class;
People people = aClass.getConstructor().newInstance();
Field field = aClass.getDeclaredField("name");
field.setAccessible(true);
field.set(people, "zs");

3.get(Object)

get(Object)参数:获取那个对象的属性值。返回值:属性值(类型为Object)。

还支持getInt(Object)、getFloat(Object)、getChar(Object)等多种返回具体类型的方法。

Class<People> aClass = People.class;
People people = aClass.getConstructor().newInstance();
Field field = aClass.getDeclaredField("name");
//设置属性值
field.setAccessible(true);
field.set(people, "zs");
//获取属性值
Object name = field.get(people);
System.out.println(name);

3.案例优化

1.需求

 2.实现

public static void main(String[] args) throws Exception {
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入包名+类名");
    String next = sc.next();
    System.out.println("请输入赋值的属性名称");
    String filed = sc.next();
    System.out.println("请给这个属性赋值");
    String value = sc.next();
    //获取类对象
    Class<?> aClass = Class.forName(next);
    //通过类对象创建实例化对象
    Object o = aClass.getConstructor().newInstance();
    //获取输入属性的属性对象
    Field declaredField = aClass.getDeclaredField(filed);
    //设置突破权限的限制
    declaredField.setAccessible(true);
    //获取属性类型对象
    Class<?> type = declaredField.getType();
    //获取属性类型名
    String simpleName = type.getSimpleName(); // String int double ...
    System.out.println(simpleName);
    Object val = null;
    //判断属性的数据类型
    switch (simpleName){
        case "String":
            val = value;
            break;
        case "int":
            System.out.println("-------------");
            val = Integer.parseInt(value);
            break;
        case "double":
            val = Double.parseDouble(value);
            break;
        case "boolean":
            val = Boolean.parseBoolean(value);
    }
    //设置属性值
    declaredField.set(o, val);
    System.out.println(o);
}

四、操作方法对象(Method)

1.介绍

  1. java.lang.reflect.Method表示类中方法的方法对象。类中每个方法对应一个Method对象。

  2. 方法对象是无法被外部实例化的。它只提供了默认访问权限修饰符的构造方法。所以基本上都是通过Class对象获取Method对象。

2.常用方法

1.invoke

  1. 通过Class对象的getMethods() 方法可以获得该类和父类所包括的全部public方法, 返回值是Method[]。

  2. 通过Class对象的getMethod()方法可以获得该类所包括的指定public方法, 返回值是Method。

  3. 每个Method对象对应一个方法,获得Method对象后,可以调用其invoke() 来执行对应方法。

  4. invoke(Object obj,Object [] args):

    1. obj代表当前方法所属对象的名字。

    2. args代表调用方法传递的实参。

代码实例:

public static void main(String[] args) throws Exception {
    //获取类对象
    Class<?> aClass = Class.forName("com.bjsxt.pojo.People");
    //通过类对象创建实例化对象
    Object o = aClass.getConstructor().newInstance();
    //获取方法对象
    Method method = aClass.getDeclaredMethod("setName", String.class);
    //执行方法
    method.invoke(o, "ls");
    System.out.println(o);
}

五、反射总结

反射优点:

  1. 功能强大。

  2. 编码时不知道具体的类型,可以使用反射动态操作。

  3. 突破封装的限制,即使private的成员也可以进行操作。

反射缺点:

  1. 代码繁琐,可读性差。

  2. 突破封装的限制,即使private的成员也可以进行操作(既是优点也是缺点)。

 六、注解(Annotation)

1.介绍

注解是从Java 5开始提供的一种技术。使用@+名称 就可以对类、方法、参数等内容添加一个描述功能。

在没有注解之前,很多技术都需要编写大量的XML配置。由于XML文档的结构性问题,想要编写一个文本节点,可能需要2层、3层甚至更多的嵌套才能实现。但是这些问题可以通过注解简化。

2.常见内置注解

1.@Override

在方法重写时,可以在方法上添加@Override注解来检查方法是否满足重写的语法要求。简单方便。

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

2.@Deprecated

如果在方法上添加这个注解,表示该方法已经过时。

过时的方法现在能使用,但是已经不再维护或将来会提供其他的使用方法。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {
}

3.@SuppressWarnings

指示编译器去忽略注解中声明的警告

@SuppressWarnings(value={"all"})
public static void main(String[] args) {
    Date date = new Date();
    System.out.println(date.toLocaleString());

}

4.@Functionallnterface

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

七、元注解

1.介绍

在上面源码中发现,每个注解名称上面都还有几个其他注解。这些注解称为元注解(Meta-annotation)。(修饰注解的注解)

元注解是对注解的注释。

Java中一共包含四个元注解:

@Target

@Retention

@Documented

@Inherited

这四个注解不是必须同时都被使用的。一般只需要用到@Target和@Retention。另外两个元注解根据自己的情况添加。

2.@Target

指定注解可以使用的位置(如:类 方法 属性...)

@Target用来描述注解的使用范围。注解里面包含了ElementType[ ]

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
	ElementType[] value();
}

ElementType 是一个枚举类型

public enum ElementType {
    /**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
    TYPE,
    /** 标明该注解可以用于字段(域)声明,包括enum实例 */
    FIELD,
    /** 标明该注解可以用于方法声明 */
    METHOD,
    /** 标明该注解可以用于参数声明 */
    PARAMETER,
    /** 标明注解可以用于构造函数声明 */
    CONSTRUCTOR,
    /** 标明注解可以用于局部变量声明 */
    LOCAL_VARIABLE,
    /** 标明注解可以用于注解声明(应用于另一个注解上)*/
    ANNOTATION_TYPE,
    /** 标明注解可以用于包声明 */
    PACKAGE,
    /**
     * 标明注解可以用于类型参数声明(1.8新加入)
     * @since 1.8
     */
    TYPE_PARAMETER,
    /**
     * 类型使用声明(1.8新加入)
     * @since 1.8
     */
    TYPE_USE
    /**
    * Module declaration.
    *
    * @since 9
    */
     MODULE
}

3.@Retention

什么时候起作用

定义注解被保留到什么时候。默认值为RetentionPolicy.CLASS

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
	RetentionPolicy value();
}

里面value取值为RetentionPoolicy。也是枚举类型,可取值为三个

public enum RetentionPolicy {
	//源文件
  SOURCE,
	//字节码
  CLASS,
	//运行时 常用
  RUNTIME
}

SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)

CLASS:注解在class文件中可用,但会被JVM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中)。

RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。

4.@Documented

生成帮助文档(javadoc)后,是否保留注解信息。如果希望保留就添加这个注解。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

5.@Inherited

如果一个注解添加了@Inherited,那么一个类被这个注解修饰后,其子类也自动添加这个注解。

当@InheritedAnno注解加上某个类A上时,假如类B继承了A,则B也会带上该注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited 
{
}

八、自定义注解

1.介绍

通过阅读常用注解可以发现,JDK中定义的注解就是在其上面添加了几个元注解。在根据自己的要求定义注解可以包含哪些属性。

我们也可以利用元注解进行自定义注解。合理的自定义注解可以简化应用程序开发。同时通过学习自定义注解也可以更好的理解后面框架中的注解。

2.自定义注解关键字

定义注解关键字:@interface

注解名称和类型命名要求相同。命名规范相同。

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME) 
public @interface MyAnnotaion {
}

3.定义注解中的内容

注解中可以包含常量和方法:

常量:定义后对外部没有太多实际意义。外部使用注解时无法对该值修改。常量默认使用public static final修饰。

方法:必须有返回值,且返回值类型只能是基本类型、String、Class、枚举、注解类型及以上类型的一维数组。方法可以有默认返回值,也可以没有。方法默认使用public abstract修饰。

虽然定义注解时是方法,但是对于外部使用时当做属性使用。

@Target({ElementType.METHOD, ElementType.FIELD}) //用在方法上, 属性上
@Retention(RetentionPolicy.RUNTIME) //运行时生效
public @interface MyAnnotaion {
    //属性
    public static final int a = 10;
    public abstract int max();
    public abstract int min() default 1;
}

4.使用注解

可以在任意类的属性中使用@MyAnnotation注解。

public class People {
  private String name;
  @MyAnnotaion(max = 100, min = 1)
  private int age;
	//...
}

5.使用反射实现注解的功能

使用反射读取注解中配置的值,判断对象属性值是否满足要求。如果不满足要求抛出异常。一般实际项目中会专门针对这个定义一个异常。

public class MyScan {
  public static boolean scan(Object obj){
    try {
      //1.获取类对象
      Class<? extends Object> aClass = obj.getClass();
      //2.通过类对象获取所有属性对象
      Field[] declaredFields = aClass.getDeclaredFields();
      //3.遍历所有的属性对象, 获取每一个属性对象
      for (Field declaredField : declaredFields) {
        declaredField.setAccessible(true);
        //4.判断 属性是否含有@MyAnnotaion 注解
        if (declaredField.isAnnotationPresent(MyAnnotaion.class)) {
          System.out.println("存在注解的属性: " + declaredField.getName());
          //5.获取注解中最大值和最小值
          MyAnnotaion annotation = declaredField.getAnnotation(MyAnnotaion.class);
          int min = annotation.min();
          int max = annotation.max();
          if (declaredField.getInt(obj) < min){
            throw new RuntimeException(declaredField.getName()+"的最小值为1, 当前值为: "+declaredField.getInt(obj));
          }
          if (declaredField.getInt(obj) > max){
            throw new RuntimeException(declaredField.getName()+"的最大值为100, 当前值为: "+declaredField.getInt(obj));
          }
        }
      }
      return true;
    } catch (IllegalAccessException e) {
      e.printStackTrace();
      return false;
    }
  }
}

6.测试效果

public class Test {
  public static void main(String[] args) {
    People student = new People("zs", 10);
    MyScan.scan(student);
  }
}

7.控制台结果

 8.以后的注解

在以后JavaEE会学习Filter,学习了框架会学习AOP、Interceptor等技术。这些技术可以实现在执行一个方法时,在前面或后面自动执行一个方法。

目前在Java SE时,还需要手动调用一下MyScan的scan()方法,以后这个方法是不需要手动调用的。可以自动判断注解。

另外我们写的实现注解功能在以后都是框架帮助我们实现的。经常直接使用框架定义好的注解,框架已经把注解封装好了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值