阶段二-Day15-反射和注解

一、反射介绍

1. 反射的引入

在项目中有People类,包含name和age属性,并生成getter/setter和toString方法。

如果现在给People中添加address属性,并让程序支持对address属性赋值,需要如何完成?

可以使用Java中的反射技术. 解决程序在运行过程中改变属性值。

2. 反射介绍

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

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

3. 反射的优缺点

优点:灵活性和扩展性。

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

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

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

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

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

二、Class类介绍

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

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

1. 数据准备

1.1 Person
public class Person implements Serializable {
    private String name;
    private int age;
    public String address;
    public Date bir;

    public static void aa(){}

    public Person(){}

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

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
1.2 Man
public class Man extends Person{
    private String name;
    private int age;

    public Man() {
    }

    public Man(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

2. 获取Class对象

2.1 通过class关键字

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

Class<People> peopleClass = People.class;
2.2 通过forName()方法

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

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

public static Class<?> forName(String className) throws ClassNotFoundException {
  //...
}
2.3 通过getClass()方法
Person person = new Person();
Class<? extends Person> aClass = person.getClass();
2.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. 获取属性对象

3.1 getField(String name)

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

3.2 getFields()

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

3.3 getDeclaredField(String name)

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

3.4 getDeclaredFields()

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

public class FieldTest {
    public static void main(String[] args) throws Exception{
        Class<?> aClass = Class.forName("com.sh.课后练习.pojo.Person");
        //获取可访问的属性
        //java.lang.NoSuchFieldException
        //私有化的访问不到,只能访问可访问的
        Field nameField = aClass.getField("name");
        System.out.println(nameField);

        //获取能访问到所有属性
        Field[] fields = aClass.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }

        //获取本类中的属性,不看权限修饰符
        Field name = aClass.getDeclaredField("name");

        //获取本类中的所有属性,不看权限修饰符
        Field[] fields1 = aClass.getDeclaredFields();
        for (Field field : fields1) {
            System.out.println(field);
        }
    }
}

4. 获取方法对象

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

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

4.2 getMethods()

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

4.3 getDeclaredMethod(String name, Class<?>... parameterTypes)

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

4.4 getDeclaredMethods()

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

public class MethodTest {
    public static void main(String[] args) throws Exception{
        Class<?> aClass = Class.forName("com.sh.课后练习.pojo.Person");

        //参数类型获取本类中和父类中public修饰的方法的方法对象,有访问权限修饰符的限制。
        Method getName = aClass.getMethod("getName");
        System.out.println(getName);

        //获取能访问的所有的方法
        Method[] methods = aClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }

        //获取本类中的方法,不受权限修饰符的影响
        Method bb = aClass.getDeclaredMethod("bb");
        System.out.println(bb);

        //获取本类中的所有方法,不受权限修饰符的影响
        Method[] methods1 = aClass.getDeclaredMethods();
        for (Method method : methods1) {
            System.out.println(method);
        }
    }
}

5. 获取构造方法对象

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

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

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

5.2 getConstructors()

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

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

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

5.4 getDeclaredConstructors()

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

public class ConstructorTest {
    public static void main(String[] args) throws Exception {
        //1.普通创建对象,普通调用构造方法
        //无参构造
        Man man1 = new Man();
        //有参构造
        Man man = new Man("zs",123);
        //2.通过反射创建对象,通过反射调用构造方法
        //无参构造
        Class<?> aClass = Class.forName("com.sh.课后练习.pojo.Man");
        Object o = aClass.newInstance();
        //有参构造
        aClass.getConstructor(String.class , int.class).newInstance("zs",123);
    }
}

三、操作属性对象(Field)

1. 介绍

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

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

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

2. 常用方法

2.1 set(Object,Object)

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

2.2 setAccessible(boolean)

setAccessible(boolean) 如果一个属性是private,即使获得到了这个属性对象,也不允许被赋值的。在赋值之前,设置setAccessible(true)。表示即使为private,也能赋值。完全破坏了Java中封装特性。

2.3 get(Object)

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

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

public class SetFieldTest {
    public static void main(String[] args) throws Exception{

        Class<?> aClass = Class.forName("com.sh.课后练习.pojo.Man");
        //使用反射来给属性赋值
        Object o = aClass.newInstance();
        Field name = aClass.getDeclaredField("name");
        //突破访问权限修饰符
        name.setAccessible(true);
        //知道对象,知道属性,修改属性值
        name.set(o,"haha");
        System.out.println(o);
        //Man{name='haha', age=0}结果
        //知道对象,知道属性,获取属性值
        Object o1 = name.get(o);
        System.out.println(o1);
    }
}

四、操作方法对象(Method)

1. 介绍

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

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

2. 常用方法

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 class SetMethodTest {
    public static void main(String[] args) throws Exception{
        Class<?> aClass = Class.forName("com.sh.课后练习.pojo.Man");
        Object o = aClass.newInstance();
        //参数一:方法名 参数二:方法参数
        Method setAge = aClass.getDeclaredMethod("ha",String.class,int.class);
        //invoke()方法执行方法,参数一:对象  参数二:方法的参数
        setAge.invoke(o,"zs",123);
        System.out.println(o);
    }
}

五、反射总结

反射优点:

  1. 功能强大。

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

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

反射缺点:

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

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

六、注解(Annotation)

1. 介绍

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

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

2. 常见内置注解

2.1 @Override

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

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

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

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

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

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

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

}
2.4 @FunctionalInterface

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

七、元注解

1. 介绍

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

元注解是对注解的注释。

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

@Target

@Retention

@Documented

@Inherited

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

@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
}

@Retention

定义什么时候起作用

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

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

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

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

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

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

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

@Documented

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

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

@Inherited

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

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

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

八、自定义注解

1. 介绍

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

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

2. 自定义注解关键字

定义注解关键字:@interface

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

3. 定义注解中内容

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

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

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

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

//在方法和属性上使用的注解
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME) //运行时生效
public @interface MyAnnotation {
    //属性默认都是公共静态常量
    public static final int a = 10;
    //方法默认都是公共抽象方法
    public abstract int max();
    //注解中的方法相当于一个属性
    public abstract int min() default 1;
}

4. 使用注解

public class Test {
    private String name;
    @MyAnnotation(max = 100,min = 1)
    private int age = 50;
}
public class AnnotationTest {
    public static void main(String[] args) throws Exception{
        //1.获取类对象
        Class<?> aClass = Class.forName("com.sh.课后练习.自定义注解.Test");
        Object o = aClass.newInstance();
        //2.获取类对象的所有属性对象
        Field[] declaredFields = aClass.getDeclaredFields();
        //3.遍历所有的属性对象,获取每一个属性对象
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            //4.判断是否还有MyAnnotation注解
            //调用isAnnotationPresent(注解.class)
            if (declaredField.isAnnotationPresent(MyAnnotation.class)) {
                System.out.println("存在注解的属性: " + declaredField.getName());
                //获取注解中的最大值和最小值
                MyAnnotation annotation = declaredField.getAnnotation(MyAnnotation.class);
                int max = annotation.max();
                int min = annotation.min();
                if (declaredField.getInt(o) < min){
                    throw new RuntimeException(declaredField.getName()+"的最小值为1, 当前值为: "+declaredField.getInt(o));
                }
                if (declaredField.getInt(o) > max){
                    throw new RuntimeException(declaredField.getName()+"的最大值为100, 当前值为: "+declaredField.getInt(o));
                }
            }
        }
    }
}

5. 以后的注解

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

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

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值