注解与反射
注解与反射
CONTENTS
反射 Java.Reflection
参考:https://www.bilibili.com/video/BV1p4411P7V3(进度:p7 end)
目录
- Java反射机制概述
- 理解Class类并获取Class实例
- 类的加载与ClassLoader
- 创建运行时类的对象
- 获取运行时类的完整结构
- 调用运行时类的指定结构
其他
可以获取注解(与注解配合)
JVM(Java虚拟机)
Hook
静态 VS 动态语言
- 动态语言
- 是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。 通俗点说就是在运行时代码可以根据某些条件改变自身结构。
- 主要动态语言:Object-C、C#、JavaScript、PHP、Python等。
- 静态语言
- 与动态语言相对应的,运行时结构不可变的语言就是
- 主要静态语言:Java、C、C++等
- Java不是动态语言,但Java可以称之为 “准动态语言” 。 即Java有一定的动态性,我们可以==利用反射机制获得类似动态语言的特性==。Java的动态性让编程的时候更加灵活!
Java反射
介绍
大概原理
Reflection (反射) 是Java被视为动态语言的关键,反射机制允许程序在执行期借助于
Reflection API
取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。Class c = Class.forName("java.lang.String")
加载完类之后,在堆内存的方法区中就产生了一个
Class类型的对象
(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。 我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射- 正常方式:引入需要的"包类”名称 ==> 通过new实例化 ==> 取得实例化对象
- 反射方式:实例化对象 ==> getClass()方法 ==> 得到完整的“包类”名称
功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
- ......
优点
- 可以实现动态创建对象和编译,体现出很大的灵活性
缺点
- 对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作。
反射主要API
java.lang.Class // 代表一个类。`Class` 是 Reflection API 中的一个核心类 java.lang.reflect.Method // 代表类的方法 java.lang.reflect.Field // 代表类的成员变量 java.lang.reflect.Constructor // 代表类的构造器 ......
获得反射对象
// 什么叫反射
public class Test02 extends Object{
public static void main(String[] args) throws ClassNotFoundException{
// 通过反射获取类的Class对象
// 一个类在内存中只有一个Class对象
// 一个类被加载后,类的整个结构都会被封装在Class对象中
Class c1 = Class.forName("com.kuang.reflection,User");
System.out.println(c1);
}
}
// 实体类: pojo, entity
class User{
private String name;
private int id;
private int age;
public User(){}
public User(String name, int id, int age){
this.name = name;
this.id = id;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public int setId() {
this.id = id;
}
public int getAge(){
return age;
}
public void setAge(int age){
this.age = age;
}
}
得到Class类
的几种方式
对象"照镜子"后可以得到的信息:
- 某个类的属性
- 方法和构造器
- 某个类到底实现了哪些接口
对于每个类而言,JRE都为其保留一个不变的Class类型的对象。 一个Class对象包含了特定某个结构(classlinterfacelenum/annotation/primitive type/void/[])的有关信息。
- Class本身也是一个类
- Class对象只能由系统建立对象
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个Class 实例所生成
- 通过Class可以完整地得到一个类中的所有被加载的结构
- Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象
Class类的常用方法
方法名 | 功能说明 |
---|---|
static ClassforName(String name) | 返回指定类名name的Class对象 |
Object newlnstance() | 调用缺省构造函数,返回Class对象的一个实例 |
getName() | 返回此Class对象所表示的实体(类,接口,数组类或void)的名称 |
Class getSuperClass() | 返回当前Class对象的父类的Class对象 |
Class[] getinterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Constructor[]getConstructors() | 返回一个包含某些Constructor对象的数组 |
Method getMothed(String name,Class.. T) | 返回一个Method对象,此对象的形参类型为paramType |
Field[] getDeclaredFields() | 返回Field对象的一个数组 |
| 方法名 | 功能说明 |
| --------------------------------------- | --------------------------------------------------------- |
| static ClassforName(String name) | 返回指定类名name的Class对象 |
| Object newlnstance() | 调用缺省构造函数,返回Class对象的一个实例 |
| getName() | 返回此Class对象所表示的实体(类,接口,数组类或void)的名称 |
| Class getSuperClass() | 返回当前Class对象的父类的Class对象 |
| Class[] getinterfaces() | 获取当前Class对象的接口 |
| ClassLoader getClassLoader() | 返回该类的类加载器 |
| Constructor[]getConstructors() | 返回一个包含某些Constructor对象的数组 |
| Method getMothed(String name,Class.. T) | 返回一个Method对象,此对象的形参类型为paramType |
| Field[] getDeclaredFields() | 返回Field对象的一个数组 |
获取Class类的实例
// 1. 若已知具体的类,通过类的class属性获取。该方法最为安全可靠,程序性能最高。
Class clazz= Person.class;
// 2. 已知某个类的实例,调用该实例的getClass()方法获取Class对象
Class clazz= person.getClass();
// 3. 已知一个类的全类名,且该类在类路径下。可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
Class clazz= Class.forName("demo01.Student");
// 4. 内置基本数据类型 (不是每个类都有) 可以直接用类名.Type
// 5. 还可以利用ClassLoader我们之后讲解
举例
// 测试class类的创建方式有哪些
public class Test03 {
public static void main(String[] args) throws classNotFoundException {
Person person = new Student();
system.out.println("这个人是: "+person.name) ;
// 方式一∶通过对象获得
Class c1 = person.getclass( );
system.out.println(c1.hashcode());
// 方式二: forName获得
Class c2 = Class.forName("com. kuang.reflection.student");
system.out.println(c2.hashcode());
// 方式三: 通过类名.class获得
Class c3 = Student.class;
system.out.println(c3.hashcode());
// 方式四: 基本内置类型的包装类都有一个Type属性
Class c4 = Integer.TYPE;
system.out.println(c4); // int
// 获得父类类型
Class c5 = c1.getSuperclass();
system.out.println(c5); // class com.kuang.reflection.Person
}
}
// 类
class Person{...}
class student extends Person{
public student(){
this.name ="学生";
}
}
class Teacher extends Person{
public Teacher(){
this.name ="老师";
}
}
反射
参考:《Java 核心技术》
API: java.lang.Class 1.0
API: java.lang.reflect.Constructor 1.1
基本介绍
- 反射库
- 反射库(reflection library)提供了一个非常丰富且精心设计的工具集
- 应用
- 这项功能被大量地应用于JavaBeans中
- 使用反射,Java可以支持Visual Basic用户习惯使用的工具
- 特别是在设计或运行中添加新类时,能够快速地应用开发工具动态地查询新添加类的能力
- 视觉上加速程序加载速度 在启动时,包含main方法的类被加载。它会加载所有需要的类。这些被加载的类又要加载它们需要的类,以此类推。 对于一个大型的应用程序来说,这将会消耗很多时间,用户会因此感到不耐烦。可以使用下面这个技巧给用户一种启动速度比较快的幻觉。 不过,要确保包含main方法的类没有显式地引用其他的类。 首先,显示一个启动画面;然后,通过调用Class.forName手工地加载其他的类
- 反射机制
- 能够分析类能力的程序称为反射(reflective)
- (没懂)使用它的主要人员是工具构造者,而不是应用程序员。如果仅对设计应用程序感兴趣,而对构造工具不感兴趣,可以跳过
与C++不同
反射机制
- Java和C++一些相似的方法
- java的
newInstance方法
对应C++中虚拟构造器
的习惯用法。但有所不同- C++中的虚拟构造器不是一种语言特性,需要由专门的库支持
- java的
Class类
与C++中的type_info类
相似,java的getClass方法
与C++中的typeid
运算符等价- C++的type_info没Java的Class强大。只能以字符串的形式显示一个类型的名字,而不能创建那个类型的对象
- java的
- C++本身没有自带的反射机制。至于虚拟构造器和type_info我也没用过、不懂
Class类
在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。
然而,可以通过专门的Java类访问这些信息。保存这些信息的类被称为Class,这个名字很容易让人混淆。
本质是泛型类
补充:如同Enum类一样,Class类实际上是一个泛型类。
例如,Employee.class的类型是 Class<Employee>
没有说明这个问题的原因是:它将已经抽象的概念更加复杂化了。在大多数实际问题中,可以忽略类型参数,而使用原始的Class类。
获取Class类
getClass()
根据实例 Object类中的 getClass() 方法将会返回一个Class类型的实例
Empolyee e; ... Class cl = e.getClass(); // 类似于Python中的 e.__class__
forName() (无论何时使用这个方法,都应该提供一个异常处理器)
根据包名字符串 可以调用静态方法forName获得类名对应的Class对象。如果类名保存在字符串中,并可在运行中改变,就可以使用这个方法
String className = "java.util.Random"; Class cl = Class.forName(className); // 报错则抛出一个checked exception(已检查异常) // 无论何时使用这个方法,都应该提供一个异常处理器(exception handler)
T.class
根据类型 如果T是任意的Java类型(或void关键字),T.class将代表匹配的类对象
Class cl1 = Random.class; Class cl2 = int.class; Class cl3 = Double[].class;
Class的基本方法
getName; 返回类名
最常用的Class方法是getName。这个方法将返回类的名字
System.out.println(e.getClass().getName); // 如果类在一个包里,包的名字也作为类名的一部分
// 类似于Python中的 e.__class__.__name__
注意:不同于getName()方法???
getName;
:返回类的名字getName()
:返回项目的名称
newInstance() 创建实例
可以用来动态地创建一个类的实例
e.getClass().newInstance(); // 创建了一个与e具有相同类类型的实例
// 如果这个类没有默认的构造器,就会抛出一个异常
// 将forName与newInstance配合起来使用
String s = "java.util.Random";
Object m = Class.forName(s).newInstance();
// 但如果需要以这种方式向希望按名称创建的类的构造器提供参数,就不要使用上面那条语句,而必须使用Constructor类中的newInstance方法
Class的比较
虚拟机为每个类型管理一个Class对象。因此,可以利用==运算符实现两个类对象比较的操作。例如
if (e.getClass() == Employee.class) {}
利用反射分析类
API:java.lang.Class 1.0
API:java.lang.reflect.Field 1.1
API:java.lang.reflect.Method 1.1
API:java.lang.reflect.Constructor 1.1
API:java.lang.reflect.Modifier 1.1
Field、Method、Constructor
反射机制最重要的内容——检查类的结构
java.lang.reflect包中有三个类Field、Method和Constructor分别用于描述类的域、方法和构造器
Class类中的 getFields、getMethods、getConstructors
方法将分别返回类提供的public域、方法和构造器数组
他们的方法
getName()
这三个类都有一个叫做 getName 的方法,用来返回项目的名称
getType()
Field类有一个 getType 方法,用来返回描述域所属类型的Class对象
Method和Constructor类有能够报告参数类型的方法,Method类还有一个可以报告返回类型的方法。
getModifiers()
这三个类还有一个叫做 getModifiers 的方法,它将返回一个整型数值,用不同的位开关描述public和static这样的修饰符使用状况
还可以利用java.lang.reflect包中的Modifier类的静态方法分析getModifiers返回的整型数值:如下
isPublic()、isPrivate()、isFinal()
- 可以使用Modifier类中的isPublic、isPrivate或isFinal判断方法或构造器是否是public、private或final。
toString()
- 可以利用Modifier.toString方法将修饰符打印出来
其他:getDeclaredFiled() / getDeclatedMethods()
可以获得一个类的某个字段或方法
一个demo示例
功能:提醒用户输入类名,然后输出类中所有的方法和构造器的签名
程序 reflection/ReflectionTest.java
其实这个程序应该用模板字符串来写的,字符串分散来写太不直观了
package reflection;
import java.util.*;
import java.lang.reflect.*;
/**
* 这个程序使用反射去打印一个类的所有特征
* @version 1.1 2004-02-21
* @author Cay Horstmann
*/
public class ReflectionTest
{
public static void main(String[] args)
{
// 从用户的终端输入中读取类名
String name;
if (argslength>0) name = args[0];
else
{
Scanner in = new Scanner(System.in);
Syste,.out.println("Enter class name (e.g. java.util.Date):");
name = int.next();
}
try
{
// 获取类信息(Class, 父类的Class, 修饰符)
Class cl = Class.forName(name);
Class supercl = cl.getSuperclass();
String modifiers = Modifier.toString(cl.getModifiers());
// 打印类名
if(modifiers.length()>0) System.out.print(modifiers+"");
System.out.print("class "+name);
// 打印父类名称(如果不为Object类的话)
if(supercl != null && supercl != Object.class) System.out.print(" extends "
+ supercl.getName());
System.out.print("\n{n\n");
printConstructors(cl);
System.out.println();
PrintMethods(cl);
System.out.println();
printFields(cl);
System.out.println("}");
}
// 找不到类的异常
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
System.exit(0);
}
/**
* 打印类的所有结构
* @param cl a class
*/
public static void printConstructors(Class cl)
{
Constructor[] constructors = cl.getDeclaredConstructors();
for (Constructor c: constructors)
{
String na,e = c.getName();
System.out.print(" ");
String modifiers = Modifier.toString(c.getModifiers());
if(modifiers.length()>0) System.out.print(modifiers + " ");
Syste,.out.print(name + "(");
// 打印参数类型
Class[] paramTypes = c.getParameterTypes();
for (int j=0; j<paramTypes.length; j++){
if(j>0) System.out.print(", ");
System.out.print(para,Types[j].getName());
}
System.out.print(");");
}
}
/**
* 打印类的所有的方法
*/
public static void printMethods(Class cl)
{
Method[] methods = cl.getDeclatedMethods();
for (Method m : methods)
{
Class retType = m.getReturnType();
String name = m.getName();
System.out.print(" ");
// 打印修改器,返回类型和方法名
String modifiers = Modifier.toString(m.getModifiers());
if (modifiers.length() > 0) System.out.print(modifiers + " ");
System.out.print(retType.getName()+" "+name+"(");
// 打印参数类型
Class[] paramTypes = m.getParameterTypes();
for(int j=0; jMparamTypes.length;j++)
{
if(j>0) System.out.print(", ");
System.out.println(");");
}
System.out.println(");");
}
}
/**
* 打印一个类中所有的字段
* @param cl a class
*/
public static void printFields(Class cl)
{
Field[] fields = cl.getDeclaredFields();
for (Field f:fields)
{
Class type = f.getType();
String name = f.getName();
System.out.print(" ");
String modifiers = Modifier.toString(f.getModifiers());
System.out.println(type.getName() + " " + name + ";");
}
}
}
getDeclatedMethods
输入输出
// 输入:
java.lang.Double
// 输出:
public class java.lang.Double extends java.lang.Number
{
public java.lang.Double(java.lang.String);
public java.lang.Double(double);
public int hashCode();
public int compareTo(java.lang.Object);
...... // 其他方法
public static final double POSITIVE_INFINITY;
public static final double NEGATIVE_INFINITY;
...... // 其他字段
}
利用反射分析对象(运行时)
API:java.lang.reflect.AccessibleObject 1.2
API:java.lang.Class 1.1
API:java.lang.reflect.Field 1.1
作用:
在编写程序时,如果知道想要查看的域名和类型,查看指定的域是一件很容易的事情。 而利用反射机制可以查看在编译时还不清楚的对象域。
方法
get()
查看对象域的关键方法是Field类中的get方法
f.get(obj)
将返回一个对象,其值为obj域的当前值
Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989);
Class cl = harry.getClass();
// 获取Class类:Employee_Class
Field f = cl.getDeclaredField("name");
// 获取Employee类的Name字段
Object v = f.get(harry);
// harry对象的name字段的值
setAccessible()
AccessibleObject类中的一个方法
/* 【访问权限问题】
* 实际上,上面这段代码存在一个问题。由于name是一个私有域,所以get方法将会抛出一个IllegalAccessException
* 除非拥有访问权限,否则Java安全机制只允许查看任意对象有哪些域,而不允许读取它们的值。
* 反射机制的默认行为受限于Java的访问控制
*/
/* 【解决方案】
* 然而,如果一个Java程序没有受到安全管理器的控制,就可以覆盖访问控制。
* 为了达到这个目的,需要调用Field、Method或Constructor对象的setAccessible方法。
* setAccessible方法是AccessibleObject类中的一个方法,它是Field、Method和Constructor类的公共超类。
* 例如:
*/
f.setAccessible(true);
// 设置为可访问权限,现在可以去调用 f.get(harry)了
getDouble()
get方法还有一个需要解决的问题。name域是一个String,因此把它作为Object返回没有什么问题。但是,假定我们想要查看salary域。它属于double类型,而Java中数值类型不是对象。要想解决这个问题,可以使用Field类中的getDouble方法,也可以调用get方法,此时,反射机制将会自动地将这个域值打包到相应的对象包装器中,这里将打包成Double。
set()
当然,可以获得就可以设置。调用f.set(obj,value)可以将obj对象的f域设置成新值。
一个demo示例
使用toString方法可以查看任意对象的内部信息,有点类似于Python的__str__
魔术方法
ArrayLIst<Integer> squares = new ArrayList<>>();
for (int i=1; i<=5; i++) squares.add(i*i);
System.out.println(new ObjectAnalyzer().toString(squares));
/* print:
java.util.ArrayList[
elementData=class java.lang.Object[]{
java.lang.Interger[value=1][][],
java.lang.Inter[value=4][][],
java.lang.Interger[value=9][][],
java.lang.Interger[value=16][][],
java.lang.Interger[value=35][][],
null,
null,
null,
null
},size=5
][modCount=5][][]
*/
使用通用的toString方法实现自己类中的toString方法
public String toString()
{
return new ObjectAnalyzer().toString(this);
}
// 这是一种公认的提供toString方法的手段
编写一个可供任意类使用的通用toString方法
@略,详见书
使用反射编写泛型数组代码
java.lang.reflect包中的Array类允许动态地创建数组。
例如,将这个特性应用到Array类中的copyOf方法实现中
Employee[] a = new Employee[100];
...
// array is full
a = Arrays.copyOf(a, 2*a.length);
如何编写这样一个通用的方法呢?
@略
调用任意方法
API:java.lang.reflect.Method 1.1
与C++不同
- C和C++
- 可以从函数指针执行任意函数。
- Java
- 从表面上看,Java没有提供方法指针,即将一个方法的存储地址传给另外一个方法,以便第二个方法能够随后调用它。
- 代替方案
- 事实上,Java的设计者曾说过:方法指针是很危险的,并且常常会带来隐患。他们认为Java提供的**接口(interface)**是一种更好的解决方案。
- 然而,反射机制允许你调用任意方法。
- J++/C#
- 微软公司为自己的非标准Java语言J++(以及后来的C#)增加了另一种被称为**委托(delegate)**的方法指针类型, 它与Java中的Method类不同。Java中内部类比委托更加有用。
getMethod()
Object invoke(Object obj, Object... args);
double s = (Double) m2.invoke(harry);
Method getMethod(String name, Class... parameterTypes);
Method m1 = Employee.class.getMethod("getName");
Method m2 = Employee.class.getMethod("raiseSalary", double.class);
注意:
可以使用method对象实现C(或C#中的委派)语言中函数指针的所有操作。同C一样,这种程序设计风格并不太简便,出错的可能性也比较大。如果在调用方法的时候提供了一个错误的参数,那么invoke方法将会抛出一个异常。
@略,详见书