U 需要知道的反射知识 -- 大白话

这篇博客详细介绍了Java反射机制,包括反射的前言、概念、流程、使用原因、获取对象、实例化对象、访问字段和方法、处理注解和泛型信息。通过反射,可以在运行时动态获取类的结构信息,降低代码耦合度,适用于框架开发和动态处理场景。

U 需要知道的反射知识 – 大白话

1. 反射前言

  • .java 类文件会被编译为 .class二进制流文件,类加载器将其加载进入内存中,JVM 堆内存中会生成一个 Class 对象,一个类对应于一个 Class对象,通过 Class 对象可以获得其在方法区中存储的类完整结构信息,包括字段,方法等属性

2. 何为反射

  • 反射 :程序在运行期间,可以通过 java 反射机制,动态获取某个类的结构信息(属性,方法等)
    • 动态 : 程序运行期间,无法修改源代码
    • 静态 :通过 new 创建对象,然后调用某个类的字段或者方法,明确知道要使用的某个确定的类
  • 总结 :通过反射机制,就能在程序运行期间,动态(用户自定义规则)去获取某个类的字段方法等属性

3.反射流程

​ 以调用 Person 类中 public get( ) 为例

  • 正常方式 :
    1. import Person 类包名
    2. 通过 new 关键字创建 Person 对象 p
    3. 对象调用方法 p.get( )
  • 反射方式 :
    1. 通过 Person 全类名获取 Class 类对象
    2. 通过 Class 对象 就可以得到访问当前 Person 类的 get( )

4. 为什么要使用反射

  • 反射基本是许多框架中使用到的技术,框架需要兼容每个使用者的要求,降低代码耦合度

  • 反射提供动态处理,可以实现按需加载,有效减少不必要的类加载到内存

    例如: JDBC 获取数据库连接参数的方式 (见下 :实例讲解)

  • 后续示例中使用到的类

    • 注解类
    @Target({ElementType.TYPE,ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.LOCAL_VARIABLE,ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
        String value();
    }
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Test {
        String value();
    }
    
    • Action 接口
    interface Action{
        void run();
        public final String food = "肉";
        //jdk11之后允许定义私有方法
        private void privateEate(){System.out.println("Action --> privateEate()");}
        private static void privateShow(){System.out.println("Action --> privateShow()");}
        public static void publicShow(){System.out.println("Action --> publicShow()");}
    }
    
    • World 类
    @Test(value = "world - Test")
    @MyAnnotation(value = "world - MyAnnotation")
    public class World {
        @MyAnnotation(value = "type")
        private String type;
        @MyAnnotation(value = "year")
        public int year;
        @Test(value = "origin")
        protected String origin;
        private String getType(){return type;};
        public int getYear(){return year;}
        protected String getOrigin(){return origin;}
    }
    
    • Person 类
    import java.util.ArrayList;
    @MyAnnotation(value = "person-MyAnnotation")
    @Test(value = "person Test")
    class Person extends World implements Action{
        @MyAnnotation(value = "name")
        private String name;
        @Test(value = "sex")
        protected String sex;
        @MyAnnotation(value = "age")
        public int age = 20;
        private ArrayList<String> toys = new ArrayList<>();
        public Person(String name, int age) { this.name = name;this.age = age;System.out.println("构造方法:Person(String name, int age)");}
        public Person(String name) { this.name = name;System.out.println("构造方法:Person(String name)");}
        public Person() {System.out.println("构造方法:Person()");}
        @MyAnnotation(value = "getName")
        private String getName(String name) {return name;}
        public String getNamePblic() {return name;}
        @MyAnnotation(value = "setName")
        public void setName(String name) {this.name = name;}
        protected int getAge() { return age;}
        public void setAge(int age) {this.age = age;}
        private void setAgePrivate(int age) {this.age = age;}
        public static void eat(String some){System.out.println("eat "+some);}
        @Override
        public void run() {System.out.println("run()");};
        public ArrayList<String> getToys(){toys.add("钢铁侠");toys.add("蜘蛛侠");return toys;}
        @Override
        public String toString() {return "Person [age=" + age + ", name=" + name + ", toys=" + toys + "]";}
    }
    

5. 通过反射如何获取 Class 对象

  • 通过已知的具体类调用 .class

  • Class p1 = Person.class;
    // 使用泛型方式在获取Person对象时会自动转化,不需要强转
    Class<Person> p2 = Person.class;
    
  • 通过 对象 调用 getClass() 方法获取

    Person person = new Person("王五");
    Class<? extends Person> class1 = person.getClass();
    

    注释:泛型通配符

    • <? extends Person> :表示泛型必须是 Person 类或者其子类,固定上限
    • <? super Person> :表示泛型必须是 Person 类或者其父类,固定下限
  • 通过 Class.forName(全类名)

    Class<?> forName = Class.forName("com.qin.Person");
    
  • 通过类加载器来获取

    Class<?> loadClass = Person.class.getClassLoader().loadClass("com.qin.Person");
    

6. 反射获取实例对象

  • 通过 Class 对象获得当前类的构造器,然后通过构造器创建对象

  • 通过反射创建类对象时,该类必须提供构造方法

    • Class 对象调用 newInstance(),该方法使用时需要提供 Person 类的无参构造方法,否则会抛出异常 ,jdk9之后不推荐使用
    Class<?> clazz = Class.forName("com.qin.Person");
    Person person1= (Person) clazz.newInstance();
    System.out.println(person1.toString());
    
    //执行结果:Person [age=0, name=null, toys=[]]
    
    • Constructor 构造器调用 newInstance() 方法,推荐使用,可以指定创建对象时,调用指定 Person 类的构造方法
    //第一个参数表示构造方法的参数类型,第二个参数是实例化的值
    Person person2 = (Person) clazz.getDeclaredConstructor(String.class).newInstance("王五");
    Person person3 = (Person) clazz.getDeclaredConstructor(String.class,int.class).newInstance("王五",18);
    System.out.println(person2.toString());
    System.out.println(person3.toString());
    //执行结果:
    //构造方法:Person()
    //Person [age=20, name=null, toys=[]]   
    //构造方法:Person(String name)
    //构造方法:Person(String name, int age)
    //Person [age=20, name=王五, toys=[]]   
    //Person [age=18, name=王五, toys=[]] 
    

7. 获取类字段属性

  • getField ( String name ) :通过指定名称获取某个特定修饰符为 public 的字段

    Class<?> clazz = Class.forName("com.qin.Person");
    Person person = (Person) clazz.getDeclaredConstructor(String.class,int.class).newInstance("王五",18); 
    
    //通过指定属性名获取 public 类型字段
    Field age = clazz.getField("age");
    //通过指定属性名获取 任意 类型字段
    Field name = clazz.getDeclaredField("name");
    //获取字段名称
    System.out.println(age.getName());
    //获取字段类型
    System.out.println(age.getType());
    //通过person对象获取字段的值
    System.out.println(age.get(person));
    
  • 获取所有修饰符为的 public 字段属性,包括父类中的字段

    //获取所有的public字段属性,包括父类中的字段,包括接口中的字段
    Field[] fields1 = clazz.getFields();
    for (Field field1 : fields1) {
        System.out.println(field1.getName());
    }
    //执行结果:
    //age       
    //food      
    //year      
    
  • 获取当前类中的所有修饰符的字段包括 private

    //获取当前类中的所有属性的字段包括private
    Field[] fields2 = clazz.getDeclaredFields();
    for (Field field2 : fields2) {
        System.out.println(field2.getName());
    }
    //执行结果:
    //name      
    //sex       
    //age       
    //toys 
    

8.获取类方法属性

  • getMethod( "方法名", 类型名.class) :通过方法名和返回值获取修饰符为 public 方法属性

    Method method1 = clazz.getMethod("setName", String.class);
    
  • getDeclaredMethod( "方法名", 类型名.class) :通过方法名和返回值获取所有修饰符的方法属性

    //第二个参数表示返回值类型,void 可传入 null
    Method method2 = clazz.getDeclaredMethod("getAge", null);
    
  • getMethods() :通过方法名获取当前类,所有父类中修饰符为 public 的方法,不包括接口中的 pulbic 方法

    Method[] methods = clazz.getMethods();
    	for (Method method : methods) {
    	System.out.println(method);
    }
    
  • getDeclaredMethods() :获取当前类所有的方法,不包括构造方法

    Method[] methods1 = clazz.getDeclaredMethods();
        for (Method method1 : methods1) {
        System.out.println(method1);
    }
    

9. Class 类方法操作说明

  • 通过反射获得的 Class 对象,可以获取类中的 字段,方法,类型,构造方法,修饰符,泛型,注解等信息

  • Class 类大多数方法 包含 Declared 与 不包含 Declared 的区别:

    • 如果方法名中含有 Declared(比如:getDeclaredMethods(String name))表示获取类中的任意修饰符为的方法

    • 如果方法名中没有 Declared 表示只能获取修饰符为 public 的方法

      getMethods() 这类返回 Method 数组的不表示 返回当前类中的所有 public 方法,而是所有类,包括父类中修饰符为 public 的方法

      都理解为不包含 Declared 只返回 public 修饰符的结构属性

10.获取注解信息

  • 获取对象上所有的注解信息,无法获取继承下来的注解

    Annotation[] annotations = clazz.getAnnotations();
    Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();
    for (Annotation annotation : annotations) {
        System.out.println(annotation.toString());
    }
    
  • 只有注解被标注为 @Retention(RetentionPolicy.RUNTIME) 的才能获取到该注解

    • RetentionPolicy.RUNTIME :该注解将会一直保留到运行时 runtime
    • RetentionPolicy.SOURCE :该注解只保留在源文件中,类似于标记作用
    • RetentionPolicy.CLASS :该注解只保留到 class 文件中,也就是随着编译器编译到字节码中,运行时反射不对解析改注解

11.获取泛型信息

  • 如何获取泛型信息

    • 首先获得 Method 对象来获取方法上面的泛型

    • getGenericParameterTypes() 获取方法参数集合

    • 如果参数类型为 ParameterizedType 类型,需要强转为 ParameterizedType

    • 然后获取方法所有参数的实际类型 getActualTypeArguments()

    • 通过调用 Type 类的 getTypeName() 来获取名称

      //1、通过全类名获取class对象
      Class<?> clazz = Class.forName("com.qin.Person");
      //2、通过指定方法名获取名字为 setToys 的方法
      Method method = clazz.getDeclaredMethod("setToys",ArrayList.class);
      //3、获取方法的所有参数
      Type[] types = method.getGenericParameterTypes();
      //3、循环遍历方法参数
      for (Type type : types) {
          //4、如果参数是属于 ParameterizedType 类型
          if(type instanceof ParameterizedType){
              //5、获取实际的泛型参数类型
              Type[] actualTypeArguments = ((ParameterizedType)type).getActualTypeArguments();
              //6、循环遍历获取泛型类型
              for (Type aType : actualTypeArguments) {
                  System.out.println(aType.getTypeName());
                  if(aType instanceof ParameterizedType){
                      //7、获取实际的泛型参数类型
                      Type[] actualTypeArguments1 = ((ParameterizedType)aType).getActualTypeArguments();
                      //8、循环遍历获取泛型类型
                      for (Type aType1 : actualTypeArguments1) {
                          System.out.println(aType1.getTypeName());
                      }
                  }
              }
          }
      }
      

12.获取字段以及方法调用操作实践

  • 如何通过反射获取某个类中的字段,以及如何重置字段中的值

    1. 全类名获取 class 对象

    2. 通过 Constructor 中的 newInstance() 创建 Person 对象

    3. 根据字段修饰符执行不同的操作

      • 如果字段修饰符为 Public ,可以使用 getField() 或者 getDeclaredField() 来获取字段

      • 如果字段修饰符不为 Public ,则需要调用 getDeclaredField() 来获取字段

        • 同时需要调用 setAccessible(true) 设置接入访问

        • 否则会抛出如下异常:

          Exception in thread "main" java.lang.IllegalAccessException: class com.qin.App cannot access a member of class com.qin.Person with modifiers "private"
          
    4. 调用 get() 方法,传入类对象获取属性值

    5. 调用 set() 方法,传入类对象以及需实际值来设置属性的值

    //1、通过全类名获取 class 对象
    Class<?> clazz = Class.forName("com.qin.Person");
    //2、通过Constructor创建 Person 对象
    Person person = (Person) clazz.getDeclaredConstructor(String.class,int.class).newInstance("王五",18); 
    //3、通过方法名获取方法:name 修饰符为 private
    Field name = clazz.getDeclaredField("name");
    //4、调用 get() 方法,传入类对象 获取属性值
    String str = (String) name.get(person);
    System.out.println(str);
    //5、设置接入访问为 true
    name.setAccessible(true);
    //6、调用 set(),修改属性值
    name.set(person, "李四");
    System.out.println(person.getNamePblic());
    
  • 如何通过反射调用类中的方法

    1. 全类名获取 class 对象

    2. 通过 Constructor 中的 newInstance() 创建 Person 对象

    3. 根据方法修饰符执行不同的操作

      • 如果调用的方法修饰符为 Public ,可以使用 getMethod() 或者 getDeclaredMethod() 来获取方法结构 Method

      • 如果调用的方法修饰符不为 Public ,则需要调用 getDeclaredMethod() 来获取方法

        • 同时需要调用 setAccessible(true) 设置接入访问

        • 否则会抛出如下异常:

          Exception in thread "main" java.lang.IllegalAccessException: class com.qin.App cannot access a member of class com.qin.Person with modifiers "private"
          
    4. 调用 invoke() 方法,传入对应类对象,以及参数值

    5. invoke() 的返回值即是类中该方法的返回值

    //1、通过全类名获取class对象
    Class<?> clazz = Class.forName("com.qin.Person");
    //2、通过Constructor(参数类型)中的newInstance(实参数值)创建 Person对象
    Person person = (Person) clazz.getDeclaredConstructor(String.class,int.class).newInstance("王五",18); 
    //3、通过方法名获取方法:setName为public 修饰符
    Method setName = clazz.getMethod("setName", String.class);
    //4、调用方法,传入类对象,以及参数值
    setName.invoke(person, "张三");
    System.out.println(person.getNamePblic());
    
    //3、通过方法名获取方法:setAgePrivate 为私有方法
    Method setAgePrivate = clazz.getDeclaredMethod("setAgePrivate", int.class);
    //4、设置允许接入访问
    setAgePrivate.setAccessible(true);
    //5、、调用方法,传入类对象,以及参数值
    setAgePrivate.invoke(person, 50);
    System.out.println(person.getAge());
    

13.实例解析

  • 动态代理

    • Proxy:完成代理的操作类,是所有动态代理类的父类

    • 通过其静态方法 static Object newProxyInstance(ClassLoader loader, Class<?>...interface, InvocationHandler h) 直接创建一个动态代理对象

    • 实现动态代理的步骤

      • 创建被代理的类,实现相应功能接口
      • 实现 InvocationHandler 接口,并重写其 invoke(Object proxy, Method method, Object[] args) 方法
        • 方法内部通过 method.invoke(obj, args)直接调用 invoke() 方法实现被代理类的方法调用
        • Object obj 参数可通过实现类构造方法传入或者提供一个赋值的方法
      • 通过 Proxy.newProxyInstance() 创建代理对象转为接口,多态
      • 通过代理对象调用被代理类的方法
      public class App {
          public static void main(String[] args) throws Exception{
              Action action = (Action) ProxyFactory.getProxyObject(new Person("王老五",30));
              action.run();
          }
      }
      
      class ProxyFactory{
          public static Object getProxyObject(Object object){
              MyInvocationHandler handler = new MyInvocationHandler();
              handler.bind(object);
              return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), handler);
          }
      }
      
      class MyInvocationHandler implements InvocationHandler{
      
          private Object obj;
      
          public void bind(Object obj){
              this.obj = obj;
          }
      
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              return method.invoke(obj, args);
          }
          
      }
      
卷积神经网络(CNN)是一种常用于图像处理和模式识别的深度学习模型。它的设计灵感来自于生物学中视觉皮层的神经元结构。为了用通俗的语言解释CNN,我们可以用以下方式来理解它: 假设你要识别一张猫的图片。首先,你的大脑会将这张图片的像素点转化成一系列数字,并且记录下它们的位置和颜色。然后,大脑会将这些数字输入到“卷积层”中。 在卷积层中,会有很多个“过滤器”。这些过滤器可以视为一双眼睛,它们通过抓取图片的不同特征来帮助你识别物体。每个过滤器都在图片上滑动并计算一个“特征图”,这个特征图描述了所检测到的特定特征。例如,一个过滤器可以检测到猫的边缘,另一个可以检测到猫的颜色等等。当所有过滤器完成计算后,就会得到一些不同的特征图。 在“池化层”中,每个特征图都会被压缩,去除一些不重要的信息。这样可以减少需要计算的数据量,并且使得特征更加鲁棒和不变形。 最后,在全连接层中,所有的特征图都被连接起来,形成一个巨大的向量。接下来,这个向量会通过一些神经元节点,最终输出识别结果,也就是“这是一张猫的图片”。 CNN的一个重要特点是参数共享,这意味着每个过滤器会在整个图片上进行计算,而不仅仅是某个局部区域。这样可以减少需要计算的参数量,提高训练速度和模型的泛化能力。 总结一下,CNN通过卷积层来提取图像的特征,并通过池化层降低特征的维度。最后,通过全连接层将所有特征连接起来并输出结果。这种结构使得CNN非常适合于图像分类和识别任务。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值