黑马基础加强之反射
1. Class类与Java反射
public final class Class<T>
extends Object
implements Serializable, GenericDeclaration, Type, AnnotatedElement
1.1.什么是Class类
在Java中,每个class都有一个相应的Class对象。也就是说当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息。Class类是用来描述字节码的。
1.2.关于Class类的小知识
1) Class的源代码声明为 public finalclass Class<T>…{…} , 说明Class类是一个泛型类型,参数T说明某个Class类对象属于哪一个类;
2) final 修饰Class类,说明Class类不会被继承;
3) Class类是不能被new 出来,只能上述的三种方法创建。
4) Class类可以获取字节码文件中的所有信息。
5) 可以使用isPrimitive方法来判断该对象是否是预定义对象,方法格式如下所示:
public boolean isPrimitive():判定指定的 Class 对象是否表示一个基本类型,是true,否false
6) 关于数组类型的Class实例对象,可以通过isArray()方法来判断,方法格式如下所示:
public boolean isArray():判定此 Class 对象是否表示一个数组类,是true,否false
1.3 九种预定义对象
在Class类中共创建了九个预定义对象,表示八个基本类型和void(void也有对应的class);这些类对象由Java虚拟机常见,与其表示的基本类型同名,即boolean,byte,char,short,int,long,float和double。
这些对象仅能通过声明为public staticfinal的变量访问。
1.4.获取Class实例的三种方式
1)利用对象调用getClass()方法获取该对象的Class实例;
1.该方法为非静态方法,继承自Object的getClass()方法;
2.这个方法在反射中不常使用,如果这需要知道明确的类名并建立相应的对象,使用起来非常麻烦。
2)运用类名.class方式来获取Class实例。
1.该方法为静态方法。
2.这个方法也不太常用,因为这方法也是需要明确的类名。
3) 使用Class类的静态方法forName(),用类的名字获取一个Class实例(Class.forName() 中”()”内为类的全名, 如Class.foName(“java.lang.Date”)”);
1.该方法仅仅需要以字符串形式给出类名即可获取这个类的Class对象,这个形式最为常用,在反射编程中经常使用,因为一开始时,源序是不知类名的。
2.使用forName()有两种返回字节码文件的两种方式。
1)如果这份字节码文件已经被加载过的,此时forName()直接返回
2)如果JVM没有找到要加载的字节码文件,那么JVM就指示类加载器去加载该字节码文件。JVM将加载完的字节吗放到自己的缓存中,再返回。
用以下代码表示有这三种获得Class代码的方式。
代码实例:
public class TestClass {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
Person p1 = new Person(20,"胡志平");
Person p2 = new Person();
//根据具体对象来获得Class对象
Class cla1 = p1.getClass();
Class cla2 = p2.getClass();
//输出true
System.out.println(cla1 == cla2);
//使用类名.class获得Class对象
Class cla3 = Person.class;
//使用forName()获得Class对象
Class cla4 = Class.forName("com.ping.test.Person");
//输出true
System.out.println(cla1 == cla3);
//输出true
System.out.println(cla1 == cla4);
}
}
//创建一个对象
class Person{
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person(){}
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
}
由上面的代码可以看出:一个类无论有多少个对象,他们的Class对象是同一个对象,也就是 同一份字节码文件。
1.5 通过反射访问的主要描述信息
组成部分 | 访问方法 | 返回值类型 | 说明 |
包路径 | getPackage() | Package对象 | 获得该类存入路径 |
类名称 | getName() | String对象 | 获得该类的名称 |
继承类 | getSuperclass() | Class对象 | 获得该类继承的类 |
实现接口 | getInterfaces() | Class对象数组 | 获得该类实现的所有接口 |
构造方法 | getConstructeors() | Constructor型数组 | 获得所有权限为public的指定构造函数 |
getConstructor(Class<?>…parameterTypes) | Constructor对象 | 获得权限为public的指定构造函数, | |
getDeclaredConstructors() | Constructor型数组 | 获得所有构造函数,按声明的顺序返回 | |
getDeclaredConstructor(Class<?>parameterTypers) | Constructor对象 | 获得指定构造方法 | |
函数 | getMethods() | Method型数组 | 获得所有权限为public的方法 |
getMethod(String name,Class<?>…parameterTypes) | Method对象 | 获得权限为public的指定方法 | |
getDeclareMethods() | Method型数组 | 获得所有方法,按声明顺序返回 | |
getDeclareMethod(String name , Class<?>….parmeterTypes) | Method对象 | 获得指定方法 | |
成员变量 | getFields() | Field型数组 | 获得所有权限为public的成员变量 |
getField(String name) | Field对象 | 获得权限为public的指定成员变量 | |
getDeclaredFields() | Field对象数组 | 获得所有成员变量,按声明顺序返回 | |
getDeclareField(String name) | Field对象 | 获得指定成员变量 | |
内部类 | getClasses() | Class型数组 | 获得所有权限为public的内部类 |
getDeclaredClasses() | Class型数组 | 获得所有内部类 | |
内部类的声明类 | getDeclaringClass() | Class对象 | 如果该类为内部类,则返回它的成员类,否则返回null |
说明:通过方法getFields()和getMethods()依次获得权限为public的成员变量和方法时,将包含从父类中继承到的成员变量与方法,而通过方法getDeclaredFields()和getDeclaredMethods()只是获得在本类中定义的所有成员变量和方法。
总结:反射就是Java类中的各种成分映射成相应的Java类。
2.Constructor类 -->代表字节码中的构造函数
public final class Constructor<T>
extends AccessibleObject
implements GenericDeclaration, Member
在通过下列一组方法访问构造函数时,将返回Constructor类型的对象或数组。每个Constructor对象代表一个构造函数,利用Constructor对象可以操纵相应的构造函数。
2.1相应的构造函数
问指定的构造方法,需要根据该函数的入口参数来访问。例如,访问一个入口参数依次为String和int 型的构造函数,通过下面两种方式均可实现。
Class.getDeclaredConstructor(String.class,int.class);
Class.getDeclaredConstructor(newClass[]{String.class,int.class});
即jdk.1.4与jdk1.5的差别,参数数组与可变参数的差别也。
2.2 Constructor类常用方法
实例代码:访问构造函数
import java.lang.reflect.Constructor;
public class ConstructorReflectDemo {
/**
* @param args
*/
public static void main(String[] args){
Example example = new Example("我",200);
//获得Class对象
Class exampleC = example.getClass();
//获得所有构造方法
Constructor[]declaredConstructors = exampleC.getDeclaredConstructors();
//遍历构造方法
for(int i = 0 ; i < declaredConstructors.length ; i ++){
//每个构造方法的对象
Constructor constructor = declaredConstructors[i];
//可变参数
System.out.println("查看是否允许带有可变数量的参数:" + constructor.isVarArgs());
//获得所有参数类型
System.out.println("该构造函数的入口参数类型依次为:");
Class[]parameterTypes = constructor.getParameterTypes();
for(int j = 0 ; j < parameterTypes.length ; j++){
System.out.println(" " + parameterTypes[j]);
}
//获得所有可能抛出异常的信息类型
System.out.println("该构造函数抛出异常的类型为:");
Class[]exceptionTypes = constructor.getExceptionTypes();
for(int j = 0 ; j < exceptionTypes.length ; j ++ ){
System.out.print(" " + exceptionTypes[j]);
}
Example example2 = null;
while(example2 == null){
try {
if(i == 0){
//通过执行默认没有参数的构造方法创建对象
example2 = (Example)constructor.newInstance();}
else if(i == 1){
//通过执行两个参数的构造方法创建对象
example2 = (Example)constructor.newInstance("7",5);
}else{
//通过执行具有可变参数的构造方法创建对象
Object[]parameters = new Object[]{new String[]{"100","200","300"}};
example2 = (Example)constructor.newInstance(parameters);
}
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("创建对象时出现抛出异常,下面执行setAccessible()方法");
constructor.setAccessible(true);//设置为允许访问
//e.printStackTrace();
}
}
example2.print();
System.out.println();
}
}
}
class Example{
String s ;
int i1,i2 ,i3;
private Example() {
super();
// TODO Auto-generated constructor stub
}
public Example(String s , int i1){
this.s = s;
this.i1 = i1;
}
public Example(String...strings){
if(0<strings.length){
i1 = Integer.valueOf(strings[0]);
}
if(1<strings.length){
i2=Integer.valueOf(strings[1]);
}if(2<strings.length){
i3 = Integer.valueOf(strings[2]);
}
}
public void print(){
System.out.println("s = " + s);
System.out.println("i1 = " + i1);
System.out.println("i2 = " + i2);
System.out.println("i3 = " + i3);
}
}
输出:
查看是否允许带有可变数量的参数:false
该构造函数的入口参数类型依次为:
该构造函数的抛出异常的类型为:
创建对象时出现抛出异常,下面执行setAccessible()方法
s = null
i1 = 0
i2 = 0
i3 = 0
查看是否允许带有可变数量的参数:false
该构造函数的入口参数类型依次为:
class java.lang.String
int
该构造函数的抛出异常的类型为:
s = 7
i1 = 5
i2 = 0
i3 = 0
查看是否允许带有可变数量的参数:true
该构造函数的入口参数类型依次为:
class [Ljava.lang.String;
该构造函数的抛出异常的类型为:
s = null
i1 = 100
i2 = 200
i3 = 300
3.Field类
public final class Field
extends AccessibleObject
implements Member
3.1 得到Field类的方法
每个Field对象代表一个成员变量,利用Field对象操纵相应的成员变量
3.2 Field类中提供的常用方法
代码实例:访问成员变量
import java.lang.reflect.Field;
public class FiledDemo {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Example_02 example = new Example_02();
Class exampleC = example.getClass();
//获得所有成员变量
Field [] declaredFields = exampleC.getDeclaredFields();
for(int i = 0 ; i < declaredFields.length ; i ++){
//遍历成员变量
Field field = declaredFields[i];
//得到成员变量的名称
String fieldName = field.getName();
//得到成员变量的类型
System.out.println("名称:" + fieldName);
Class fieldType = field.getType();
System.out.println("类型为:" + fieldType);
boolean isTurn = true;
while(isTurn){
//如果该成员变量的访问权限为private,则抛出异常,即不允许访问
try {
//设置循环条件,第循环一次即跳出循环
isTurn = false;
//获得成员变量
System.out.println("修改前的值为:" + field.get(example));
//判断成员变量是否为int型
if(fieldType.equals(int.class)){
System.out.println("利用方法setInt()修改成员变量的值");
//为int型赋值
field.setInt(example, 168);
//判断成员变量是否为float型
}else if(fieldType.equals(float.class)){
System.out.println("利用setFloat()修改成员变量的值");
//为float型赋值
field.setFloat(example, 63.52f);
//判断成员变量是否为boolean型
}else if(fieldType.equals(boolean.class)){
System.out.println("利用setBoolean()修改成员变量的值");
//为boolean型数据赋值
field.setBoolean(example, true);
}else{
//为剩下来的变量赋值
System.out.println("利用方法set()修改成员变量的值");
field.set(example, "Hello");
}
//输出修改的后的值
System.out.println("修改后的值:"+field.get(example));
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("在设置成员变量时抛出异常!");
//设置访问权限
field.setAccessible(true);
isTurn = true;
e.printStackTrace();
}
}
}
}
}
class Example_02{
int i;
public float f;
protected boolean b;
private String s;
}
输出:
名称:i
类型为:int
修改前的值为:0
利用方法setInt()修改成员变量的值
修改后的值:168
名称:f
类型为:float
修改前的值为:0.0
利用setFloat()修改成员变量的值
修改后的值:63.52
名称:b
类型为:boolean
修改前的值为:false
利用setBoolean()修改成员变量的值
修改后的值:true
名称:s
类型为:class java.lang.String
在设置成员变量时抛出异常!
java.lang.IllegalAccessException: Class com.ping.test.FiledDemo can not access a member of class com.ping.test.Example_02 with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
at java.lang.reflect.Field.doSecurityCheck(Field.java:960)
at java.lang.reflect.Field.getFieldAccessor(Field.java:896)
at java.lang.reflect.Field.get(Field.java:358)
at com.ping.test.FiledDemo.main(FiledDemo.java:32)
修改前的值为:null
利用方法set()修改成员变量的值
修改后的值:Hello
5. Method类
在通过下列一组方法访问方法,将返回Method类型的对象或数组。每个Method对象代表一个方法,利用Method对象可以操作相应的方法。
5.1得到Method类的方法
如果是访问指定的方法,需要根据该方法的名称和入口参数的类型来访问。例如,访问一个名称为print、入口参数类型依次为String和int类型的方法,可以通过如下两种方式来实现。
ObjectClass.getDeclaredMethod(“print”,String.class,int.class);
objectClass.getDeclaredMethod(“print”,newClass[]{String.class,int.class});
即jdk.1.4与jdk1.5的差别,参数数组与可变参数的差别也。
5.2 Method类中提供的常用方法
实例代码:访问方法
import java.lang.reflect.Method;
public class MethodDemo {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Example_03 example = new Example_03();
//获得Class对象
Class exampleC = example.getClass();
//获得所有非构造方法
Method[]declaredMethods = exampleC.getDeclaredMethods();
for(int i = 0 ; i < declaredMethods.length ; i++){
Method method = declaredMethods[i];
System.out.println("方法名称为:" + method.getName());
//判断是否允许带可变数量的参数
System.out.println("是否允许带有可变数量的参数:" + method.isVarArgs());
System.out.println("入口参数类型依次是:" );
//获得入口参数类型集合
Class [] parameterTypes = method.getParameterTypes();
//循环输出入口参数
for(int j = 0 ; j < parameterTypes.length ; j ++ ){
System.out.println(" " + parameterTypes[j]);
}
//获得返回值类型
System.out.println("返回值类型为:" + method.getReturnType());
System.out.println("可能抛出的异常为:" );
//获得方法可能发生的异常集合
Class [] exceptionTypes = method.getExceptionTypes();
for(int j = 0 ; j < exceptionTypes.length ; j ++){
System.out.println(" " + exceptionTypes[j]);
}
boolean isTurn = true;
while(isTurn){
//若该方法的访问权限为private,则抛出异常,即不允许访问
isTurn = false;
try {
if(i == 0){
//执行方法
method.invoke(example);}
else if(i == 1){
//执行方法
System.out.println("返回值为:" + method.invoke(example, 167));
}
else if(i == 2){
//执行方法
System.out.println("返回值为:" + method.invoke(example, "7" , 5));
}else{
//定义二维数组
Object [] parameters = new Object[]{new String[]{"P","I","N","G"}};
//执行方法
System.out.println("返回值为:" + method.invoke(example, parameters));
}
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("在执行方法时出现异常:" );
//暴力反射,设置允许访问
method.setAccessible(true);
isTurn = true;
e.printStackTrace();
}
}
System.out.println();
}
}
}
//一个带典型方法的类
class Example_03{
static void staticMethod(){
System.out.println("执行staticMethod方法");
}
public int publicMethod(int i){
System.out.println("执行publicMethod()方法");
return i *100;
}
protected int protectedMethod(String s , int i){
System.out.println("执行protectedMetho()方法");
return Integer.valueOf(s) + i;
}
private String privateMethod(String...strings){
System.out.println("执行privateMethod()方法");
StringBuffer sb = new StringBuffer();
for(int i = 0 ; i < strings.length ; i ++ ){
sb.append(strings[i]);
}
return sb.toString();
}
}
输出:
方法名称为:staticMethod
是否允许带有可变数量的参数:false
入口参数类型依次是:
返回值类型为:void
可能抛出的异常为:
执行staticMethod方法
方法名称为:publicMethod
是否允许带有可变数量的参数:false
入口参数类型依次是:
int
返回值类型为:int
可能抛出的异常为:
执行publicMethod()方法
返回值为:16700
方法名称为:protectedMethod
是否允许带有可变数量的参数:false
入口参数类型依次是:
class java.lang.String
int
返回值类型为:int
可能抛出的异常为:
执行protectedMetho()方法
返回值为:12
方法名称为:privateMethod
是否允许带有可变数量的参数:true
入口参数类型依次是:
class [Ljava.lang.String;
返回值类型为:class java.lang.String
可能抛出的异常为:
在执行方法时出现异常:
java.lang.IllegalAccessException: Class com.ping.test.MethodDemo can not access a member of class com.ping.test.Example_03 with modifiers "private transient"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
at java.lang.reflect.Method.invoke(Method.java:588)
at com.ping.test.MethodDemo.main(MethodDemo.java:57)
执行privateMethod()方法
返回值为:PING
6. 执行main方法
(1)目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。用普通方式调完后,大家要明白为什么要用反射方式去调啊?
(2)问题:启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,newString[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。
(3)解决办法:
mainMethod.invoke(null,new Object[]{newString[]{"xxx"}});
mainMethod.invoke(null,(Object)newString[]{"xxx"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了
示例代码:
class TestArguments
{
public static void main(String[] args)
{
for (String arg : args)
{
System.out.println(arg);
}
}
}
通过传统方式调用代码如下所示:
TestArguments.main(new String[]{"111","222","333"});
运行结果如下所示:
通过反射的方式调用TestArguments这个类的main方法,代码如下所示:
String startingClassName = args[0];
Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);
//下面语句使用会出现异常见下图
mainMethod.invoke(null, new String[]{ "111", "222", "333" });
运行时错误结果如下所示:
通过第(3)步中的解决方法运行时代码如下所示:
String startingClassName = args[0];
Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);
//编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了
mainMethod.invoke(null, new Object[] { new String[] { "111", "222", "333" } });
mainMethod.invoke(null, (Object) new String[] { "111" });
运行时结果如下所示:
提示:
(1)获取一个类的完整名称:使用功能键“F2”,如图所示:
(2)在MyEclipse中如何传递一个参数运行,步骤如下图所示:
7.数组的反射
(1)具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
(2)Arrays.asList()方法处理int[]和String[]时的差异。
int[] a1 = new int[] { 1, 2, 3 };
String[] a4 = new String[] { "a", "b", "c" };
System.out.println("直接打印出对象中内容");
System.out.println(a1);
System.out.println(a4);
System.out.println("Arrays.asList()方法输出");
System.out.println(Arrays.asList(a1));
System.out.println(Arrays.asList(a4));
运行结果如下所示:
(3)Array工具类用于完成对数组的反射操作。
// 通过反射打印数组
private static void printObject(Object obj)
{
Class clazz = obj.getClass();
if (clazz.isArray())
{
int len = Array.getLength(obj);
for (int i = 0; i < len; i++)
{
System.out.println(Array.get(obj, i));
}
}
else
{
System.out.println(obj);
}
}
----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------