深入理解Java中的反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
一:Class类
在面向对象的世界里,万物皆对象。类也是对象,类是java.lang.Class类的实例对象。
Class类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
基本的 Java 类型(
boolean
、byte
、char
、short
、int
、long
、float
和double
)和关键字void
也表示为 Class 对象。Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的
defineClass
方法自动构造的。
上面来自于JDK的罗里吧嗦,下面我来说下自己的体会:
类不是抽象的,类是具体的!
类是.class字节码文件,要想获取一个Class实例对象,首先需要获取.class字节码文件!
然后调用Class对象的一些方法,进行动态获取信息以及动态调用对象方法!
二:类类型
新建一个Foo类。Foo这个类也是实例对象,是Class的实例对象。
不知道你是否在意过类的声明与方法的声明:
public class Foo{
Foo(){
//构造方法
}
}
public Foo method(){
//...
}
我们知道public后跟返回类型,也就可以知道class也是一个类型。
如何表示Class的实例对象?
public static void main(String[] args) {
//Foo的实例对象,new 就出来了
Foo foo1 = new Foo();
//如何表示?
//第一种:告诉我们任何一个类都有一个隐含的静态成员变量class
Class c1 = Foo.class;
//第二种:已经知道该类的对象通过getClass方法
Class c2 = foo1.getClass();
System.out.println(c1 == c2);
//第三种:动态加载
Class c3 = null;
try {
c3 = Class.forName("cn.zyzpp.reflect.Foo");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(c2 == c3);
}
上述打印结果全是true
尽管 c1或c2 都代表了Foo的类类型,一个类只能是Class类的一个实例变量。
我们完全可以通过类的类类型(Class类型)创建类的实例对象。
//此时c1 c2 c3为Class的实例对象
try {
// Foo foo = (Foo)c1.newInstance();
Foo foo = (Foo)c3.newInstance();
foo.print();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
静态加载
new 创建对象是静态加载类,在编译时刻就需要加载所有的可能使用到的类 。
动态加载
使用 Class.forName("类的全称") 加载类称作为动态加载 。
编译时刻加载类是静态加载类,运行时刻加载类是动态加载类。
举个例子
定义Office类
public class Office {
public void print() {
System.out.println("office");
}
}
定义Loading类
public class Loading {
public static void main(String[] args) {
try {
//在运行时再动态加载类
//arg[0] 为java执行命令时传的参数
Class<?> a = Class.forName(args[0]);
Office office = (Office) a.newInstance();
office.print();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
执行过程
D:\>javac -encoding utf-8 Loading.java Office.java
D:\>java Loading Office
office
通过Class a=Class.forName(arg[0])
动态加载获取类,因编译时不知道使用哪个类,因此编译没有加载任何类,直接通过编译,运行时,根据 java Loading Office
(office是一个类类型/类,下标arg[0]),去确定a是哪个类。这就是动态加载。如果Office类不存在,此时运行会报错。这就是为何有时候会出现编译通过,运行报错的原因。
动态加载一个好处,就是可以随时增加需要编译的类。例如把Office改造为抽象类或接口,定义不同的子类,动态选择加载。
三:类的反射
通过上面的三种方法获取到类的类类型,就可以获取到该类的成员方法,成员变量,方法参数注释等信息。
方法对象是Method类,一个成员方法就是一个Method对象。
方法 | 解释 |
---|---|
getMethods() |
返回该类继承以及自身声明的所有public的方法数组 |
getDeclaredMethods() |
返回该类自身声明的所有public的方法数组,不包括继承而来 |
成员变量也是对象,是java.lang.reflect.Field对象,Field类封装了关于成员变量的操作。
方法 | 解释 |
---|---|
getFields() |
获取所有的public的成员变量信息,包括继承的。 |
getDeclaredFields() |
获取该类自己声明的成员变量信息,public,private等 |
获取Java语言修饰符(public、private、final、static)的int返回值,再调用Modifier.toString()
获取修饰符的字符串形式,注意该方法会返回所有修饰符。
方法 | 解释 |
---|---|
getModifiers() |
以整数形式返回由此对象表示的字段的 Java 语言修饰符。 |
获取注释
方法 | 解释 |
---|---|
getAnnotations() |
返回此元素上存在的所有注释。 |
getDeclaredAnnotations() |
返回直接存在于此元素上的所有注释。 |
构造函数也是对象,是java.lang.reflect.Constructor的对象。
方法 | 解释 |
---|---|
getConstructors() |
返回所有public构造方法 |
getDeclaredConstructors() |
返回类的所有构造方法,不止public |
完整示例
private void printClassMessage(Object obj){
//要获取类的信息,首先获取类的类类型
Class clazz = obj.getClass();
//获取类的名称
System.out.println(Modifier.toString(clazz.getModifiers())+" "+ clazz.getClass().getName()+" "+clazz.getName()+"{");
System.out.println("----构造方法----");
//构造方法
Constructor[] constructors = clazz.getDeclaredConstructors();
for (Constructor constructor: constructors){
//构造方法修饰符与名字
System.out.print(Modifier.toString(constructor.getModifiers())+" "+constructor.getName()+"(");
//构造函数的所有参数类型
Class[] parameterTypes = constructor.getParameterTypes();
for (Class c: parameterTypes){
System.out.print(c.getName()+", ");
}
System.out.println("){}");
}
System.out.println("----成员变量----");
//成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field: fields){
System.out.println(" "+Modifier.toString(field.getModifiers())+" "+field.getType().getName() + " " + field.getName()+";");
}
System.out.println("----成员方法----");
//Method类,方法对象,一个成员方法就是一个Method对象
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods){
//获取方法返回类型
Class returnType = method.getReturnType();
//获取方法上的所有注释
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation: annotations){
//打印注释类型
System.out.println(" @"+annotation.annotationType().getName()+" ");
}
//打印方法声明
System.out.print(" "+Modifier.toString(returnType.getModifiers())+" "+returnType.getName()+" "+method.getName()+"(");
//获取方法的所有参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
//获取方法的所有参数
Parameter[] parameters = method.getParameters();
for (Parameter parameter: parameters){
//参数的类型,形参(全是arg123..)
System.out.print(parameter.getType().getName()+" "+parameter.getName()+", ");
}
System.out.println(")");
}
System.out.println("}");
}
以String对象为例,打印结果:
public final java.lang.Class java.lang.String{
----构造方法----
public java.lang.String([B, int, int, ){}
java.lang.String([C, boolean, ){}
----成员变量----
private final [C value;
private int hash;
----成员方法----
@java.lang.Deprecated
public abstract final void getBytes(int arg0, int arg1, [B arg2, int arg3, )
......
}
四:方法的反射
定义了一个类Foo用于测试
public class Foo{
public void print(String name,int num) {
System.out.println("I am "+name+" age "+num);
}
}
目标:通过反射获取该方法,传入参数,执行该方法!
1.获取类的方法就是获取类的信息,获取类的信息首先要获取类的类类型
Class clazz = Foo.class;
2.通过名称+参数类型获取方法对象
Method method = clazz.getMethod("print", new Class[]{String.class,int.class});
3.方法的反射操作是通过方法对象来调用该方法,达到和new Foo().print()一样的效果
方法若无返回值则返回null
Object o = method.invoke(new Foo(),new Object[]{"name",20});
五:通过反射认识泛型
public static void main(String[] args) {
ArrayList<String> stringArrayList = new ArrayList<>();
stringArrayList.add("hello");
ArrayList arrayList = new ArrayList();
Class c1 = stringArrayList.getClass();
Class c2 = arrayList.getClass();
System.out.println(c1 == c2);
}
打印结果为true
c1==c2的结果返回说明编译之后集合的泛型是去泛型化的。换句话说,泛型不同,对类型没有影响。
Java中集合的泛型其实只是为了防止错误输入,只在编译阶段有效,绕过编译就无效。
验证
我们可以通过反射来操作,绕过编译。
public static void main(String[] args) {
ArrayList<String> stringArrayList = new ArrayList<>();
stringArrayList.add("hello");
ArrayList arrayList = new ArrayList();
Class c1 = stringArrayList.getClass();
Class c2 = arrayList.getClass();
System.out.println(c1 == c2);
try {
Method method = c1.getMethod("add",Object.class);
method.invoke(stringArrayList,20);
System.out.println(stringArrayList.toString());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
打印结果:
true
[hello, 20]
成功绕过了泛型<String>的约束。
深入理解Java中的反射机制的更多相关文章
- 深入理解Java中的反射机制和使用原理!详细解析invoke方法的执行和使用
反射的概念 反射: Refelection,反射是Java的特征之一,允许运行中的Java程序获取自身信息,并可以操作类或者对象的内部属性 通过反射,可以在运行时获得程序或者程序中的每一个类型的成员活 ...
- 夯实Java基础系列12:深入理解Java中的反射机制
本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...
- 浅说Java中的反射机制(二)
写过一篇Java中的反射机制,不算是写,应该是抄了,因为那是别人写的,这一篇也是别人写的,摘抄如下: 引自于Java基础--反射机制的知识点梳理,作者醉眼识朦胧.(()为我手记) 什么是反射? 正常编 ...
- 浅说Java中的反射机制(一)
在学习传智播客李勇老师的JDBC系列时,会出现反射的概念,由于又是第一次见,不免感到陌生.所以再次在博客园找到一篇文章,先记录如下: 引用自java中的反射机制,作者bingoideas.(()为我手 ...
- java中的反射机制在Android开发中的用处
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反 ...
- 【Java】深入理解Java中的spi机制
深入理解Java中的spi机制 SPI全名为Service Provider Interface是JDK内置的一种服务提供发现机制,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用 ...
- 【Java基础】java中的反射机制与动态代理
一.java中的反射机制 java反射的官方定义:在运行状态下,可以获取任意一个类的所有属性和方法,并且可通过某类任意一对象实例调用该类的所有方法.这种动态获取类的信息及动态调用类中方法的功能称为ja ...
- java 中利用反射机制获取和设置实体类的属性值
摘要: 在java编程中,我们经常不知道传入自己方法中的实体类中到底有哪些方法,或者,我们需要根据用户传入的不同的属性来给对象设置不同的属性值,那么,java自带的反射机制可以很方便的达到这种目的,同 ...
- Java 中的反射机制
JAVA反射机制 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法:这种动态获取的信息以及动态调用对象的方法的功能称为ja ...
随机推荐
- IIS做反向代理重定向到NodeJS服务器
1. 安装ARR 2. 建立虚拟目录并配置URL Rewrite 3. 启动ARR
- leetcode-217存在重复元素
leetcode-217存在重复元素 题意 给定一个整数数组,判断是否存在重复元素. 如果任何值在数组中出现至少两次,函数返回 true.如果数组中每个元素都不相同,则返回 false. 示例 1: ...
- (后端)解决code唯一码(java)简便方法
public String next() { long appBootTimes = systemVariableService.getAppBootTimes(); return Long.toSt ...
- Spring(mvc)思维导图
spring mvc简介与运行原理 Spring的模型-视图-控制器(MVC)框架是围绕一个DispatcherServlet来设计的,这个Servlet会把请求分发给各个处理器,并支持可配置的处理器 ...
- [20190322]测试相同语句遇到导致cursor pin S的疑问.txt
[20190322]测试相同语句遇到导致cursor pin S的疑问.txt--//昨天测试遇到的情况,链接:http://blog.itpub.net/267265/viewspace-26388 ...
- go语言打造个人博客系统(一)
go语言打造个人博客系统(一) 为什么选择go语言? 听说go语言是在几年前,但真正深入了解他却是在2017年,因为当时作为讲师 ,需要准备go语言的课程,结果稍一接触立刻就喜欢上这门语言了,作为 ...
- Django基础笔记
1.准备工作 .虚拟环境设置 python3 pip install virtualenv python -m venv env(虚拟环境文件名) env\Scripts\activate pip i ...
- c/c++ 数组的智能指针 使用
数组的智能指针 使用 数组的智能指针的限制: 1,unique_ptr的数组智能指针,没有*和->操作,但支持下标操作[] 2,shared_ptr的数组智能指针,有*和->操作,但不支持 ...
- python3 requests + BeautifulSoup 爬取阳光网投诉贴详情实例代码
用到了requests.BeautifulSoup.urllib等,具体代码如下. # -*- coding: utf-8 -*- """ Created on Sat ...
- SpringBoot实现热部署(修改class不需要重启)
热部署: devtools可以实现页面热部署(即页面修改后会立即生效, 这个可以直接在application.properties文件中配置spring.thymeleaf.cache=false来实 ...