若要学习别人写好的开源的框架(这些框架为了保持通用,在底层大量使用了反射和注解相关的知识和内容)所以,对反射注解的学习非常重要!
反射非常强大,甚至可以操作类的私有成员
- Java版本:java 14.0.1
- Java提供的方法随着Java版本的不同,会有一些细微的差别。如果本文一些方法的细节与你理解不同,请注意一下Java版本
反射
类 + 包 + 属性
反射是一个非常底层的知识
在类的基础上,又可以抽取出一层,有6个(可以描述任何的类,和类中间的成员):
- Class描述类本身
- Field描述类中的属性
- Method描述类中的方法
- Constructor描述类中的构造方法
- Package还有描述包的
- Annotation用来描述类中的注解,比如说
@Override
注解可以放置在类上面,属性上面,方法上面,构造方法上面,参数前面
这6个构在一起,就是反射的机制
就像File file = new File("路径");
,file对象(在内存中)通过路径和真实的文件产(在硬盘中)生映射关系
- 类是用来描述一组对象
- 反射机制认为是用来描述一组类
Class
获取Class的三种方式:
Class c = Class.forName("包名.类名");
//要写类的全名Class c = 类名.class;
//类必须要存在Class c = 对象.getClass();
//这是Object中的方法
类有自己的结构:权限修饰符,特征修饰符,类名字,继承,实现
Class常用方法:
getModifiers()
返回修饰符(包括权限修饰符、特征修饰符),而且返回值是整型。
返回值对应的修饰符:
getName()
返回值String,返回类全名(包名.类名)getSimpleName()
返回值String,只返回类名(不带包名)getPackage()
返回值是Package,返回类所在的包getSuperClass()
返回值Class,获取超类(即父类)getInterface()
返回值是Class[],获取当前类的所有父亲接口getClasses()
返回值Class[],返回内部类
通过以上七个方法,可以把一个类的结构描述清楚
但是我们所要的不是类啊,而是类中的成员(属性和方法)
Field
Field f = c.getFileld("属性名");
c是Class类型,返回值是Field,用来获取类的属性(只能是公有的)getModifiers()
获取属性的修饰符getName()
获取属性名getType()
获取属性的类型,返回值是Class
我们获取属性是为了操作属性(存值或取值);必须要有类的对象,才能对属性值进行操作
get(对象)
获取属性- 如果属性是通过Field获取的,返回值是Object,需要根据需求强制转换
- 如果属性是通过DeclaredField获取的,返回值是动态的,属性是什么类型就返回什么类型
set(对象, 值)
设置属性
不知道属性名,索性把公有属性全取出来
- Class有一个方法可以获取全部公有属性
getFields()
,返回值是Field[]
这个方法也是只能获取公有的属性,而且还包括继承而来的属性
反射很强,私有属性也可以操作(取值、存值)哦
Field[] f = c.getDeclaredField("属性名");
c是Class类型,返回值是Field,用来获取类的属性(包括私有)- Class还有一个方法可以获取全部属性(包括私有的)
getDeclaredFields()
,返回值Field[]- 这个方法可以获取本类中的全部属性,但不能获取父类的属性
- 除了private属性都可以直接进行赋值
- 操作私有属性还要在set()方法前面先调用一个方法
setAccessible(true)
Method
- 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);//获取带参数的
int modifiers = m.getModifiers();
Class mrt = m.getReturnType();
获取返回值的数据类型String name = m.getName();
Class[] mpts = m.getParameterTypes();
获取参数列表类型Class[] mets = m.getExceptionTypes();
获取方法抛出异常的类型
上面方法使我们可以获取方法的结构,但是重要的是如何操作方法
Object result = invoke(对象, 执行方法需要传递的参数...);
调用方法,也可以接收返回值
注意,返回值类型是Object,需要根据需求强制转换
不知道方法名,索性把公有方法全取出来
- Class的一个方法
Method[] ms = c.getMethods()
获取所有公有的方法(包括自己类的和父类的)
操作私有方法
- Class的一个方法
Method ms = c.getDeclaredMethod("方法名", Class...参数类型)
用来获取类方法(包括私有) - 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;
}
- String的不可变特性,体现在长度、内容
- value被final修饰,所以长度是无法修改的
- 内容却并不是一定不能被修改,反射技术可以做到这一点
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)
- IOC(Inversion of Control,控制反转)对象的控制权反转了
- DI(Dependency Injection,依赖注入)对象的控制权是别人的,别人创建对象的同时,帮我们自动注入属性值
注:DI 是 IOC 的一种体现 - 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
类实现了序列化接口,不添加序列号IDrawtypes
集合没有定义泛型deprecation
过时的方法unchecked
出现了泛型的问题,可以不检测all
所有的警告都不管,包含了以上所有
这些警告除了unchecked
都可以通过编码规范给去掉,所以不建议使用
5. 注解中可以携带信息,可以不携带信息
信息不能随便写 信息的类型只能是如下的类型:
- 基本数据类型
- String类型
- 枚举类型enum
- 注解类型annotation
- 数组类型[],数组的内部需要是如上的四种数据类型
6. 如何描述一个注解类型(元注解)
-
通过
@interface
定义一个新的注解类型 -
发现写法和接口非常相似(可以利用接口的特点来记忆注解)
- 可以描述 public static final 的属性(修饰符可以省略),比较少见
- 可以描述 public abstract 的方法(修饰符可以省略),返回值必须有,而且返回值类型是(基本数据类型、String、enum、annotation、数组)
-
我们自己定义的注解如果想要拿来使用
- 光定义还不够,还需要做很多细致的说明
- 细致的说明:需要利用Java提供好的注解(称之为元注解:是注解的一种,但不是拿来使用的,而是用来说明注解的)来说明
元注解通常有这么几类:
-
@Target
描述当前的这个注解可以放置在哪里写@Target(信息)
信息
是数组类型的,数组里面存放的值是ElementType
类型的
- 写起来好麻烦哦,每次写数组中的值,都要先写
Element.
;不过可以通过一种导包方式,简化这种写法
-
@Retention
retention是保留的意思,描述当前的注解存在什么作用域中- 注解是写在源代码上的,源代码文件SOURCE –> 编译 –> 字节码文件CLASS –> 加载 –> 内存执行RUNTIME
@retention(信息)
信息是一个类型,有三种值,一般使用RetentionPolicy.RUNTIME
-
@Inherited
描述当前这个注解能否被子类对象继承
它不需要携带信息的,就这么写,代表自定义的注解可以被子类继承 -
@Document
描述这个注解能否被文档所记录,不太常用
- 自己去使用自己描述的注解
1. 注解的方法做事,将我们传递给他的参数搬运走了,给了别人(以后解析注解的人)
2. 当注解只有一个方法,而且方法名为value
的时候,用的时候方法名可以省略不写了
3. 还可以像属性一样有默认值
7. 使用注解
利用反射技术解析注解(包括注解内部携带的信息)
【举个例子】自定义一个注解MyAnnotation
,并在某方法的属性上使用
- 红色方框圈起来的代码,如果不强求使用反射来写,一行代码就搞定了
`String[] values = ((MyAnnotation)a).value();
利用反射创建对象(终极版)
文章前面只使用了反射来完成 IOC+DI,其实并不完善,DI 注入属性值时只写了使用Scanner控制台输入,其实并不好。
在这里使用注解来完成 DI 注入属性值的功能:
- 代码:github
- 缺点(无法处理的数据类型):char、数组、集合、对象
今天的文章Java 反射和注解 基础知识总结分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/64483.html