Java核心卷vs java疯狂讲义_Java> Java核心卷读书笔记 - 反射

反射简介

能够分析类能力的程序成为反射(reflective)。

反射可以用来干什么?

反射经常用于构建库或工具,主要包含以下能力:

运行时分析类的能力;

运行时查看对象,如编写一个toString查看对象属性;

实现通用的数组操作代码;

利用Method对象,类似于C/C++函数指针;

Class类

主要作用:维护Java对象类型信息 -- 程序运行时,Java运行时系统始终为所有对象维护一个被成为运行时的类型标识。该信息跟踪者每个对象所属的类。虚拟机利用运行时类型信息选择相应方法执行。

保存这些维护类型信息的类称为Class。

获取Class类对象

如何获取Class类对象?

有3种方法:

对象的getClass()方法;

Class静态方法Class.forName();

类名.class;

示例:

// 方法1: 一个类的对象 -> Class类对象

Employee e;

Class c = e.getClass(); // 通过getClass(), 获取对象e的类型信息

// 方法2: 类名称: -> Class 类对象

String className = "java.util.Random"; // 类名(需要包含包的路径)

Class c2 = Class.forName(className) ; // 通过Clas静态方法forName(),

// 方法3: 类名.class -> Class类对象

Class random = Random.class;

Class cint = int.class;

Class cdoubles = Double[].class;

Class类包含哪些类的信息?

类名;

创建类的实例;

比较类型信息;

读取类名

Class的getName方法可以读取类名,包也作为类名的一部分

System.out.println(e.getClass.getName() + " " + e.getName());

创建类的实例

Class对象的newInstance方法

// 根据不同获取Class对象方式, 分为三种方式, 不过都是调用Class对象的newInstance方法

// 方式一

Employee e = new Employee();

e.getClass().newInstance();

// 方式二

String s = "java.util.Random";

Object m = Class.forName(s).newInstance();

// 方式三

Object m = Integer.class.newInstance();

比较类型信息"=="

类似于Ojective-C的内省方法,可以用 “==”检查某个类对象是否为指定类对象。

如,

// 检查e的Class对象是否为Employee类对象

Employee e;

if(e.getClass() == Employee.class) {

..

}

// 检查e的父对象是否为Object类对象

Employee e;

Class cl = e.getClass();

Class supercl = cl.getSupperClass();

if (supercl == Object.class) {

// process e or cl

}

捕获异常

如果类名对应类不存在,用forName()创建Class对象会抛出异常ClassNotFoundException。简单处理方式,如下

try {

Claass c1 = Class.forName("TestNotExistClass");

// do sth. with c1

}catch(Exception e) {

e.printStackTrace();

}

反射分析类

字段类:Field,提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段;

方法类:Method;

构造器类:Constructor;

修饰符工具类:Modifiers;

Field 字段类型类, Method 方法类型类, Constructor 构造器类

java.lang.reflect包有三个类Field、Method、Constructor分别用于描述类的域(字段)、方法、构造器。

三个类都有getName()方法,返回项名称。

Field类有getType方法,返回域所属类型的Class对象。

Method类和Constructor类有能够报告参数类型的方法。

Method类getReturnType方法能报告函数返回值类型。

Class常用方法

Field[] getFields(); // 返回包含Field对象的数组,记录了类或其超类的公有域

Field[] getDeclaredFields(); // 返回包含Field对象的数组,记录了类的所有域(不包括超类)

Method getMethod(String name, Class>... parameterTypes); // 返回指定名称 + 形参类型和数量的方法

Method[] getMethods(); // 返回包含Method对象的数组,记录了类或其超类的公有方法

Method[] getDeclaredMethods(); // 返回包含Method对象的数组,记录了类的所有方法(不包括超类)

Constructor[] getConstructors(); // 返回包含Constructor对象的数组,记录了类或其超类的公有构造器

Constructor[] getDeclareConstructor(); // 返回包含Constructor对象的数组,记录了类的所有构造器(不包括超类)

Class getClass(); // 获取对象或者类的Class对象

Class getSuperclass(); // 获取对象或者类的父类Class对象

Class getName(); // 获取对象或类的名称(字符串)

static Class forName(String); // 根据路径创建Class对象

TypeVariable[] getTypeParameters(); // 获取声明的变量类型的数组

int getModifiers(); // 获取对象或类的的Java语言修饰符(如public, static)使用情况

Field、Method、Constructor常用方法

Class getType(); (Field独有) // 获取域的类型

Class getReturnType(); (Method独有) // 获取返回值类型

Class[] getExceptionTypes(); (Constructor和Method类中) // 描述方法抛出的异常类型数组

Class[] getParameterTypes(); (Constructor和Method类中) // 描述参数类型的Class对象数组

Object get(Object obj); // 获取obj对象中Field对象表示的值, 调用对象是Field对象(即要获取的属性),obj对象是要查询的属性值所属类对象. 也就是说,返回obj.调用对象的Field值

void set(Object obj, Object newValue); // 设置obj.调用对象所属Field = newValue

Class getDeclaringClass(); // 返回用于描述类中定义的Constructor、Method或Field的类型

Class getName(); // 获取名称

int getModifiers(); // 获取修饰符使用情况

void setAccessible(boolean ); // 修改反射对象的访问标志 (为调试、持久存储和相似机制提供的功能)

boolean isAccessible(); // 查询反射对象可访问标志

Modifiers 修饰符工具类

Field、Method、Constructor三个类都有getModifiers方法,返回一个整型值,不同的位代表public static等修饰符使用情况。

可以利用java.lang.reflect包中的Modifier类的静态分析getModifiers返回的整型值,如使用Modifier类的isPublic、isPrivate、isFinal判断方法或构造器是否是public、private或final。

Modifiers.toString() 将修饰符打印出来。

获取Field, Method, Constructor

Class类的getFields、getMethods和getConstructors方法返回类的public 域、方法和构造器数组。(所有的public成员,包括超类的public成员)

Class类的getDeclareFields、getDeclareMethods、getDeclareConstructorsf方法返回类类的所有域、方法和构造器数组。(所有的public、private、protected成员,但不包括超类的成员)

Modifiers 常用方法

static String toString(int modifiers); // 读取modifilers中设置的修饰符使用情况的字符串表示

// 读取是否为函数名对应修饰符修饰

static boolean isAbstract(int modifiers);

static boolean isFinal(int modifiers);

static boolean isInterface(int modifiers);

static boolean isNative(int modifiers);

static boolean isPrivate(int modifiers);

static boolean isProtected(int modifiers);

static boolean isPublic(int modifiers);

static boolean isStatic(int modifiers);

static boolean isStrict(int modifiers);

static boolean isSynchronized(int modifiers);

static boolean isVolatile(int modifiers);

运行时使用反射分析对象

查看数据域实际内容

先获得Class对象,再通过Class对象GetDeclaredField获得对应名称数据域,如果是私有访问权限,就利用Field/Method/Constructor的setAccessible方法修改访问权限控制属性。

修改访问权限

示例

Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989) ;

Class cl = harry.getClass(); // cl 代表Employee类对象

Field f = cl .getDeclaredField("name"); // 获取harry对象的name数据域

Object v = f.get (harry) ;

// 获取harry对象的name数据域的值,也就是说"Harry Hacker"字符串对象

存在问题:如果"name"域是private访问权限,那么后面的get方法就会抛出异常,需要先修改访问权限。

Field f = cl .getDeclaredField("name"); // 获取harry对象的name数据域

f.setAccessible(true);

Object v = f.get (harry) ;

查看任意对象的内部信息

ArrayList squres = new ArrayList<>();

for (int i = 1; i <= 5; i++) {

squares.add(i);

}

System.out.println(new ObjectAnalyzer.toString(squares));

使用反射编写泛型数组

如何复制一个数组?为何要使用反射编写泛型数组?

例如实现Arrays.copyOf()方法。常规做法,编写一个通用的程序:

// Arrays.copyOf方法的使用

Employee[] a = new Employee[100];

// modify array a

// 创建新数组(存储),并复制老的元素到数组a (将Employee[] 类型转化成了Object[]类型)

a = Arrays.copyOf(a, 2*a.length); // 超出被复制数组长度部分用默认值0填充

// 自定义copyOf方法 -- 存在问题的自定义方法

public static Object[] badCopyOf(Object[] a, int newLength) {

if(newLength <= 0) return new int[0];

Object[] newArray = new Object[newLength];

System.arraycopy(a, 0, newArray, Math.min(a.length, newLength));

return newArray;

}

程序根据原有数组Employee[] a,新建一个指定长度新数组Object[] newArray,再将数组a的值全部复制到newArray中。

看起来很美好,然而存在一个严重问题:新建的newArray是Object[]类型,即使强制转化成Employee[]类型,也不可能真正转化成Employee[],除非从一开始就是Employee[]中间可以临时转化成Object[]。而用户是不能直接根据Employee所包含的方法使用Object对象的,即使是强制转换也不行。

改进:将a的类型信息也一并传入复制函数。根据参数创建指定类型数组,可以利用Array.newInstance()

Object newArray = Array.newInstance(componentType, newLength);

componentType: 要创建数组类型信息,通过Class对象的getComponentType()方法获得;

newLength: 要创建数组长度,通过Array.getLength(a)获得;

复制一个新的同类型的数组的步骤:

获得数组a的类对象(Class cl = a.getClass());

确认a是数组(Class的isArray方法);

获得数组a的类型信息(cl.getComponentType());

获得数组a的长度(Array.getLength(a));

创建指定类型和长度的数组(Array.newInstance(componentType, newLength));

复制旧数组元素到新数组(System.arraycopy());

实现copyOf代码:

public static Object copyOf(Object a, int newLength) {

Class cl = a.getClass();

if(!cl.isArray() return null; // 确认不是数组,就返回空

Class componentType = cl.getComponentType();

int length = Array.getLength(a); // 前提是a实际是一个数组

Object newArray = Array.newArray(componentType, newLength);

System.arrarycopy(a, 0, newArray, 0, Math.min(length, newLength));

return newArray;

}

调用任意方法

C/C++有指针执行任意函数,java没有方法指针,不过提供了接口,另外还可以通过反射机制调用任意方法。

要调用的方法是invoke,原型:

/**

* 第一个参数obj是隐式参数(静态方法隐式参数可忽略),其余对象提供了显式参数(没有显示参数就用null)

* invoke的参数和返回值必须是Object类型,1)设计风格复杂,类似于C;2)会经过多次类型转化,导致错过编译器类型检查,出错可能性较大(如提供invoke错误参数)

*/

public Object invoke(Object obj, Object...args)

public Object invoke(Object implicitParameter,Object[] explicitParamenters)

使用示例:

String n = (String)m1.invoke(harry);

如果返回的是基本类型,invoke方法会自动返回其包装器类型。

Method m1 = Employee.class.getMethod("getName"); // 无参数方法getName()

Method m2 = Employee.class.getMethod("raiseSalary", double.class); // 包含一个形参 raiseSalary(double)

String name = (String)m1.invoke(e1); // 返回Employee对象e1.getName() 结果

m2.invoke(e2, 10.0); //调用e2.raiseSalary(double),为e2增加薪资

Method m3 = Math.class.getMethod("max", int.class, int.class); // m3代表方法Math.max(int, int), 注意函数名和参数类型、数量都要对应上

int max = (int)m3.invoke(null, 20, 40); // 静态方法invoke第一个参数传入null,后面的参数与取得实际m3所需要形参类型和数量对应上

System.out.println("max number = " + max);

小结

反射要点

获得对应Class对象

通过Class对象调用getDeclaredFields获得对应Field

利用反射查看编译时不清楚的对象域

建议

仅在必要时,才使用Method对象的回调功能(invoke),一般情况下最好使用接口即lambda表达式来调用方法。因为接口回调速度比使用Method对象更快,更容易维护。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值