Java 反射和注解 基础知识总结

Java 反射和注解 基础知识总结若要学习别人写好的开源的框架(这些框架为了保持通用,在底层大量使用了反射和注解相关的知识和内容)所以,对反射注解的学习非常重要!反射非常强大,甚至可以操作类的私有成员文章目录反射ClassField

若要学习别人写好的开源的框架(这些框架为了保持通用,在底层大量使用了反射和注解相关的知识和内容)所以,对反射注解的学习非常重要!

反射非常强大,甚至可以操作类的私有成员

  • Java版本:java 14.0.1
  • Java提供的方法随着Java版本的不同,会有一些细微的差别。如果本文一些方法的细节与你理解不同,请注意一下Java版本

反射

类 + 包 + 属性

反射是一个非常底层的知识

在类的基础上,又可以抽取出一层,有6个(可以描述任何的类,和类中间的成员):

  1. Class描述类本身
  2. Field描述类中的属性
  3. Method描述类中的方法
  4. Constructor描述类中的构造方法
  5. Package还有描述包的
  6. Annotation用来描述类中的注解,比如说@Override
    注解可以放置在类上面,属性上面,方法上面,构造方法上面,参数前面

这6个构在一起,就是反射的机制

就像File file = new File("路径");,file对象(在内存中)通过路径和真实的文件产(在硬盘中)生映射关系
在这里插入图片描述

  • 类是用来描述一组对象
  • 反射机制认为是用来描述一组类

Class

获取Class的三种方式:

  1. Class c = Class.forName("包名.类名");//要写类的全名
  2. Class c = 类名.class;//类必须要存在
  3. Class c = 对象.getClass();//这是Object中的方法

类有自己的结构:权限修饰符,特征修饰符,类名字,继承,实现

Class常用方法:

  1. getModifiers()返回修饰符(包括权限修饰符、特征修饰符),而且返回值是整型。
    返回值对应的修饰符:
    在这里插入图片描述
  2. getName()返回值String,返回类全名(包名.类名)
  3. getSimpleName()返回值String,只返回类名(不带包名)
  4. getPackage()返回值是Package,返回类所在的包
  5. getSuperClass()返回值Class,获取超类(即父类)
  6. getInterface()返回值是Class[],获取当前类的所有父亲接口
  7. getClasses()返回值Class[],返回内部类

通过以上七个方法,可以把一个类的结构描述清楚
但是我们所要的不是类啊,而是类中的成员(属性和方法)

Field

  1. Field f = c.getFileld("属性名");c是Class类型,返回值是Field,用来获取类的属性(只能是公有的)
  2. getModifiers()获取属性的修饰符
  3. getName()获取属性名
  4. getType()获取属性的类型,返回值是Class

我们获取属性是为了操作属性(存值或取值);必须要有类的对象,才能对属性值进行操作

  1. get(对象)获取属性
    • 如果属性是通过Field获取的,返回值是Object,需要根据需求强制转换
    • 如果属性是通过DeclaredField获取的,返回值是动态的,属性是什么类型就返回什么类型
  2. set(对象, 值)设置属性

不知道属性名,索性把公有属性全取出来

  1. Class有一个方法可以获取全部公有属性getFields(),返回值是Field[]
    这个方法也是只能获取公有的属性,而且还包括继承而来的属性

反射很强,私有属性也可以操作(取值、存值)哦

  1. Field[] f = c.getDeclaredField("属性名");c是Class类型,返回值是Field,用来获取类的属性(包括私有)
  2. Class还有一个方法可以获取全部属性(包括私有的)getDeclaredFields(),返回值Field[]
    • 这个方法可以获取本类中的全部属性,但不能获取父类的属性
    • 除了private属性都可以直接进行赋值
    • 操作私有属性还要在set()方法前面先调用一个方法setAccessible(true)

Method

  1. Class的一个方法getMethod("方法名", Class...参数类型),用来获取类(包括父类)的公有方法,返回值是Method
    • 在学Field时,可以通过属性名来获取属性,因为属性名是唯一的
    • 但是方法存在重载这个玩意,还需要参数列表:通过方法名定位方法,通过方法参数类型对应的Class来精确定位
    • 参数中只写方法名,即获取(无参)方法

比如说有一个方法(有重载)

public void sleep(){ 
   }
public void sleep(int time){ 
   }

那么:

//c是上述方法所在类的Class

Method m1 = c.getMethod("sleep");//获取无参的
Method m2 = c.getMethod("sleep", int.class);//获取带参数的
  1. int modifiers = m.getModifiers();
  2. Class mrt = m.getReturnType();
    获取返回值的数据类型
  3. String name = m.getName();
  4. Class[] mpts = m.getParameterTypes();
    获取参数列表类型
  5. Class[] mets = m.getExceptionTypes();
    获取方法抛出异常的类型

上面方法使我们可以获取方法的结构,但是重要的是如何操作方法

  1. Object result = invoke(对象, 执行方法需要传递的参数...);调用方法,也可以接收返回值
    注意,返回值类型是Object,需要根据需求强制转换

不知道方法名,索性把公有方法全取出来

  1. Class的一个方法Method[] ms = c.getMethods()获取所有公有的方法(包括自己类的和父类的)

操作私有方法

  1. Class的一个方法Method ms = c.getDeclaredMethod("方法名", Class...参数类型) 用来获取类方法(包括私有)
  2. Class还有一个方法可以获取全部方法(包括私有的)Method[] ms = c.getDeclaredMethods()
    • 这个方法可以获取本类中的全部方法,但不能获取父类的方法
    • 除了private方法都可以直接进行调用
    • 调用私有方法还要在invoke()方法前面先调用一个方法setAccessible(true)

Constructor

获取构造方法

Constructor  con = c.getConstructor(Class...参数类型);//自己类+父类的公有构造方法
Constructor con = c.getDeclaredConstructor(Class...参数类型);//自己类的构造方法(包括私有)

Constructor[] cons = clazz.getConstructors();//自己类+父类的全部公有构造方法
Constructor[] cons = clazz.getDeclaredConstructors();//自己类的全部构造方法(包括私有)

Constructor类中的常用方法

int modifiers = con.getModifiers();
String name = con.getName();
Class[] cpts = con.getParameterTypes();
Class[] cets = con.getExceptionTypes();

//执行无参构造方法
con.newInstance();//返回值是Object

//执行带参数构造方法
con.newInstance(参数...);

//执行私有构造方法之前,需要
con.setAccessible(true);

利用反射修改String的内容

在学习Java反射时,不论是通过何种方式都不约而同的提到,可以通过反射修改String的内容

但是,随着Java版本的变更,String底层也屡经修改

  • java9之前String底层数组的实现采用的是char数组
public final class String{ 
   
	private final char[] value;
}
  • 自java9开始,String底层采用byte数组和编码标识来识别
public final class String{ 
   
	private final byte[] value;
	private final byte coder;
}
  1. String的不可变特性,体现在长度、内容
  2. value被final修饰,所以长度是无法修改的
  3. 内容却并不是一定不能被修改,反射技术可以做到这一点

Java9之前

import java.lang.reflect.Field;

public class ChangeStringValue { 
   
    public static void main(String[] args) throws Exception{ 
   
        String str = new String("zgh");
        System.out.println(str);//zgh

        //反射的技术 可以获取私有属性,还可以操作私有属性
        //1.获取String类对应的那个Class
        Class c = str.getClass();
        
        //2.通过c获取String中的value属性
        Field field =c.getDeclaredField("value");
        
        //3.设置私有属性可以修改
        field.setAccessible(true);
        
        //4.获取value属性里面的值
        char[] t = (char[]) field.get(str);
        
        //5.通过t的地址引用 找到真实String对象中的数值
        // 修改数组内的每一个元素
        t[0] = 'Z'; t[1] = 'G'; t[2] = 'H';
        
        //输出修改后的str
        System.out.println(str);//ZGH
    }
}

自Java9开始

import java.lang.reflect.Field;

public class ChangeStringValue { 
   
    public static void main(String[] args) throws Exception{ 
   
        String str = new String("zgh");
        System.out.println(str);//zgh

        //反射的技术 可以获取私有属性,还可以操作私有属性
        //1.获取String类对应的那个Class
        Class c = str.getClass();
        
        //2.通过c获取String中的value属性
        Field field =c.getDeclaredField("value");
        
        //3.设置私有属性可以修改
        field.setAccessible(true);
        
        //4.获取value属性里面的值
        byte[] t = (byte[]) field.get(str);
        
        //5.通过t的地址引用 找到真实String对象中的数值
        // 修改数组内的每一个元素
        t[0] = (byte)'Z'; t[1] = (byte)'G'; t[2] = (byte)'H';
        
        //输出修改后的str
        System.out.println(str);//ZGH
    }
}
  • 我如今使用的Java版本为Java14.0.1,此代码依旧可以成功改变String的值
  • 以后,谁知道会怎么样呢~~~
  • 可能以后的Java版本就不能借助反射修改String的值,谁知道呢 >_<

利用反射创建对象

利用反射设计一个小工具(替代我们自己创建对象的过程)String –> 类 –> 对象

这是Spring(开源的一个框架)的思想,Spring有两个非常重要的思想(IOC、AOP)

  1. IOC(Inversion of Control,控制反转)对象的控制权反转了
  2. DI(Dependency Injection,依赖注入)对象的控制权是别人的,别人创建对象的同时,帮我们自动注入属性值
    注:DI 是 IOC 的一种体现
  3. AOP(Aspect Oriented Programming,面向切面编程)

IOC

public class MySpring { 
   
    public Object getBean(String className){ 
   //参数为类全名
        Object obj = null;
        try { 
   
            Class c = Class.forName(className);
            obj = c.getDeclaredConstructor().newInstance();
        } catch (Exception e) { 
   
            e.printStackTrace();
        }
        return obj;
    }
}

IOC + DI

以下代码的DI体现在:给MVC分层思想的domian层中的实体类自动注入属性值

//导包...
public class MySpring { 
   
    public Object getBean(String className){ 
   //参数为类全名
        Object obj = null;
        try { 
   
            Class c = Class.forName(className);
            obj = c.getDeclaredConstructor().newInstance();

            //把所有的属性都找到
            Field[] fs = c.getDeclaredFields();
            for(Field field:fs){ 
   
                //1.获取属性名
                String fieldName = field.getName();
                
                //2.手动的拼接串---setXXX方法
                String first = fieldName.substring(0,1).toUpperCase();
                String other = fieldName.substring(1);
                StringBuilder builder = new StringBuilder("set");
                builder.append(first);
                builder.append(other);
                
                //3.获取属性的类型,为了找寻setXXX方法时传递参数
                Class fieldTypeClass = field.getType();
                
                //4.通过处理好的setXXX方法名,找寻类中的setXXX方法
                Method m = c.getMethod(builder.toString(), fieldTypeClass);
                
                //5.赋值(读取配置文件 或者 注解)
                m.invoke(obj,);
            }
        } catch (Exception e) { 
   
            e.printStackTrace();
        }
        return obj;
    }
}

如果属性值是通过用户在控制台输入,并且 DI 注入的属性值的类型是除了 char 的其它七个基本类型或者String,可以将就着这么写

//5.赋值(用户在控制台进行输入)
System.out.println("请给"+ fieldName + "属性赋值");
String value = input.nextLine();

//补充:八个基本类型的包装类有七个都含有带String的构造方法,
//除了Character都有参数为String的构造方法
Constructor con = fieldTypeClass.getConstructor(String.class);
m.invoke(obj, con.newInstance(value));

IOC + DI + 泛型

//导包...
public class MySpring { 
   
    public <T>T getBean(String className){ 
   //参数为类全名
        T obj = null;
        try { 
   
            Class c = Class.forName(className);
            obj = (T)c.getDeclaredConstructor().newInstance();
            
            Field[] fs = c.getDeclaredFields();
            for(Field field:fs){ 
   
                String fieldName = field.getName();
                String first = fieldName.substring(0,1).toUpperCase();
                String other = fieldName.substring(1);
                StringBuilder builder = new StringBuilder("set");
                builder.append(first);
                builder.append(other);
                
                Class fieldTypeClass = field.getType();
                Method m = c.getMethod(builder.toString(), fieldTypeClass);
                m.invoke(obj,);
            }
        } catch (Exception e) { 
   
            e.printStackTrace();
        }
        return obj;
    }
}

IOC + DI + 泛型 + 生命周期托管(实现单例模式)

//导包...
public class MySpring { 
   

	//属性 为了存储所有被管理的对象
    private static HashMap<String,Object> beanBox = new HashMap<>();
    
    public static <T>T getBean(String className){ 
   //参数为类全名
        T obj = null;
        try { 
   
            Class c = Class.forName(className);

			//1.直接从beanBox集合中获取
            obj = (T)beanBox.get(className);
            //2.如果obj是null,证明之前没有创建过对象
            if (obj == null){ 
   
                obj = (T)c.getDeclaredConstructor().newInstance();
                beanBox.put(className, obj);
            }

            Field[] fs = c.getDeclaredFields();
            for(Field field:fs){ 
   
                String fieldName = field.getName();
                String first = fieldName.substring(0,1).toUpperCase();
                String other = fieldName.substring(1);
                StringBuilder builder = new StringBuilder("set");
                builder.append(first);
                builder.append(other);
                
                Class fieldTypeClass = field.getType();
                Method m = c.getMethod(builder.toString(), fieldTypeClass);
                m.invoke(obj,);
            }
        } catch (Exception e) { 
   
            e.printStackTrace();
        }
        return obj;
    }
}

注解

注解(Annotation)注释不同

注释的写法:

  • 单行注释//
  • 多行注释/* */
  • 文档注释/** */

1. 注解的写法:

  • @XXX
  • 或者上面的写法后面还跟着一个括号,里面携带一些信息@XXX[一些信息]
    这是注解最常用的用法

2. 注解放置在哪儿

  • 类的上面
  • 类成员(属性、方法、构造方法)的上面
  • 变量的上面
  • 参数的前面

3. 注解的作用

  • 注解的作用1:充当注释的作用(仅仅是一个文字的说明)

举个例子】在平时用Idea写Java时,会遇到一些方法上面有横线提示(这种方法已过时,虽然也可以用,但是已经被更好的方法替代了),比如:
在这里插入图片描述
那编辑器怎么知道这些方法是过时的呢?
随便打开一个,看一下源码:
在这里插入图片描述
@Deprecated仅仅是充当注释的作用:提示这个方法已经过时

  • 注解的作用2:用来做代码的检测(验证)

举个例子】重写和重载容易弄错,如果在方法上面加上注解@Override,就可以检测出自己写的是否是自己想写的重写方法,

这种注解还可以帮我们做代码的检测(减少写代码出错的次数)

  • 注解的作用3:可以携带一些信息(内容)
    • 超级有用!
    • 变量、数组、集合、对象虽然也可以携带信息,都存储在内存中,是临时性的;
    • 文件(.properties、.xml …)在硬盘上存储,是永久性的
    • 文件和代码是隔离的(在开发中,调试代码,来回的弄,很不方便);
    • 但是注解是写在代码中(开发中更方便,但是没有文件这么灵活,代码打包后注解就无法修改了)

4. Java中预定义好的注解

  • @Deprecated 用来说明方法是废弃的
  • @Override 用来做代码的检测,检测此方法是否是一个重写方法
  • @SuppressWarnings(信息) 去除警告信息
    • 信息的类型是String[]
    • 如果信息的只有一个,信息的类型还可以就是String

举个例子
在这里插入图片描述
在Idea中,str因为没有被调用过,str下面有波浪号警告;其实可以用注解@SuppressWarnings(信息)把这种警告去掉。

信息有很多种值,例如unused,就可以把变量定义后未被使用的警告去掉
在这里插入图片描述
因为@SuppressWarnings(信息)中的信息只写了一个,可以把大括号去掉
在这里插入图片描述
但是这玩意,尽量不要用


@SuppressWarnings(信息)信息的值:

  • unused 变量定义后未被使用
  • serial 类实现了序列化接口,不添加序列号ID
  • rawtypes 集合没有定义泛型
  • deprecation 过时的方法
  • unchecked 出现了泛型的问题,可以不检测
  • all 所有的警告都不管,包含了以上所有

这些警告除了unchecked都可以通过编码规范给去掉,所以不建议使用

5. 注解中可以携带信息,可以不携带信息

信息不能随便写 信息的类型只能是如下的类型:

  • 基本数据类型
  • String类型
  • 枚举类型enum
  • 注解类型annotation
  • 数组类型[],数组的内部需要是如上的四种数据类型

6. 如何描述一个注解类型(元注解)

  1. 通过@interface定义一个新的注解类型

  2. 发现写法和接口非常相似(可以利用接口的特点来记忆注解)

    • 可以描述 public static final 的属性(修饰符可以省略),比较少见
    • 可以描述 public abstract 的方法(修饰符可以省略),返回值必须有,而且返回值类型是(基本数据类型、String、enum、annotation、数组)
  3. 我们自己定义的注解如果想要拿来使用

    • 光定义还不够,还需要做很多细致的说明
    • 细致的说明:需要利用Java提供好的注解(称之为元注解:是注解的一种,但不是拿来使用的,而是用来说明注解的)来说明

元注解通常有这么几类:

  • @Target 描述当前的这个注解可以放置在哪里写

    • @Target(信息) 信息是数组类型的,数组里面存放的值是ElementType类型的
      在这里插入图片描述
    • 写起来好麻烦哦,每次写数组中的值,都要先写Element.;不过可以通过一种导包方式,简化这种写法
      在这里插入图片描述
  • @Retention retention是保留的意思,描述当前的注解存在什么作用域中

    • 注解是写在源代码上的,源代码文件SOURCE –> 编译 –> 字节码文件CLASS –> 加载 –> 内存执行RUNTIME
    • @retention(信息) 信息是一个类型,有三种值,一般使用RetentionPolicy.RUNTIME
      在这里插入图片描述
  • @Inherited 描述当前这个注解能否被子类对象继承
    它不需要携带信息的,就这么写,代表自定义的注解可以被子类继承

  • @Document 描述这个注解能否被文档所记录,不太常用

  1. 自己去使用自己描述的注解

1. 注解的方法做事,将我们传递给他的参数搬运走了,给了别人(以后解析注解的人)
在这里插入图片描述
2. 当注解只有一个方法,而且方法名为value的时候,用的时候方法名可以省略不写了

在这里插入图片描述
3. 还可以像属性一样有默认值
在这里插入图片描述

7. 使用注解

利用反射技术解析注解(包括注解内部携带的信息)

举个例子】自定义一个注解MyAnnotation,并在某方法的属性上使用
在这里插入图片描述
在这里插入图片描述

  • 红色方框圈起来的代码,如果不强求使用反射来写,一行代码就搞定了
`String[] values = ((MyAnnotation)a).value();

利用反射创建对象(终极版)

文章前面只使用了反射来完成 IOC+DI,其实并不完善,DI 注入属性值时只写了使用Scanner控制台输入,其实并不好。

在这里使用注解来完成 DI 注入属性值的功能:

  • 代码:github
  • 缺点(无法处理的数据类型):char、数组、集合、对象

今天的文章Java 反射和注解 基础知识总结分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/64483.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注