------- android培训、java培训、期待与您交流! ----------
反射的概念
反射就是把java类中的各种成分映射成相应的java类.例如,一个java类中用一个class类的对象表示,一个类中的组成部分:成员变量,方法,构造方法,包等信息也用一个个java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等也是一个个的类.表示java类的class类显然要提供一系列的方法,来获得相应的变量,方法,构造方法修饰符,包等信息.这些信息技术用相应类的实例对象来表示.它们是Field,method,contructor,package等等.
映射得到的成分有哪些
一个类里面有很多信息,我们把它们都封装成对象。它包括包名、类名、构造方法、方法、成员变量(字段)、甚至方法的修饰符、返回类型、参数类型、注解、类的类加载器、还有类的字节码对象通通封装成对象。你在源文件中能看到的内容,几乎都能通过反射拿到,甚至你看不到的字节码对象都能拿到。这里面最关键的就是这个字节码对象Class,因为其它的东西也可以通过它拿到,所以说Class类是反射的基石。注意它不是指的硬盘上的那些.class后缀的字节码文件,那些对象已经被封装成了File类,这个Class是指将这些.class文件加载进内存后,在内存中的那些字节码信息。很明显这个对象是不能new的,JVM是不可能在没有.class文件的前提下给你造出一个类的字节码信息。
这些成分的特点(注意事项)
①:对于参数名没有封装成一个Name类,而是直接用的String,并且可以通过Class的getName方法获得。
②:对于8种基本类型数据封装成了对象,包括void类型,把它们封装成Class的9个对象,可以通过各自的包装类的成员获得。如:int.class=Integer.TYPE;void.class=Void.TYPE.
③:修饰符不能直接获得其对象,Class中的getModifiers返回的是一个int数据,但是可以通过Modifier包装类的静态方法Modifier.toString(int mod)获得修饰符的字符串表现形式。
④:构造方法和普通方法有很大的不同,它们被封装在两个不同的类中Constructor和Method
⑤:我们得到的成员和方法的映射类与前面的成员和方法不一样,这里得到的是字节码。我们只能得到一个类中的被所有对象共享的信息,即类的信息,可不能得到依赖与具体对象的信息。比方说成员变量,我们可以得到它的名称、类型,因为所有的对象只要属于这个类它都有这么一个类型叫这样名称的成员变量,但是这个字节码信息中就不包含这个成员变量的值,因为值是依赖与对象的,它不是字节码中该有的信息。同样的对于方法,我们可以得到返回值类型、方法名、参数类型,但是我们不能得到一个可以启动的方法。要想得到成员变量的值,或者启动一个方法,必须调用它们包装类的方法,并且方法中必须得传递一个参数,这个参数就是具体的对象。如:Field中的public Object get(Object obj);Method中的public Objectinvoke(Object obj, Object... args),前面是具体对象,后面是这个方法启动所需的参数,方法不接收参数后一个参数可以省略。
⑥:理论上我们可以获得一个类中的所有信息,包括所有成员、所有方法。但是对于从父类继承的非public的成员我们确实无法得到。拿方法来说Class的getMethods方法确实能拿到父类的方法,但是只能拿到公有的(public的),getDeclaredMethods可以拿到所有方法包括私有的,但是它不能拿到从父类继承的。API中确实没有提供一种打破这种封装的方法,让我们拿到从父类继承的非public成员。
怎么得到一个类的字节码对象?
有三种方式
(1)类名.class,如String.class
(2)调用该类对象的方法,对象.getClass。如new Date().getClass().
这个方法是从Object类继承的,是动态绑定的。
(3)调用Class的静态方法Class.for("类名"),如:Class.forName("java.util.Date")
这个方法有一些特殊
①它必须写全包名,因为不同的包下可能有同名的类,类被编译后肯定是带包名的。
②编译的时候是不会检查.class文件是否存在,运行是如果没找到就抛异常。
③在(2)中类已然被加载进内存才可能有对象进而调用方法,但是在(1)中类是不会被加载进内存的,在(3)中运行是它会找到.class文件并尝试把它加进内存,所以有(3)这种方法虽然没有对象,但是静态代码块也是会执行的。如:
public class ForNameTest {
public static void main(String[] args) {
// Class clazz1 = Demo.class;//如果解开这句话把(3)方式注释,静态代码块不会执行
try {
Class clazz3 = Class.forName("Demo");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class Demo {
static {
System.out.println("static area run");
}
}
有了反射我们可以很容易的调用一个类的方法,获得它的成员,甚至是创造出它的对象。
怎么用Class字节码创建一个属于它的类对象?
我们既然都可以得到它的构造方法。那么创建它的对象也是自然的事情。即得到构造方法,然后调用Constructor的newInstance(Object... initargs)方法,参数是构造方法所需的参数,如果为空可以不写。这样子确实可以,但是API中为我们提供了一种更方便的方式,即直接调用Class的newInstance()方法来创建一个对象。其实它内部封装了得到构造方法再启动构造方法的过程,这么写虽然方便,但是只提供了无参的简写方式,Class中没有接受参数的重载方法。另外用这种方式和直接new是有一定的区别的。
用一个例子来说明反射的应用。我们用反射获得其它类的main方法并启动它。虽然我们可以直接用其它类的类名.main(参数)的形式来调用,但是用反射更灵活,我们可以把用到的类名存到配置文件中,这样更改类甚至不用改代码。
import java.lang.reflect.Method;
class ArgumentsTest {
public static void main(String[] args)// main方法很简单,就是打印数组参数
{
for (String arg : args) {
System.out.println(arg);
}
}
}
public class RefMainMethodTest {
public static void main(String[] args) throws Exception {
Method mainMethod = ArgumentsTest.class.getMethod("main",
String[].class);
// 得到方法包装类对象,除了需要方法名main还要参数,因为方法是允许重载的
String[] strs = new String[] { "zhangsan", "lisi", "wangwu" };
mainMethod.invoke(null, new Object[] { strs });
// main方法是静态的不依赖与对象,所以可以不用指定具体的对象,直接写null
}
}
这个例子中最后在传递方法参数是没有写成strs,因为这涉及到JDK1.5的新特性可变参数,它要兼容以前的写法。以前反射中写作Object[]表示传递一组参数,数组里面的每一个参数都作为方法的参数被引用。所以只要是接受数组类型的方法,在反射启动该方法时,如果传递的不是基本数据类型的数组,他都认为是你调用的老方法把这个数组看作是Object[]并且把里面的每一个元素都当成参数。在本例中,main只接受一个参数,这个参数是个String[]。如果你直接写成strs,编译的时候把你看作在调用老方法,会认为你传递了一组参数,就会报参数类型错误。所以解决的办法是把strs看作是一个参数,你可以还是有老方法,即例子中的写法 new Object[] { strs },传一组数据,但是这一组数据中只有一个,它就是strs;也可以用JDK的新特性写成(Object)strs,新特性 public Object invoke( Object obj, Object ... args),当你传递的是一个Object对象的时候,它内部帮你把这个对象封装成数组在调用。也就是说当你写一个非基本数据类型数组的时候,编译器会优先考虑你用的是老方法把它当成是Object[],不会考虑新特性把它当成是一个Object对象。