对反射和注解的详细解读

反射

1.反射的概述

编译时知道类或对象的具体信息,此时直接对类和对象进行操作即可,无需反射(reflection) >>new

如果编译不知道类或对象的具体信息,可以使用反射来实现.比如类的名称放在XML文件中,属性和属性值放在XML文件中,需要在运行时读取XML文件,动态获取类的信息.

反射的应用场合:

​ 在编译时根本无法知道该对象或类可能属于哪些类,程序员只能依靠运行时信息来发现该对象和类的真实信息. eg: log4j , Servlet , SSM框架都用到了反射.

反射的作用:

​ 1.动态创建对象

​ 2.动态操作属性

​ 3.动态调用方法

  • 在JDK中,主要有以下类来实现Java反射机制,这些类都位于java.lang.reflect包中
  1. Class类 : 代表一个类 >>在java.lang包里

  2. Constructor类 : 代表类的构造方法

  3. Field类 : 代表类的成员变量(属性)

  4. Method类 : 代表类的成员方

    <注意: 导包问题>>java.lang包下得类可以不用导入,但是其子包下得类要导入>

2.反射的入口 --Class类

2.1 Class类的概述

注 : Class本身不属于反射,它是反射的入口

  • Class类是Java反射机制的起源和入口

    • 用于获取与类相关的各种信息
    • 提供了获取类信息的相关方法
    • Class类继承Object类
  • Class类是所有类的共同的图纸

    • 每个类有自己的对象,好比图纸和实物的关系
    • 每个类也可以看做是一个对象,有共同的图纸Class,存放类的结构信息,比如类的名字,属性,构造方法,父类和接口,能够通过相应的方法去除相应的信息.
  • Class类的对象称为类对象 ( 类对象的对象我们可以称之为普通对象 )

2.2 获取Class类的类对象:

方式一: Class.forName();  >>反射常用
方式二: 类名.class;
方式三: 对象名.getClass();
 //获取一个类的类信息
        //1.1
        Class cls = Class.forName("project10.Test02.Dog");
        //1.2
        Class cls2 = Dog.class;
        //1.3
        Dog dog = new Dog();
        Class cls3 = dog.getClass();
Class<Integer> cls4 = Integer.class;//Integer的类对象
        System.out.println(cls4);
       Class cls5 = Integer.TYPE;//Integer的包装的int的类对象
        System.out.println(cls5);

2.3 Class类的常用方法:

getFields()获得类的public类型的属性(该类及其父类)
getDeclaredFields()获取类的所有属性(仅限该类)
getField()获得类的指定属性
getDeclaredField()获得类的指定属性

getMethods()获得类的public类型的方法(该类及其父类)
getDeclaredMethods()获取类的所有方法(仅限该类)
getMethod()获得类的指定方法
getDeclaredMethod()获得类的指定方法

getConstructors()获得类的public类型的构造方法(该类及其父类)
getDeclaredConstructors()获取类的所有构造方法(仅限该类)
getConstructor()获取类的指定构造方法
getDeclaredConstructor()获取类的指定构造放法

newInstance()通过类的无参构造方法创建对象

package project10.clazz;

import java.lang.reflect.*;
import java.util.Arrays;

public class TestClass01 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //获取一个类的类信息
        Class cls = Class.forName("project10.Test02.Dog");

        //从类对象中获取具体的结构信息 : get

        //2.1 获取最基本的信息
        System.out.println(cls.getName());//project10.Test02.Dog
        System.out.println(cls.getSimpleName());//Dog
        System.out.println(cls.getSuperclass());//class project10.Test02.Pet
        System.out.println(Arrays.toString(cls.getInterfaces()));//[interface java.io.Serializable]
        System.out.println(cls.getModifiers());//获取类的修饰符类型 1>>public 0>>默认


        //2.2 获取成员变量
        Field[] fields = cls.getFields();//获取该类和其父类的所有public属性
        Field[] dfields = cls.getDeclaredFields();//获取该类的所有属性,不包括上级类
        for (Field f:fields
             ) {
            System.out.println(f);
        }
        System.out.println("..........................................");
        Field fi = cls.getField("p");//获取单个的public修饰的方法
        System.out.println(fi.getName()+"属性的修饰符: "+ Modifier.toString(fi.getModifiers())+",属性类型:"+fi.getType());
        Field f = cls.getDeclaredField("variety");//获取单个的属性,不包括上级类(与修饰符无关)
        System.out.println(f.getName()+"属性的修饰符: "+ Modifier.toString(f.getModifiers())+",属性类型:"+fi.getType());
//        如果要使用getDeclaredField得到上级的属性,就要用cls.getSuperClass().getDeclaredField();
        System.out.println(".........................");
        //2.3 获取成员方法
        Method[] methods = cls.getMethods(); //获取该类及其父类的public方法
        for (Method m:methods
             ) {
            System.out.println(m.getName()+"方法的修饰符:"+Modifier.toString(m.getModifiers())+",方法返回值类型:"+m.getReturnType());
        }
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
        Method[] dM = cls.getDeclaredMethods();//获取该类的所有方法
        for (Method m:dM
             ) {
            System.out.println(m.getName()+"方法的修饰符:"+Modifier.toString(m.getModifiers())+",方法返回值类型:"+m.getReturnType());
        }
        System.out.println("..............................");
        Method m1 = cls.getMethod("ill");
        Method m2 = cls.getDeclaredMethod("ill");


        //2.4 获取构造方法
        System.out.println("===================== ");
        Constructor[] con = cls.getConstructors();
        Constructor[] dcon = cls.getDeclaredConstructors();

        Constructor cons = cls.getConstructor(String.class);
        System.out.println(cons);

        //3.通过构造方法创建对象
        Object obj = cons.newInstance();
        System.out.println(obj.toString());

    }
}

3. 反射技术

3.1 使用反射创建对象

newInstance()创建对象
  1. 调用无参数构造方法创建对象
    • 调用Constructor的newInstance()
    • 调用Class的newInstance()
  2. 调用有参数构造方法创建对象(只有一种形式)
    • 调用Constructor的newInstance()

注意: 1> 关注访问修饰符的权限问题

​ 2> 能够从Class中获取非public的构造方法 ,与能否执行是两回事

​ 解决方案 : setAccessible(true); 暴力反射

3.2 使用反射操作属性

get();获取属性
set();修改属性
package project10.clazz;

import project10.Test02.Dog;

import java.lang.reflect.Field;

/**
 * 使用反射操作属性,是直接操作属性,额而不是通过Setter和Getter方法间接操作
 *
 * 实际开发中直接操作属性并不多,更多的是通过Setter和Getter方法间接操作
 *
 */
public class Testclass02 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        //不使用反射操作属性
        Dog dog = new Dog();
        dog.setVariety("拉布拉多");
        dog.getVariety();       //不是操作属性,这是在执行方法

        //使用反射操作属性
        //1.获取类的完整路径字符串
        String className = "project10.Test02.Dog";
        //2.得到类的类对象
        Class cls = Class.forName(className);
        //3.使用反射创建对象
        Object obj = cls.newInstance();  //无参
//        System.out.println(obj);
        //4.从Class中获取属性
        Field f = cls.getDeclaredField("variety");
        //5.使用反射操作属性
        f.setAccessible(true);
        f.set(obj,"拉布拉多");//AccessibleObject 能获取和能否操作是两回事  >>dog.variety = "拉布拉多"
        System.out.println(obj);
        Object obj1 = f.get(obj);//String variety = dog.variety;
        System.out.println(obj1);

    }
}

3.3 使用反射执行方法

invoke()执行方法
package project10.clazz;

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

/**
 * 使用反射执行方法
 */
public class TestClass03 {
    public void shout(){
        System.out.println("........shout..........");
    }

    public int add(int num1,int num2){
        return num1+num2;
    }

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //不使用反射执行方法
        TestClass03 tc = new TestClass03();
        tc.shout();
        int value = tc.add(1, 2);
        System.out.println(value);


        //1.获取路径的完整字符串
        String className = "project10.clazz.TestClass03";
        //2.获取类的类对象
        Class cls = Class.forName(className);
        //3.使用反射创建对象
        Object obj = cls.newInstance();
        //4.从类对象中获取方法
        Method m1 = cls.getMethod("shout");
        Method m2 = cls.getMethod("add", int.class, int.class);
        //5.使用反射执行方法
        m1.invoke(obj);     // 相当于: 不使用反射的 >>对象.方法;  这个是相反的,是方法名.invoke(对象);
        Object valueN = m2.invoke(obj, 1, 2);
        System.out.println(valueN);
    }
}

实际开发中,动态操作属性不多,更多的是动态的执行方法

使用反射执行方法,不一定必须通过反射来创建对象

但是如果通过反射创建了对象,一般都要通过反射来执行方法

3.4 使用反射操作泛型

3.4.1 泛型的概述

​ 没有出现泛型之前,Java中所有数据类型包括:基本类型和原始类型(引用类型)

Class类的一个具体对象代表一个指定的基本类型和原始类型(引用类型)

​ 泛型出现后,也就扩充了数据类型:

  • parameterized types(参数化类型): 就是我们平常所用到的泛型List,Map<K,V>的List和Map
  • type variables(类型变量): 比如List中的T . [注意和参数化类型的区别]
  • array types(数组类型): 并不是我们工作中所使用的数组String[],byte[]<这种的都属于Class> ,而是带泛型的数组,比如: List[],T[].
  • WildcardTypes(泛型表达式类型 通配符类型): 例如List< ? extends Number >

Java 采用泛型擦除机制来引入泛型.但是擦除的是方法题中的局部变量上定义的泛型, 在泛型类,泛型接口,泛型方法定义的泛型,在成员变量上定义的泛型依旧会保存(可以理解为定义泛型信息保留,使用泛型信息擦除).保留下来的信息可以通过反射获取.

另一方面,Class类的一个具体对象代表一个指定的原始类型和基本类型 , 和泛型相关的新扩充进来的类型不好被统一到Class类中,否则会涉及到JVM指令集的修改,是很致命的.

​ 为了能通过反射操作泛型,但是实现扩展性而不影响之前的操作,java就新增了ParameterizedTypes , TypeVariables , ArrayTypes , WildcardTypes几种类型来代表不能被归到Class类中的类型,但是又和原始类型齐名的类型.

在这里插入图片描述

package project10.clazz;

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 TestGeneric {
    public void method1 (Map<Integer,Student> map, List<Student> list, String str ){

    }
    public Map<Integer,Student> method2(){
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        //获取当前类的类对象
        Class cls = TestGeneric.class;
        //获取method1方法的参数中的泛型类型
        Method m1 = cls.getMethod("method1", Map.class, List.class, String.class);
        //1.不带泛型的参数
        Class[] types = m1.getParameterTypes();
        System.out.println(types.length);
        for (int i = 0; i <types.length ; i++) {
            System.out.println(types[i]);
        }
        System.out.println("..........................");
        Type[] gtypes = m1.getGenericParameterTypes();
        for (Type p:gtypes
             ) {
            System.out.println(p);
            if (p instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) p).getActualTypeArguments();
                for (Type ata:actualTypeArguments
                     ) {
                    System.out.println("         "+ata);
                }
            }
        }
    }
}

package project10.clazz;

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 TestGeneric2 {
    public void method1 (Map<Integer,Student> map, List<Student> list, String str ){

    }
    public Map<Integer,Student> method2(){
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        //获取当前类的类对象
        Class cls = TestGeneric2.class;

        //获取返回值类型(不带泛型)
        Method m2 = cls.getMethod("method2");
        Class<?> returnType = m2.getReturnType();
        System.out.println(returnType);
        //获取返回值类型(带泛型)
        Type returnType1 = m2.getGenericReturnType();
        System.out.println(returnType1);
        Type[] actualTypeArguments = ((ParameterizedType) returnType1).getActualTypeArguments();//向下强转
        for (Type a:actualTypeArguments
             ) {
            System.out.println("    "+a);
        }
    }
}

3.4.2 使用反射操作泛型
package project10.clazz;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * 1.反射的强大之处
 * 2.使用反射创建方法,不见得必须使用反射创建对象
 */
public class TestGeneric3 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        List<Integer> list = new ArrayList<>();
        list.add(123);
        list.add(456);
        list.add(789);
        list.add(999);

        System.out.println(list);//[123, 456, 789, 999]

        Class cls = list.getClass();//得到底层的类对象
        Method m1 = cls.getMethod("add", Object.class);//通过反射来调用方法,不受泛型的限制
        System.out.println(m1);
        m1.invoke(list,true);
        m1.invoke(list,"lmx");
        m1.invoke(list,3.14);
        System.out.println(list);//[123, 456, 789, 999, true, lmx, 3.14]

    }
}

4. 反射的优缺点

优点:

​ 功能强大

​ 1).编译的时候不知道类或对象的具体信息,可以使用反射根据运行时获取的路径信息来操作

​ 2).可以突破封装的限制.即使是private的也可以操作,例如:暴力反射。

缺点:

​ 1).代码可读性差,不好理解

​ 2).执行速度慢

​ 3).突破了封装的限制(不遵守规范)

注解

1. 认识注解

Annotation , JDK1.5新提供的技术

注解的作用:

  • 编译检查 : 比如@SuppressWarnings , @Deprecated 和 @Override 都有编译建嘻哈的作用

  • 替代配置文件 : 使用反射来读取注解信息

    目前大部分的框架(eg:Spring)都是用的注解简化代码并提高编码的效率(使用直接之前使用的xml进行配置)

    注解其实就是代码里的特殊标记,它用于替代配置文件:传统方法通过配置文件告诉如何运行, 有了注解技术后,开发人员可以通过注解告诉类如何运行.

在java技术里注解的典型应用:

​ 可以通过反射技术去得到类里面的注解,以决定怎么去运行类. 注解可以标记在 包, 类, 属性, 方法, 方法参数 以及 局部变量 上,且同一个地方可以同时标注多个注解.

​ 注解可以在编译(source),类加载(class),运行时(runtime)被读取,并执行相应的处理,以便于其他工具补充信息或者进行部署.

2.内置注解

主要有三个内置注解

  • @Override - 检查该方法是否是重写方法. 如果发现其父类,或者是引用的接口中没有改方法是,会报编译错误.

  • @Deprecated - 标记过时方法. 如果使用该方法,会报编译警告.

  • @SuppressWarnings - 指示编译器去忽略注解中声明的警告.

    参数 说明
    deprecation 使用了过时的类或方法的警告
    unchecked 执行了未检查的转换时的警告,如:适用集合时未指定泛型
    fallthrough 当在switch语句使用时发生case穿透
    path 在类路径,源文件路径等有不存在路径的警告
    serial 当在可序列化的类上缺少serialVersionUID定义是的警告
    finally 任何finally子句不能完成是的警告
    all 关于以上所有情况的警告

    ​ 从Java 7 开始,额外添加了3个注解:

    • @SafeVarargs - Java7开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告.
    • @FunctionalInterface - Java8开始支持,标识一个匿名函数或函数式接口.
    • @Repeatable - Java8开始支持,标识某注解可以在同一个声明上使用.

    3. 元注解

    元注解是指注解的注解,在JDK1.5中提供了4个标准的用来对注解类型进行注解的注解类. 可以使用这4个元注解来对我们自定义的注解类型进行注解.

    1. @Retention - 用来约束注解的生命周期,分别有三个值,原码级别(source),类文件级别(class)或者运行时级别(runtime),若没有@Retention,则默认是RetentionPolicy.CLASS. 其含义如下:
      • SCOURCE : 注解将被编译器丢失(该类型的注解信息只会保留在源码里 , 源码经过编译后,注解信息会被丢失,不会保留在编译好的class文件里)
      • CLASS : 注解在class文件中可用,但会被JVM丢弃(该类型的注解信息会保留在源码和class文件里,在执行的时候,不会加载到虚拟机中)
      • RUNTIME : 注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取到注解的信息(医院吗,class文件和执行时都有注解的信息),eg:SpringMVC中的@Controller…
    2. @Target - 用来约束注解可以应用的地方(如方法,类或字段),其中ElementType是枚举类型. 若没有@target,则该Annotation(注解)可以用于任何地方
    3. @Documented - 标记这些注解是否包含在用户文档中.
    4. **@Inherited - 指示注解类型被自动继承.**如果在注解类型声明中存在Inherited 元注解,并且用户在某一类声明中查询该注解类型,同时该类声明中没有此类型的注解,则奖盖类的超类中自动查询该注解类型.

3. 自定义注解

package project10.annotation2;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;

@Retention(value = RetentionPolicy.RUNTIME )
@Target(value = {METHOD,TYPE,PARAMETER})
public @interface MyAnnotation {
    
    int id() default 0;//配置参数
    
    String name();
    
    double [] scource() default 0;
}

package project10.annotation2;
@MyAnnotation(id = 1,name = "lmx",scource = {1.1,2.2,3.3})
public class Student {


    private int age;



    @MyAnnotation(name = "lmx")
    public void run( int age){

    }
}

总结:

  • 定义注解的关键字是 @interface
  • 自定义注解中可以定义多个配置参数,不是成员方法,不是成员变量; 说明参数的名称,以及参数值的类型
  • 如果只有一个配置参数,一般命名为value
  • 如果配置参数是value,并且只有一个配置参数,value可以省略.

注意:

  • 定义注解时,意味着它实现了java.lang.annotation.Annotation接口,即该注解就是一个Annotation.
  • 和我们通常的implements实现接口的方法不同. Annotation接口的实现细节都由编译器完成.通过@interface定义注解后,该注解不能继承其他注解或接口.
  • 注解常见的API及其关系如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X4xA6EIw-1646832361660)(E:\java笔记\笔记\注解常见API及其关系.jpg)]

4. 使用反射读取注解

目前大部分框架都是用了注解简化代码并提高编码效率(使用注解之前使用的是xml进行配置)

ass Student {

private int age;



@MyAnnotation(name = "lmx")
public void run( int age){

}

}




**总结:**

- 定义注解的关键字是 @interface
- 自定义注解中可以定义多个配置参数,不是成员方法,不是成员变量; 说明参数的名称,以及参数值的类型
- 如果只有一个配置参数,一般命名为value
- 如果配置参数是value,并且只有一个配置参数,value可以省略.

**注意:**

- 定义注解时,意味着它实现了java.lang.annotation.Annotation接口,即该注解就是一个Annotation.
- 和我们通常的implements实现接口的方法不同. Annotation接口的实现细节都由编译器完成.通过@interface定义注解后,该注解不能继承其他注解或接口.
- 注解常见的API及其关系如下:

[外链图片转存中...(img-X4xA6EIw-1646832361660)]



## 4.  使用反射读取注解

目前大部分框架都是用了注解简化代码并提高编码效率(使用注解之前使用的是xml进行配置)



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值