Java基础之反射机制详解
写在最前
很高兴前一段的文章能给大家带来帮助,相信大家都清楚基础知识对于程序员来说是解决问题的根基,笔者的系列文章将从英文定义、思路分析、实例应用一步一步对重要的基础知识进行讲解,没关注的童鞋赶紧点一波关注,跟笔者一起巩固你们的基础吧!!
附前文地址:十个不可忽视的Java基础知识
上次的文章中提到了Java的反射机制,但有读者私下反映讲解并不透彻,仍然属于懵圈状态,因此本文将对其进行详解。
RTTI和反射?
这是一个经常被混淆的问题。RTTI or Reflection? 相信看过《Thinking in Java》的同学都有这么个疑惑,在Type Information这一章中有这么一段话。
Java allows you to discover information about objects and classes at run time. This takes two forms: "traditional" RTTI, which assumes that you have all the types available at compile time, and the reflection mechanism, which allows you to discover and use class information solely at run time.
这其中,RTTI(Runtime Type Information)是C++ 中的一个概念,B大明显先学的C++,在这本书之前也是先出版过《Thinking in C++》,因此可能对这个名词有深厚的感情?总之,千万不要尝试去区分RTTI和反射(Reflection)的区别,这两者就像老人和男人(来自某知乎网友贴切的举例),分开理解是很蠢的。
这里多说一句,诸如TIJ这种神书,也并不是适合每一个人去理解知识的,在有些篇幅中,如果你发现自己卡住了,一定要多看例子,多查资料,用自己从中悟出的道理去解释,不要去和死定义纠缠。
反射的目的
很多人在熟练掌握本文所述内容后仍不解RTTI的目的,一般有两个原因:一是代码编写量过小,自己设计的经验不足,二则是不具备动态的设计思路。我们先来看下面和个例子(出自TIJ):
import java.util.*;
abstract class Shape{
void draw() {
System.out.println(this + ".draw() ");
}
abstract public String toString();
}
class Circle extends Shape {
public String toString() {
return "Circle ";
}
}
class Square extends Shape {
public String toString() {
return "Square ";
}
}
class Triangle extends Shape {
public String toString() {
return "Triangle ";
}
}
public class Shapes {
public static void main(String[] args) {
List<Shape> shapeList = Arrays.asList(
new Circle(), new Square(), new Triangle()
);
for (Shape shape : shapeList) {
shape.draw();
}
}
}
在上例中,我们创建了Shape类,其中Circle, Triangle, 以及Square是他的三个子类。在主方法中我们创建了Shape的list,然后用匿名的方式创建三种子类的引用,不难看出,在这里他们都被强制向上转换为了Shape。所以输出的值为:
/*
Output:
Circle.draw()
Square.draw()
Triangle.draw()
*/
由此可见,我们通过熟悉多态(Polymorphism),也就是在子类中重写toString()方法,实现了最基础的在运行中获取类型信息。利用此种方法,可使你的代码更易读写及维护,设计也更容易被理解,执行或修改。但如果我们不只是需要draw()方法被执行,而需要执行一些并不适用于全部子类的方法,比如rotate(),旋转,对于Circle来说执行没有任何意义,那么我们就需要反射机制,我们便可以使Shape调用属于他子类独有的方法。
这里光靠定义说明确实是很难理解,举个通俗的例子吧。比如屏幕上有一大堆各种各样的水果,苹果,梨,香蕉。苹果被点击时候执行爆炸方法,香蕉被点击的时候执行剥皮方法,梨被点击的时候砍成两半。那么我不知道用户点击的是哪个水果,但我希望直接执行这个水果的方法,这时候利用反射机制将会使得我们的代码简单已读且利于修改再开发。
Class类型及对象的获取方法
在运行时(runtime)承载类型信息的Class对象,你的程序中任何一个类,在你编译的时候,这个Class对象都会被创建。简单点说就是编译后出现的Name.class那个文件,Name是和你的类名相同的名字(创建这个对象的是JVM的一个子系统叫做"class loader")。Java中获取Class类型的方法有如下三种:
- .class
所有的引用数据类型我们都可以在类名后加上".class"来获取Class类型,这种方法是静态的。这里要多说一句的是,若是基本类型,我们同样可以使用“封装类.TYPE”的方法,比如"int.class"对应的是"Integer.TYPE"。
- Class.forName()
通过一个类的String名来获取Class类型,这种方法同上一样为静态,且必须抛出ClassNotFoundException。
- getClass()
这种方法是通过实例名来获取Class类型,是动态的,这点与前两种不同,注意区分。
综合来看上述三种方法,我们用一个表格来简单来区分他们:
方法名 | 获取形式 |
---|---|
.class | 获取声明时的类 |
getClass() | 获取运行时的类 |
Class.forName() | 通过类名来获得类 |
在获得类型之后,在后面加一个newInstance()方法,便能创建对象,例如:
Class cc = Class.forName("Daniel");
Object oo = cc.newInstance();
//这里调用的是关于cc的无参数构造方法
类中属性的获取方法
有了Class对象后,我们当然希望对其的方法和参数进行执行或修改,此处我们最常见的便是getDeclaredFields()方法了。例如:
Class cc = Class.forName("Daniel");
Field[] field = cc.getDeclaredFields();
需要注意的是,类中又很多方法,所以我们应该定义一个Field数组。在这里常用的方法我将列在下面的这个表格当中,需要用时我们只需套用特定的方法即可,理解起来非常容易。
方法 | 作用 |
---|---|
getDeclaredMethods() | 获取所有的方法 |
getReturnType() | 获得方法的返回类型 |
getParameterTypes() | 获得方法的传入参数类型 |
getDeclaredMethod("",.class,……) | 获得特定的方法 |
getDeclaredConstructors() | 获取所有的构造方法 |
getDeclaredConstructor(.class,……) | 获取特定的构造方法 |
getSuperclass() | 获取某类的父类 |
getInterfaces() | 获取某类实现的接口 |
用以上方法进行反编译,使我们的代码变得更加灵活,这也是OO类型编程的一个终极目标。
说到这里,我们再一起来看一下上次文章中提到的来自CSDN上的例子:
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* <a href="http://lib.csdn.net/base/java" class='replace_word' title="Java 知识库" target='_blank' style='color:#df3434; font-weight:bold;'>Java </a>Reflection Cookbook
*
* @author Michael Lee
* @since 2006-8-23
* @version 0.1a
*/
public class Reflection {
/**
* 得到某个对象的公共属性
*
* @param owner, fieldName
* @return 该属性对象
* @throws Exception
*
*/
public Object getProperty(Object owner, String fieldName) throws Exception {
Class ownerClass = owner.getClass();
Field field = ownerClass.getField(fieldName);
Object property = field.get(owner);
return property;
}
/**
* 得到某类的静态公共属性
*
* @param className 类名
* @param fieldName 属性名
* @return 该属性对象
* @throws Exception
*/
public Object getStaticProperty(String className, String fieldName)
throws Exception {
Class ownerClass = Class.forName(className);
Field field = ownerClass.getField(fieldName);
Object property = field.get(ownerClass);
return property;
}
/**
* 执行某对象方法
*
* @param owner
* 对象
* @param methodName
* 方法名
* @param args
* 参数
* @return 方法返回值
* @throws Exception
*/
public Object invokeMethod(Object owner, String methodName, Object[] args)
throws Exception {
Class ownerClass = owner.getClass();
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Method method = ownerClass.getMethod(methodName, argsClass);
return method.invoke(owner, args);
}
/**
* 执行某类的静态方法
*
* @param className
* 类名
* @param methodName
* 方法名
* @param args
* 参数数组
* @return 执行方法返回的结果
* @throws Exception
*/
public Object invokeStaticMethod(String className, String methodName,
Object[] args) throws Exception {
Class ownerClass = Class.forName(className);
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Method method = ownerClass.getMethod(methodName, argsClass);
return method.invoke(null, args);
}
/**
* 新建实例
*
* @param className
* 类名
* @param args
* 构造函数的参数
* @return 新建的实例
* @throws Exception
*/
public Object newInstance(String className, Object[] args) throws Exception {
Class newoneClass = Class.forName(className);
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Constructor cons = newoneClass.getConstructor(argsClass);
return cons.newInstance(args);
}
/**
* 是不是某个类的实例
* @param obj 实例
* @param cls 类
* @return 如果 obj 是此类的实例,则返回 true
*/
public boolean isInstance(Object obj, Class cls) {
return cls.isInstance(obj);
}
/**
* 得到数组中的某个元素
* @param array 数组
* @param index 索引
* @return 返回指定数组对象中索引组件的值
*/
public Object getByArray(Object array, int index) {
return Array.get(array,index);
}
}
再一次回看我们会发现,以上不过只是将各种方法的应用简单写出,并没有过多的逻辑。简单来说,这种类型的代码对于我们来说只是字典型的材料,在设计的过程中即查即用,没必要逐一去背诵。
结语
关于Java反射部分的知识,本文中也涉及得差不多了。对于反射机制,比起了解语法,更重要的是要去理解它在设计模式中发挥的作用,而设计模式是Java编程思想中相当重要的一环,有关内容在后续的文章中可能还会提及,希望大家持续关注。
文章中若有错误或不尽人意之处还请大家指出,欢迎大家私信作者的微博:LightningDC或直接在文章下方回复进行交流。笔者还会继续为大家更新,欢迎关注,如果文章对你有用,就随手点个赞吧哈哈,码字不易。