关于JAVA 反射 基础知识/编码经验的一些总结
Posted 山河已无恙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于JAVA 反射 基础知识/编码经验的一些总结相关的知识,希望对你有一定的参考价值。
写在前面
- 温习一下毕业以来学习的东西。准备做成一个系列。所以对于每一部分技术点进行一个笔记整理。更多详见 java面试的一些总结
- 笔记主要是以网上开源的一本
《Java核心面试知识整理》
面试笔记为原型,结合工作中学习
的知识。《Effective Java》
、《编写高质量代码(改善Java程序的151个建议)》
这两本书为方向进行整理。 - 笔记立足
DevOps
。开发+运维+测试三个方向 ,面向对JAVA有一定了解
的小伙伴用于温习
,因为理论较多一点
。在不断更新,博文内容理解不足之处请小伙伴留言指正。
傍晚时分,你坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺
了。当时我是个年轻人,但我害怕这样生活下去,衰老下去。在我看来,这是比死亡更可怕的事
。--------王小波
二、JAVA 反射
动态语言
动态语言,是指程序在运行时可以改变其结构
:新的函数可以引进,已有的函数可以被删除等结构上的变化。比如常见的 javascript 就是动态语言
,除此之外 Ruby,Python 等也属于动态语言
,而 C、C++则不属于动态语言
。从反射角度说 JAVA 属于半动态语言
。
反射机制概念(运行状态中知道类所有的属性和方法)
反射(Reflection)机制
允许程序在运行时借助Reflection API
取得任何类
的内部信息
,并不能直接操作对象的内部属性及方法。反射被视为动态语言的关键
。
白话讲解
:在 Java 中的反射机制
是指在运行状态
中,对于任意一个类
都能够知道这个类所有的属性和方法
;并且对于任意一个对象
,都能够调用它的任意一个方法
;这种动态获取信息以及动态调用对象方法的功能成为Java 语言的反射机制
。
JAVA 反射 API
如果面试被问到,对于反射API了你了解多少?怎么回答。
Reflection API
提供了Constructor
,Field
和Method
类,这三个类定义在java.lang.reflect
包中,分别用于描述类的构造方法
,属性
和方法
。JAVA8
之后在在java.lang.reflect
包中添加了Executable
抽象类,parameter
l类这两个API,Executable
描述类的执行对象
,parameter
描述方法参数信息。
当然,获取这些反射APl的实例信息,还需要一个最重要对象元类Class
,java.lang.Class
类封装一个对象和接口运行时的状态。当类加载
时Class
类型的对象自动创建
,Class
没有公共构造方法
,其对象是JVM
在类加载时通过类加载器中的defineClass()
方法自动构造的,不能显示
的实例化一个class对象
。
如果面试被问到你刚才说了类加载
,你可以讲讲类加载么?
所谓类加载
,就是指将类
的class文件
读入内存
,并为之创建一个Java.lang.class对象
。即当线程
使用任何一个类时,系统都会为之创建一个java.lang.Class
对象。java
可以使用使用forName动态加载类文件
,动态加载(Dynamic Loading)
是指在程序运行时
加载需要的类库文件
,对Java程序来说,一般情况下,一个类文件
在启动
时或首次初始化
时会被加载到内存中
,而反射则可以在运行时决定
是否要加载一个类
,一个类文件只有在被加载到内存中才可能生成实例对象,即加载到内存中,生成Class
对象,通过new
关键字生成实例对象
。
每个类被加载
之后,会生成一个Class对象
,通过Class对象
可以访问JVM
中该类的信息,一旦类被载入JVM中
,同一个类
将不会被再次载入
,被载入的类都有一个唯一的标识
,是该类
得到全限定类名(包括包名和类名)
。
Class
Class常用方法:
如果面试被问到你对Class对象
了解多少,说几个常用方法,怎么回答?
Class常用方法 | 描述 |
---|---|
static Class forName(String className) | 返回指定类名 的Class 对象 |
T newInstance() | 调用默认的构造方法,返回该Class对象的一个实例 |
String getName() | 返回Class 对象锁对应的类名 |
构造器相关方法 | 描述 |
---|---|
Constructor<?>[] getConstructors() | 返回Class 对象所对应类的所有public构造方法 |
Constructor<T>getConstructors(Class<?>...parameterType) | 返回Class对象所对应的类的指定参数列表的public构造方法 |
Constructor<?>[]getDeclaredConstructors() | 返回Class 对象所对应的所有构造方法 ,与访问权限无关 |
Constructor<T>getDeclaredConstructors(Class<?>...parameterTypes) | 返回Class对象 所对应类的指定参数列表 的所有构造方法 ,与 访问权限无关 ,如果使用私有构造器构造,需要开启访问权限 setAccessible(true) :设置通过反射访问该成员变量时取消访问权限检查。 |
方法相关方法 | 描述 |
---|---|
Method[] getMethod() | 返回Class 对象所对应类的所有public方法 |
Method getMethod(String name,Class<?>...parameterType) | 返回Class 对象所对应的指定参数列表 的public方法 |
Method[] getDeclaredMechods() | 返回Class 对象所对应的所有方法 ,与访问权限无关 |
Method getDeclaredMethod(String name,Class<?>...parameterTypes) | 返回Class 对象对应类的指定参数列表 的方法,与访问权限无关 |
属性相关方法 | 描述 |
---|---|
Field[] getFields() | 返回Class 对象所对应类的所有public成员变量 |
Field getField(String name) | 返回Class 对象所对应的类的指定参数 的public成员变量 |
Field[] getDeclaredFields( ) | 返回Class 对象所对应类的所有成员变量 ,与访问权限无关 |
Field getDeclaredField(String name) | 返回Class 对象所对应类指定参数 的成员变量 ,与访问权限无关 |
这里需要注意的是(Method
、Constructor
的也一样):
- 调用
getDeclaredFields()
方法可以获取包括私有
和受保护
的所有属性
,但不包括
父类的属性
; - 调用
getField()
方法可以获得所有的public属性
。包括从父类继承
的。
注解相关方法 | 描述 |
---|---|
Annotation [] getAnnotation() | 返回Class对象 所对应类上存在的所有注解 |
< A extends Annotation>A getAnnotation(Class < A >annotationClass ) | 返回Class对象 所对应类上存在的指定类型的注解 |
Class自身信息相关方法 | 描述 |
---|---|
Class<?>getDeclaringClasses() | 返回Class 对象所对应的外部类 |
Class<?>[] getDeclaredClasses() | 返回Class 对象所对应的类里包含的所有内部类 |
Class<? super T>getSuperclass() | 返回Class 对象所对应的类里的父类的Class对象 |
int getModifiers() | 返回Class 对象所对应类的修饰符 ,返回的整数是修饰符的对应常量,需要是使用Modified工具类解码 |
Class [] getInterfaces() | 返回Class 对象所对应类实现的所用接口 |
Class LoadergetClassLoader() | 返回该类的类加载器 |
包相关方法 | 描述 |
---|---|
Package getPackage() | 返回Class对象 所对应的包 |
Class谓词相关方法 | 描述 |
---|---|
boolean isArray() | 判断Class 对象是否表示一个数组类 |
boolean isEnum() | 判断Class 对象是否表示一个枚举 |
boolean isInterface() | 判断Class 对象是否表示一个接口 |
boolean isInstance(Object obj) | 判断obj对象 是否是该Class对象 的一个实例 |
boolean isAnnottation() | 返回Class 对象是否标识一个注解类型 |
获取Class对象
的四种方式:
如果面试被问到如何获取Class对象,怎么回答?
- 使用
Class类
的forName(String classNmae)
静态方法,参数class
代表所需要类的全限定类名
。forName()
方法声明抛出ClassNotFoundException
受检异常,调用必须捕获或抛出异常
。
Class<?> reflection_demoClass = Class.forName("com.liruilong.Reflection.Reflection_Demo");
- 调用某个类的
class属性
来获取该类对应的Class对象
,对象.class
;类的Class属性
获得该类所对应的Class对象
,会始代码更安全
。程序性更好
。string
类型的字符串不能使用String.class
方式。需要使用Class.forName(“java.lang.String”)
,Object
类的.class
文件默认是不包含参数信息的。
Class<Reflection_Demo> reflection_demoClass = Reflection_Demo.class;
- 调用某个类的
getclass()
方法来获取该类对应的class对象
,该方法是Object类中的一个方法
Class<? extends Reflection_Demo> aClass = new Reflection_Demo().getClass();
- 调用元类Class对应的
getClassLoader()
获取类加载器,ClassLoader
,这个有点牵强,姑且算一种吧,嘻嘻。。
ClassLoader classLoader = Reflection_Demo.class.getClassLoader();
Class<?> bClass = classLoader.loadClass("com.liruilong.Reflection.Reflection_Demo");
下面我们看看剩下的API吧!
Executable
Executable抽象类
:JAVA8
在java.lang.reflect
包下新增了一个Executable
抽象类,代表可执行的类成员。Executable
抽象类派生了Constructor
和Method
两个子类。Executable
抽象类提供了大量方法来获取参数
,修饰符
或注解
等信息。
方法 | 描述 |
---|---|
parameter [] getparameters() | 获取所有形参 ,返回一个parameter [] 数组 |
int getParameterCount() | 获取形参个数 |
abstract int getModifiers() | 获取修饰符 ,返回的整数是修饰符关键字对应的常量 |
boolean isVarArgs() | 判断是否包含数量可变的形参 |
Constructor
Constructor类
:用于表示类的构造方法
。通过Class
的getConstructor()
方法来获取构造方法
的集合
。
方法 | 描述 |
---|---|
String getName() | 返回构造器的名称 |
Class [] getParameterTypes() | 返回当前构造方法的参数类型 |
int getModifiers() | 返回修饰符 的整型标识 ,返回的整数是修饰符是标识常量,需要使用Modified工具类方法解码Modified.toSting(int mod),可以通过Modified.PUBLIC 查看对应的值 |
Method
Method类
:用于封装方法的信息,调用Class
对象的getMethods()
方法或getMethod()
可以获取当前类的所有方法或指定的方法。
常用方法 | 功能描述 |
---|---|
String getName() | 返回方法的名称 |
Class[] getparameterType() | 返回方法的参数类型 |
int getModifieds() | 返回修饰符 的整型标识 |
Class getReturnType() | 返回当前方法的返回类型 |
Field
Field类
:用于封装属性信息,调用Class
对象的getFields()
或getField()
方法可以获取当前类的所有属性
或指定属性
。
常用方法 | 描述 |
---|---|
String getName() | 获取属性的名称 |
int getMOdifiers() | 返回修饰符的整型标识 |
getXxx(Object obj) | 获取属性的值,此处的Xxx对应的java8中的基本类型,如果属性是引用类型,直接使用get(Object obj)方法 |
setXxx(Object obj,Xxx val) | 设置属性的值,此处的Xxx对应Java8中的基本类型,如果属性是引用类型,直接使用set(Object obj,Object val)方法 |
Class [] getType() | 返回当前属性的类型 |
parameter
parameter类
:是JAVA8中新增的API,每个paramtete 对象代表一个参数。Parameter类提供许多方法来获取参数信息
方法 | 功能 |
---|---|
int getModifiers() | 获取参数的修饰符 |
String getName() | 获取参数的形参名 |
Type getparameterizedType() | 获取带泛型 的形参类型 |
Class<?>getType() | 获取形参类型 |
boolean isVarArgs() | 判断该参数是否为可变参数 |
boolean isNamePreaent() | 判断.class 文件中是否包含方法的形参名信息 |
利用反射创建对象
的的两种方式:
如果面试被问到使用反射如何创建对象,怎么回答?
Class 对象的 newInstance()
: 使用Class
对象的newInstance()
方法来创建该Class
对象对应类的实例,但是这种方法要求该Class
对象对应的类有默认
的空构造器
。
Class<?> reflection_demoClass = Class.forName("com.liruilong.Reflection.Reflection_Demo");
Reflection_Demo o = (Reflection_Demo) reflection_demoClass.newInstance();
System.out.println(o);// com.liruilong.Reflection.Reflection_Demo@677327b6
- 调用
Constructor
对象的newInstance()
: 先使用Class
对象获取指定的Constructor
对象,再调用Constructor
对象的newInstance()
方法来创建Class
对象对应类的实例,通过这种方法可以选定构造方法创建实例
。
Class<?> reflection_demoClass = Class.forName("com.liruilong.Reflection.Reflection_Demo");
Constructor<?> constructors = reflection_demoClass.getConstructor(null);
Reflection_Demo o1 = (Reflection_Demo) constructors.newInstance(null);
System.out.println(o1); // com.liruilong.Reflection.Reflection_Demo@14ae5a5
关于反射的一些其他编码经验:
如果面试问关于反射,平常开发中有哪些经验,要怎么回答?
- 注意
Class类本身
的特殊性:Java语言把Java源文件编译
为后缀为class
的字节码文件
,然后通过ClassLocale
机制把类文件加载
到内存
中,最后生成实例执行
,Java使用元类(MetaClass)
来描述加载
到内存中
的类数据
,即Class类
,描述类
的类对象
,需要注意Class的一些特性
。-
无构造函数
,不能主动实例化,Class对象在加载时由java虚拟机通过类加载器中的defineClass自动构造。 -
可以描述基本类型 Class as=int.class;
8个基本类型执行JVM
中并不是一个对象
,一般存在
于栈中
,通过Class
可以描述
它们,可以使用int.calss
描述int类型
的类对象
。 -
Class对象都是单例模式
,一个Class对象描述一个类
,只描述一个类,即一个类只有一个Class对象
。Class是java 的反射入口
,只有在获得一个类的动态描述
时才能动态的加载调用
。
-
- 适时选择
getDeclaredXXX
和getXXX
:getDeclaredMethod
方法获得的是所有public访问级别
的方法,包括从父类继承来
的方法,而getDeclareMethod
获得自身类的所有方法
,包括公有的(public)
,私有(private)
,方法等,不受访问权限限制
。如果需要列出
所有继承自父类的方法
,可以先获得父类
,然后使用getDeclareMethods
,之后持续递归
。 - 反射访问
属性
或方法
时将Accessible
设置为true
,java
中通过反射执行方法
的步骤,获取一个对象的方法
,然后根据isAccessible
返回值确定是否能执行
,如果返回false
,则需要调用setAccessible(true)
,在调用invoke
执行方法。-
Access
并不是语法层次
理解的访问权限
,而是指是否更容易获得
,是否进行安全检查
。动态的修改
一个类或方法或执行方法时都会受到Java安全体系的制约
,而安全处理
非常消耗资源
,所以对于运行期
要执行的方法或修改的属性就提供了Accessible可选项
,由开发者决定是否要逃避安全体系的检查
。 -
AccessibleObject
是field
,Method
,constructor
的父类
,决定
其是否
可以快速访问
而不进行访问控制检查
,AccessobleObject
类中是以override
变量保存该值的。
-
-
Accessible
属性只是用来判断是否需要进行安全检查的,如果不需要则直接执行,可以提升系统性能
.AccessibleObject
的其他两个子类field
和constructor
也相似,所以要设置Accessible
为true
。
Method method= genericDemo.class.getMethod("toArray");
if(!method.isAccessible())
method.setAccessible(true);
method.invoke(obj, args);
public class Foo {
public final void doStuff() {
System.out.println("Do liruilong ___$# ^_^");
}
public static void main(String[] args) throws Exception, Throwable {
Method method = Foo.class.getMethod("doStuff");
System.out.println("可以访问吗!!"+method.isAccessible());
method.invoke(new Foo());
}
- 动态加载不适合数组,当使用forName加载一个类时,8个基本类型排除,它不是一个具体的类,还要具有可追索的类路径,否则包ClassNotFoundException异常。数组虽然是一个类,但没有定义类路径,可以加载编译后的对象动态动态加载一个对象数组,但是没有意义。在java中数组是定长的,没有长度的数组是不允许存在的。可以使用Array数组反射类来动态加载一个数组。
//动态创建一个数组
String [] strs = (String[]) Array.newInstance(String.class,8);
int[][] ints = (int [][])Array.newInstance(int.class,2,3);
元素类型 | 编译后的类型 |
---|---|
byte[] | [B |
char[] | [C |
Double[] | [D |
Float[] | [F |
Int[] | [I |
Long[] | [J |
Short[] | [S |
Boolean | [Z |
引用类型(如String) | [L引用类型 |
- 动态可以让代理模式更灵活,java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标对象生成代理,静态代理:通过代理主题角色和具体主题角色共同实现抽象主题角色的逻辑的,只是代理主题角色把相关的执行逻辑委托给了具体主题角色而已。
interface subject{
public void request();
}
class RealSubject implements subject{
public void request(){
}
}
class Proxy implements subject{
private subject subjects = null;
public Proxy(){
subjects = new RealSubject();
}
public Proxy(subject subjects){
this.subjects =subjects;
}
public void request(){
befoer();
subjects.request();
afert();
}
public void befoer(){}
public void afert(){}
}
java基于java.lang.reflect.Proxy用于实现动态代理,使SubjectHandler作为主要的逻辑委托对象,invoke是必须要实现的,完成对真实方法的调用。即通过InvocationHandler接口的实现类来实现,所有被代理的方法都是由InvocationHandler接管实际的处理任务。
interface subject{
public void request();
}
class RealSubject implements subject{
public void request(){
}
}
class SubjectHandler implements InvocationHandler{
private subject subjects;
private SubjectHandler(subject subjects) {
this.subjects = subjects;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("预处理");
Object obj = method.invoke(subjects,args);
System.out.println("后处理");
return obj;
}
}
//场景类
public static void main(String[] args) {
subject subjects = new RealSubject();
InvocationHandler handler = new SubjectHandler(subjects);
//当前加载器
ClassLoader cl = subjects.getClass().getClassLoader();
//动态代理
subject proxy = (subject) Proxy.newProxyInstance(cl,subjects.getClass().getInterfaces().request(),handler);
//执行具体的角色方法
proxy.request();
}
- 使用反射增加装饰模式的普遍性,装饰模式:动态的给一个对象添加一些额外的职责。使用动态代理可以实现装饰模式。
//动物
interface Animal{
public void doStuff();
}
//老鼠
class Rat implements Animal{
@Override
public void doStuff(){
System.out.println("Jetty Tom");
}
}
//定义某种能力
interface Featuer{
public void load();
}
class FlyFeatuer implements Featuer{
public void load(){
System.out.println("增加 一支翅膀");
}
}
class DigFeatuer implements Featuer{
public void load(){
System.out.println("增加钻地能力!");
}
}
class DecorateAnimal implements Animal{
private Animal animal;
private Class<? extends Featuer> clz;
public DecorateAnimal(Animal animal,Class<? extends Featuer> clz){
this.animal = animal;
this.clz = clz;
}
@Override
public void doStuff(){
InvocationHandler handler = new InvocationHandler() {
//具体的包装行为
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj = null;
//设置包装
// public class Modifier
// extends ObjectModifier类提供了static方法和常量来解码类和成员访问修饰符。 修饰符集合被表示为具有表示不同修饰符的不同位位置的整数。
//method.getModifiers()返回由该对象表示的可执行文件的Java语言modifiers 。
if (Modifier.isPublic(method.getModifiers())){
obj = method.invoke(clz.newInstance(),args);
}
animal.doStuff();
return obj;
}
};
ClassLoader cl = getClass().getClassLoader();
Featuer Proxy = (Featuer) java.lang.reflect.Proxy.newProxyInstance(cl,clz.getInterfaces(),handler);
Proxy.load();
}
}
public class Demo {
public static void main(String[] args) {
//定义Jerry老树
Animal Jerry = new Rat();
Jerry = new DecorateAnimal(Jerry,FlyFeatuer.class);
Jerry = new DecorateAnimal(Jerry,DigFeatuer.class);
Jerry.doStuff();
}
}
- 反射让模板方法模式更强大,模板方法模式:定义一个操作中的算法骨架,将一些步骤延迟到子类中,使子类不改变一个算法的结构即可定义该算法的某些特定的步骤,即父类定义抽象模板为骨架,其中包括基本方法(由子类实现的方法,并且在模板方法被调用)和模板方法(实现对基本方法的调用,完成固定的逻辑)
public abstract class AbsPopulator{
public final void dataInitialing() throws Exception{
doInit();
}
protected abstract void doInit();
}
public class UserPopulator extends AbsPopulator{
protected void doInit(){}
/*初始化用户表,如创建,加载数据等*/
}
//基于反射增强的模板方法
以上是关于关于JAVA 反射 基础知识/编码经验的一些总结的主要内容,如果未能解决你的问题,请参考以下文章