反射

Java中的反射库(reflection library)中提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵Java代码的程序。

能够分析类能力的程序称为反射(reflective)。反射的功能很强大,下面是反射的用途:

  • 在运行中分析类的能力;
  • 在运行中查看对象,例如编写一个toString方法供所有类使用;
  • 实现通用的数组操作代码;
  • 利用Method对象,这个对象很像C++中的函数指针;

1 Class类的使用

Java是面向对象的语言,在Java中,所有的东西都是类(除了静态方法和基本类型)。那么,类是不是一个对象呢?

在Java程序运行期间,Java运行时系统时钟为所有的对象维护一个被称为运行时的类型标志。这个信息跟踪着每个对象的所属类。虚拟机利用运行时类型信息选择相应的方法执行。

Class类保存了Java类的这些信息。官网中,对这个类型称为类的类类型,也就是说一个类的对象。比如,有一个类Student,可以使用下面的代码创建一个实例:

  1. Stuent stu=new Studnet();  
Stuent stu=new Studnet();
即stu是类Student的一个实例。那既然类也是对象,那么Student是什么的实例呢?Student是类Class的一个实例。

可以通过如下三种方法获得一个类的类类型。

(1)getClass方法

如果有一个类的实例,那么可以通过这个实例的getClass方法获得这个类的类类型:

  1. Class cl=stu.getClass();  
Class cl=stu.getClass();
(2)静态方法forName

forName是Class的一个静态方法,如果没有一个类的实例,但是知道这个类的名字,可以使用这个方法获得这个类的类类型:

  1. Class cl=Class.forName("Student");  
Class cl=Class.forName("Student");
如果类名保存在字符串中,并可在运行中改变,就可以使用这个方法。当然,这个方法只有在参数是类名或接口名的时候才能够执行,否则forName方法将抛出一个异常。因此,使用这个方法时,应该处理这个异常。

(3).class

第三个获得类类型 的方法很简单。如果T是任意的Java类型,T.class将代表匹配的类对象。比如:

  1. Class cl1=Student.class;  
  2. Class cl2=int.class  
  3. Class cl3=Double[].class  
Class cl1=Student.class;
Class cl2=int.class
Class cl3=Double[].class

注意,一个Class对象实际上表示一个类型,而这个类型不一定是一个类。比如,int不是类,但int.class是一个Class类型的对象。

虚拟机为每个类型管理一个Class对象。因此可以使用==运算符实现两个类对象的比较:

  1. if(stu.getClass()==Student.class)...  
if(stu.getClass()==Student.class)...

这将判断为真。

Class中最常用的一个方法就是getName方法,这个方法将返回类的名字。

还可以通过类类型创建一个类的实例。比如:

  1. Class cl=Student.class;  
  2. Student stu=(Student)cl.newInstance();  
Class cl=Student.class;
Student stu=(Student)cl.newInstance();

这就创建了一个Student实例。不过,newInstance方法使用的是默认的构造器(没有参数的构造器)初始化新创建的对象。

使用forName和newInstance方法可以根据存储在字符串中的类名创建一个实例:

  1. String s="java.util.Date";  
  2. Object date=Class.forName(s).newInstance();  
String s="java.util.Date";
Object date=Class.forName(s).newInstance();

2 捕获异常

在第一节中,Class类的forName方法可能会抛出一个异常,可以使用下面的代码捕获并处理异常:

  1. try{  
  2.     String name=...;//get class name  
  3.     Class cl=Class.forName(name);//might throw exception  
  4.     do something with cl  
  5. }catch(Exception e){  
  6.     e.printStackTrace();  
  7. }  
try{
    String name=...;//get class name
    Class cl=Class.forName(name);//might throw exception
    do something with cl
}catch(Exception e){
    e.printStackTrace();
}

3 利用反射分析类的能力

下面简要的介绍一下反射机制最重要的内容:检查类的结构。

在java.lang.reflect包中有三个类Field、Method和Constructor分别描述类的域、方法和构造器。这三个类都有一个叫getName的方法,用来返回项目的名称。Field类有一个getType方法,用来返回描述域所属的Class对象。Method和Constructor类有能够报告参数类型的方法,Method还有一个可以报告返回类型的方法。这三个类有一个getModifiers的方法,它将返回一个整数值,用不同的位开关描述public和static这样的修饰符的使用状况,还可以利用Modifier.toString方法将修饰符打印出来。

Class类的getFields、getMethods和getCostructors方法可以获得类提供的public域、方法和构造器数组,其中包括超类的共有成员。Class类的getDeclatedFields、getDeclatedMethods和getDeclaredConstructors方法可以获得类中声明的全部域、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员。

Constructor类:

  • String getName():返回构造器的名字;
  • int getModifiers():返回修饰符;
  • int getParameterCount():返回构造器中参数的个数;
  • Class<?>[] getParameterTypes():返回参数的类型;

Field类:

  • String getName():返回域的名字;
  • int getmodifiers():返回修饰符;
  • Class<?> getType():返回域的类类型;

Method类:

  • String getName():返回方法的名字;
  • int getModifiers():返回修饰符;
  • int getParameterCount():返回参数的个数;
  • Class<?>[] getParameterTypes():返回参数的类类型;
  • Class<?> getReturnType():返回返回类类型;

下面的代码编写了一个可以打印一个类的所有构造器、方法和域的相关信息的类:

  1. package reflection;  
  2. import java.lang.reflect.*;  
  3. public class PrintClassInfo {  
  4.     private PrintClassInfo(){}  
  5.     public static void printClassInfo(String name){  
  6.         try{  
  7.             Class cl=Class.forName(name);  
  8.             Class supercl=cl.getSuperclass();  
  9.             String modifiers=Modifier.toString(cl.getModifiers());  
  10.             if(modifiers.length()>0)System.out.print(modifiers+" ");  
  11.             System.out.print("class "+name);  
  12.             if(supercl!=null&&supercl!=Object.class)System.out.print(" extends "+supercl.getName());  
  13.             System.out.print("\n{\nConstructors:\n");  
  14.             printConstructors(cl);  
  15.             System.out.println("Methods:");  
  16.             printMethods(cl);  
  17.             System.out.println("Fields:");  
  18.             printFields(cl);  
  19.             System.out.println("}");  
  20.         }catch(ClassNotFoundException e){  
  21.             e.printStackTrace();  
  22.         }  
  23.         System.exit(0);  
  24.     }  
  25.     public static void printConstructors(Class cl){  
  26.         Constructor[] cons=cl.getConstructors();  
  27.         for(Constructor c:cons){  
  28.             String name=c.getName();  
  29.             System.out.print("    ");  
  30.             String modifiers=Modifier.toString(c.getModifiers());  
  31.             if(modifiers.length()>0)System.out.print(modifiers+" ");  
  32.             System.out.print(name+"(");  
  33.               
  34.             Class[] paramTypes=c.getParameterTypes();  
  35.             for(int i=0;i<paramTypes.length;i++)  
  36.             {  
  37.                 if(i>0)System.out.print(", ");  
  38.                 System.out.print(paramTypes[i].getName());  
  39.             }  
  40.             System.out.println(");");  
  41.         }  
  42.     }  
  43.     public static void printMethods(Class cl){  
  44.         Method[] methods=cl.getDeclaredMethods();  
  45.         for(Method m:methods){  
  46.             Class retType=m.getReturnType();  
  47.             String name=m.getName();  
  48.             System.out.print("    ");  
  49.             String modifiers=Modifier.toString(m.getModifiers());  
  50.             if(modifiers.length()>0)System.out.print(modifiers+" ");  
  51.             System.out.print(retType.getName()+" "+name+"(");  
  52.               
  53.             Class[] paramTypes=m.getParameterTypes();  
  54.             for(int i=0;i<paramTypes.length;i++){  
  55.                 if(i>0)System.out.print(", ");  
  56.                 System.out.print(paramTypes[i].getName());  
  57.             }  
  58.             System.out.println(");");  
  59.         }  
  60.     }  
  61.     public static void printFields(Class cl){  
  62.         Field[] fields=cl.getDeclaredFields();  
  63.         for(Field f:fields){  
  64.             Class type=f.getType();  
  65.             String name=f.getName();  
  66.             System.out.print("    ");  
  67.             String modifiers=Modifier.toString(f.getModifiers());  
  68.             if(modifiers.length()>0)System.out.print(modifiers+" ");  
  69.             System.out.println(type.getName()+" "+name+";");  
  70.         }  
  71.     }  
  72. }  
package reflection;
import java.lang.reflect.*;
public class PrintClassInfo {
	private PrintClassInfo(){}
	public static void printClassInfo(String name){
		try{
			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);
			if(supercl!=null&&supercl!=Object.class)System.out.print(" extends "+supercl.getName());
			System.out.print("\n{\nConstructors:\n");
			printConstructors(cl);
			System.out.println("Methods:");
			printMethods(cl);
			System.out.println("Fields:");
			printFields(cl);
			System.out.println("}");
		}catch(ClassNotFoundException e){
			e.printStackTrace();
		}
		System.exit(0);
	}
	public static void printConstructors(Class cl){
		Constructor[] cons=cl.getConstructors();
		for(Constructor c:cons){
			String name=c.getName();
			System.out.print("    ");
			String modifiers=Modifier.toString(c.getModifiers());
			if(modifiers.length()>0)System.out.print(modifiers+" ");
			System.out.print(name+"(");
			
			Class[] paramTypes=c.getParameterTypes();
			for(int i=0;i<paramTypes.length;i++)
			{
				if(i>0)System.out.print(", ");
				System.out.print(paramTypes[i].getName());
			}
			System.out.println(");");
		}
	}
	public static void printMethods(Class cl){
		Method[] methods=cl.getDeclaredMethods();
		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 i=0;i<paramTypes.length;i++){
				if(i>0)System.out.print(", ");
				System.out.print(paramTypes[i].getName());
			}
			System.out.println(");");
		}
	}
	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());
			if(modifiers.length()>0)System.out.print(modifiers+" ");
			System.out.println(type.getName()+" "+name+";");
		}
	}
}

这个类有一个静态方法printClassInfo,用于打印类的基本信息,在方法的内部调用了printConstructors、printMethosds和printFields方法。测试代码如下:

  1. String name;  
  2. System.out.println("Enter the class name:");  
  3. Scanner scanner=new Scanner(System.in);  
  4. name=scanner.next();  
  5. PrintClassInfo.printClassInfo(name);  
String name;
System.out.println("Enter the class name:");
Scanner scanner=new Scanner(System.in);
name=scanner.next();
PrintClassInfo.printClassInfo(name);

运行输入java.lang.String,结果如下:

public final class java.lang.String
{
Constructors:
    public java.lang.String([B, int, int);
    public java.lang.String([B, java.nio.charset.Charset);
    public java.lang.String([B, java.lang.String);
    public java.lang.String([B, int, int, java.nio.charset.Charset);
    public java.lang.String([B, int, int, java.lang.String);
    public java.lang.String(java.lang.StringBuilder);
    public java.lang.String(java.lang.StringBuffer);
    public java.lang.String([B);
    public java.lang.String([I, int, int);
    public java.lang.String();
    public java.lang.String([C);
    public java.lang.String(java.lang.String);
    public java.lang.String([C, int, int);
    public java.lang.String([B, int);
    public java.lang.String([B, int, int, int);
Methods:
    public boolean equals(java.lang.Object);
    public java.lang.String toString();
    public int hashCode();
    public int compareTo(java.lang.String);
    public volatile int compareTo(java.lang.Object);
    public int indexOf(java.lang.String, int);
    public int indexOf(java.lang.String);
    public int indexOf(int, int);
    public int indexOf(int);
    static int indexOf([C, int, int, [C, int, int, int);
    static int indexOf([C, int, int, java.lang.String, int);
    public static java.lang.String valueOf(int);
    public static java.lang.String valueOf(long);
    public static java.lang.String valueOf(float);
    public static java.lang.String valueOf(boolean);
    public static java.lang.String valueOf([C);
    public static java.lang.String valueOf([C, int, int);
    public static java.lang.String valueOf(java.lang.Object);
    public static java.lang.String valueOf(char);
    public static java.lang.String valueOf(double);
    public char charAt(int);
    private static void checkBounds([B, int, int);
    public int codePointAt(int);
    public int codePointBefore(int);
    public int codePointCount(int, int);
    public int compareToIgnoreCase(java.lang.String);
    public java.lang.String concat(java.lang.String);
    public boolean contains(java.lang.CharSequence);
    public boolean contentEquals(java.lang.CharSequence);
    public boolean contentEquals(java.lang.StringBuffer);
    public static java.lang.String copyValueOf([C);
    public static java.lang.String copyValueOf([C, int, int);
    public boolean endsWith(java.lang.String);
    public boolean equalsIgnoreCase(java.lang.String);
    public static transient java.lang.String format(java.util.Locale, java.lang.String, [Ljava.lang.Object;);
    public static transient java.lang.String format(java.lang.String, [Ljava.lang.Object;);
    public void getBytes(int, int, [B, int);
    public [B getBytes(java.nio.charset.Charset);
    public [B getBytes(java.lang.String);
    public [B getBytes();
    public void getChars(int, int, [C, int);
    void getChars([C, int);
    private int indexOfSupplementary(int, int);
    public native java.lang.String intern();
    public boolean isEmpty();
    public static transient java.lang.String join(java.lang.CharSequence, [Ljava.lang.CharSequence;);
    public static java.lang.String join(java.lang.CharSequence, java.lang.Iterable);
    public int lastIndexOf(int);
    public int lastIndexOf(java.lang.String);
    static int lastIndexOf([C, int, int, java.lang.String, int);
    public int lastIndexOf(java.lang.String, int);
    public int lastIndexOf(int, int);
    static int lastIndexOf([C, int, int, [C, int, int, int);
    private int lastIndexOfSupplementary(int, int);
    public int length();
    public boolean matches(java.lang.String);
    private boolean nonSyncContentEquals(java.lang.AbstractStringBuilder);
    public int offsetByCodePoints(int, int);
    public boolean regionMatches(int, java.lang.String, int, int);
    public boolean regionMatches(boolean, int, java.lang.String, int, int);
    public java.lang.String replace(char, char);
    public java.lang.String replace(java.lang.CharSequence, java.lang.CharSequence);
    public java.lang.String replaceAll(java.lang.String, java.lang.String);
    public java.lang.String replaceFirst(java.lang.String, java.lang.String);
    public [Ljava.lang.String; split(java.lang.String);
    public [Ljava.lang.String; split(java.lang.String, int);
    public boolean startsWith(java.lang.String, int);
    public boolean startsWith(java.lang.String);
    public java.lang.CharSequence subSequence(int, int);
    public java.lang.String substring(int);
    public java.lang.String substring(int, int);
    public [C toCharArray();
    public java.lang.String toLowerCase(java.util.Locale);
    public java.lang.String toLowerCase();
    public java.lang.String toUpperCase();
    public java.lang.String toUpperCase(java.util.Locale);
    public java.lang.String trim();
Fields:
    private final [C value;
    private int hash;
    private static final long serialVersionUID;
    private static final [Ljava.io.ObjectStreamField; serialPersistentFields;
    public static final java.util.Comparator CASE_INSENSITIVE_ORDER;
}

值得注意的是,这个程序可以分析Java解释器能够加载的任何类,而不仅仅是编译程序时可以使用的类。

4 动态加载类

类的加载有两种方式,一个是在编译时加载的静态加载类,另一个是在运行时加载的动态加载类。当我们使用new来创建一个对象时,使用的是静态加载类,这个时候必须保证类已经实现。而Class的forName方法不但可以获得一个类的类类型,还是一个动态加载类的方法,可以越过编译,在运行时加载一个类。

动态加载有什么好处么?考虑一个场景,比如自己要实现一个办公软件Office,里面有各种组件Word和Excel等。有一个Office工具可以启动各种组件,由于现阶段只有两个组件Word和Excel,那么Office可以这样编写:

  1. class Office  
  2. {  
  3.     public static void main(String[] args)  
  4.     {  
  5.         if("Word".equals(args[0]))  
  6.         {  
  7.             Word w=new Word();  
  8.             w.start();  
  9.         }  
  10.         if("Excel".equals(args[0]))  
  11.         {  
  12.             Excel e=new Excel();  
  13.             e.start();  
  14.         }  
  15.     }  
  16. }  
class Office
{
	public static void main(String[] args)
	{
		if("Word".equals(args[0]))
		{
			Word w=new Word();
			w.start();
		}
		if("Excel".equals(args[0]))
		{
			Excel e=new Excel();
			e.start();
		}
	}
}

这里,根据传进来的参数选择启动哪一个组件。

这里使用的就是静态加载,因为使用new创建的一个对象。不过,如果开发Word的组工作较快,已经完成了开发,代码假如如下:

  1. class Word  
  2. {  
  3.     public void start()  
  4.     {  
  5.         System.out.println("Word running...");  
  6.     }  
  7. }  
class Word
{
	public void start()
	{
		System.out.println("Word running...");
	}
}

这仅仅打印一句话,但是Excel组开发较慢,还没有开发出来,那么Office还能用么?当然不能,编译Office出错:


说找不到Excel类。这样,整个的Office都不能使用了。

这时,就可以使用动态加载了。使用Class的forName方法动态加载一个类:

  1. Word w=(Word)Class.forName(args[0]);  
Word w=(Word)Class.forName(args[0]);

不过这里有个问题,就是由于没能事先知道首先实现的是哪个组件,所以不能强制类型转换成Word。那怎么办?就可以使用接口了。定义一个接口OfficeAble,让所有的组件实现这个接口即可:

  1. interface OfficeAble  
  2. {  
  3.     void start();  
  4. }  
interface OfficeAble
{
	void start();
}

新的Word类如下:

  1. class Word implements OfficeAble  
  2. {  
  3.     public void start()  
  4.     {  
  5.         System.out.println("Word running...");  
  6.     }  
  7. }  
class Word implements OfficeAble
{
	public void start()
	{
		System.out.println("Word running...");
	}
}
这样,在OfficeBetter类中就可以使用接口了:

  1. class OfficeBetter  
  2. {  
  3.     public static void main(String[] args)  
  4.     {  
  5.         try  
  6.         {  
  7.             Class cl=Class.forName(args[0]);  
  8.             OfficeAble oa=(OfficeAble)cl.newInstance();  
  9.             oa.start();  
  10.         }  
  11.         catch(Exception e)  
  12.         {  
  13.             e.printStackTrace();  
  14.         }  
  15.     }  
  16. }  
class OfficeBetter
{
	public static void main(String[] args)
	{
		try
		{
			Class cl=Class.forName(args[0]);
			OfficeAble oa=(OfficeAble)cl.newInstance();
			oa.start();
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
}
注意,forName会抛出异常,要进行捕获并处理。

这时,编译Word和OfficeBetter:


编译没有错误了,运行也正确。

当启动Excel时就会报错:


说没有找到Excel类。

当Excel实现完后,只需要编译Excel,不需要编译OfficeBetter就可以运行:


结果正确。这样,使用动态加载类,就可以随时添加功能而不需要重新编译。

5 在运行时使用反射分析对象

现在已经知道了如何查看任意对象的数据域名称和类型:

(1)获得对应的类类型;

(2)通过类类型调用getDeclaredFields方法;

利用反射机制可以查看在编译时还不清楚的对象域。

查看对象域的关键方法是Field类中的get方法。如果f是一个Field类型的对象,obj是某个包含f域的类的对象,f.get(obj)将返回一个对象,其值为obj与的当前值。

既然能够得到域的值,那么也就能设置域的值。可以使用Field类中的set方法设置域的值,用法为f.set(obj,value),obj是包含f域的对象,value是要设置的值。

还要注意,如果f是一个私有域,那么直接使用get方法会抛出一个IllegalAccessException异常。除非拥有访问权限,否则Java安全机制只允许查看任意对象有哪些域而不允许得去域的值。

反射机制的默认行为受限于Java的访问控制。然而,如果一个Java程序没有受到安全管理器的控制,就可以覆盖访问控制。可以调用Field、Method或Constructor类中的setAccessible方法达到这个目的:

f.setAccessible(true);

setAccessible方法是AccessibleObject类中的一个方法,它是Field、Method和Constructor类的超类。

下面的代码演示了使用get和set方法获得和设置域的值:

  1. Student stu=new Student("Bai",20,99);  
  2. Class cl=stu.getClass();  
  3. Field f=cl.getDeclaredField("name");  
  4. f.setAccessible(true);  
  5. String stuname=(String)f.get(stu);  
  6. System.out.println("The name is:"+stuname);  
  7. f.set(stu, "Liu");  
  8. System.out.println("The name is:"+(String)f.get(stu));  
Student stu=new Student("Bai",20,99);
Class cl=stu.getClass();
Field f=cl.getDeclaredField("name");
f.setAccessible(true);
String stuname=(String)f.get(stu);
System.out.println("The name is:"+stuname);
f.set(stu, "Liu");
System.out.println("The name is:"+(String)f.get(stu));
其中Student类包含String类型的name、int类型的age和double类型的score。

运行结果如下:

The name is:Bai
The name is:Liu

即成功获得和设置域的值。

不过get还有一个问题,就是name是一个String,因此把它当做Object返回没有问题。但是如果要返回double的score,double不是对象,这时可以使用Field类中的getDouble方法。此时,反射机制会自动将这个域值打包到相应的对象包装器中。

  1. Field fs=cl.getDeclaredField("score");  
  2. fs.setAccessible(true);  
  3. System.out.println("The score is:"+fs.getDouble(stu));  
Field fs=cl.getDeclaredField("score");
fs.setAccessible(true);
System.out.println("The score is:"+fs.getDouble(stu));

结果如下:

The score is:99.0

6 使用invoke调用任意方法

在C和C++中,可以从函数指针执行任意函数。虽然Java没有提供函数指针,但反射机制允许调用任意方法。

和Field中的get方法类似,Method类中有个invoke方法,它允许调用包装在当前Method对象中的方法。invoke方法的签名是:

  1. Object invoke(Object obj,Object... args);  
Object invoke(Object obj,Object... args);

从这里可以看出,invoke方法有个Object类型的返回值,不过,要是调用的方法没有返回值呢,这时invoke放回null。还有,invoke第一个参数是一个对象,第二个参数是一个可变参数,实际上就是一个Object数组。

在一个类中,必须知道哪些信息才能唯一确定一个方法呢?

由于Java中可以有同名的方法,因此可以使用参数来进行区分。不过要注意,返回类型不能作为区分两个函数的依据。因此,在使用Method类中的getMethod和getDeclaredMethod方法时,要给出方法名和方法参数:

  1. Method getMethod(String name,Class... parameterTypes)  
Method getMethod(String name,Class... parameterTypes)

下面的例子给出了invoke的使用:

  1. import java.lang.reflect.*;  
  2. public class InvokeTest {  
  3.     public static void main(String[] args) {  
  4.         A a=new A();  
  5.         Class cl=a.getClass();  
  6.         try {  
  7.             Method m1=cl.getMethod("add"new Class[]{String.class,String.class});  
  8.             m1.invoke(a, new Object[]{"hello","world"});  
  9.             Method m2=cl.getMethod("add"int.class,int.class);  
  10.             m2.invoke(a, 1,2);  
  11.             Method m3=cl.getMethod("add");  
  12.             m3.invoke(a);  
  13.             Method m4=cl.getMethod("add", String.class,int.class);  
  14.             m4.invoke(null"Liu",100);  
  15.         } catch (Exception e) {  
  16.             // TODO Auto-generated catch block  
  17.             e.printStackTrace();  
  18.         }  
  19.     }  
  20. }  
  21. class A{  
  22.     public void add(){  
  23.         System.out.println("do nothing...");  
  24.     }  
  25.     public void add(int a,int b){  
  26.         System.out.println(a+b);  
  27.     }  
  28.     public void add(String a,String b){  
  29.         System.out.println(a.toUpperCase()+b.toUpperCase());  
  30.     }  
  31.     public static void add(String name,int age){  
  32.         System.out.println(name+" is "+age+" years old.");  
  33.     }  
  34. }  
import java.lang.reflect.*;
public class InvokeTest {
	public static void main(String[] args) {
		A a=new A();
		Class cl=a.getClass();
		try {
			Method m1=cl.getMethod("add", new Class[]{String.class,String.class});
			m1.invoke(a, new Object[]{"hello","world"});
			Method m2=cl.getMethod("add", int.class,int.class);
			m2.invoke(a, 1,2);
			Method m3=cl.getMethod("add");
			m3.invoke(a);
			Method m4=cl.getMethod("add", String.class,int.class);
			m4.invoke(null, "Liu",100);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
class A{
	public void add(){
		System.out.println("do nothing...");
	}
	public void add(int a,int b){
		System.out.println(a+b);
	}
	public void add(String a,String b){
		System.out.println(a.toUpperCase()+b.toUpperCase());
	}
	public static void add(String name,int age){
		System.out.println(name+" is "+age+" years old.");
	}
}

这里定义了一个简单的类A,里面有四个方法名相同的方法add,其中一个是静态方法。这四个方法的返回类型都是void,因此可以通过参数类型进行区分。在测试代码中我们使用getMethod方法获得add方法,其中第一个参数都是add,第二个参数给出参数类型。第二个参数是一个可变参数,即 一个数组,有两种方法给出这个可变参数。

第一种,构造一个Class数组:

  1. Method m1=cl.getMethod("add"new Class[]{String.class,String.class});  
Method m1=cl.getMethod("add", new Class[]{String.class,String.class});

还可以直接列出参数,有几个就列出几个,用逗号(,)分隔:

  1. Method m2=cl.getMethod("add"int.class,int.class);  
Method m2=cl.getMethod("add", int.class,int.class);

获得Method对象后,就可以调用invoke执行这个方法了。invoke有两个参数,第一个参数给出包含这个方法的对象,第二个参数也是一个可变参数,即传递给要执行的方法的参数。对给出参数可以使用上面两种方法中的一种。

这里要注意的是,对于静态方法,没有包含它的对象,这时第一个参数置为null即可。

运行结果如下:

HELLOWORLD
3
do nothing...
Liu is 100 years old.

7 反射与泛型

我们知道,集合中的泛型使得一个集合中只能保存一种类型数据。比如ArrayList<String>只能保存String类型,如果代码中有add(20)就会报错,因为20是int类型,不能转换成String。那么泛型是在哪个阶段起作用呢?

泛型的使用在编写代码中起到了一种类型检查的作用。我们知道,泛型只是编译器的功能,Java虚拟机并没有泛型,也就是说,是不是如果绕过编译,就能在泛型中添加别的类型的数据呢?比如在ArrayList<String>中添加int数据。

下面的代码演示了这种情况:

  1. import java.lang.reflect.*;  
  2. import java.util.ArrayList;  
  3. public class MethodDemo {  
  4.     public static void main(String[] args) {  
  5.         ArrayList list=new ArrayList();  
  6.         ArrayList<String> list1=new ArrayList<>();  
  7.                 System.out.println(list.getClass()==list1.getClass());  
  8.     }  
  9. }  
import java.lang.reflect.*;
import java.util.ArrayList;
public class MethodDemo {
	public static void main(String[] args) {
		ArrayList list=new ArrayList();
		ArrayList<String> list1=new ArrayList<>();
                System.out.println(list.getClass()==list1.getClass());
	}
}

上面的代码定义了两个ArrayList,其中list不是泛型,list1是泛型。我们首先检查list和list的类类型是否相同。结果如下:

true

说明在编译阶段是去泛型化的。

接下来修改代码如下:

  1. import java.lang.reflect.*;  
  2. import java.util.ArrayList;  
  3. public class MethodDemo {  
  4.     public static void main(String[] args) {  
  5.         ArrayList list=new ArrayList();  
  6.         ArrayList<String> list1=new ArrayList<>();  
  7.           
  8.         list.add(10);  
  9.         list.add("hello");  
  10.           
  11.         list1.add("world");  
  12.         //list1.add(20);ERROR:can't add int to ArrayList<String>  
  13.           
  14.           
  15.         try {  
  16.             Method m=list1.getClass().getDeclaredMethod("add", Object.class);  
  17.             m.invoke(list1, 20);  
  18.             System.out.println(list1.size());  
  19.             System.out.println(list1);  
  20.             for(String s:list1){  
  21.                 System.out.print(s+" ");  
  22.             }  
  23.         } catch (Exception e) {  
  24.             // TODO: handle exception  
  25.             e.printStackTrace();  
  26.         }  
  27.     }  
  28. }  
import java.lang.reflect.*;
import java.util.ArrayList;
public class MethodDemo {
	public static void main(String[] args) {
		ArrayList list=new ArrayList();
		ArrayList<String> list1=new ArrayList<>();
		
		list.add(10);
		list.add("hello");
		
		list1.add("world");
		//list1.add(20);ERROR:can't add int to ArrayList<String>
		
		
		try {
			Method m=list1.getClass().getDeclaredMethod("add", Object.class);
			m.invoke(list1, 20);
			System.out.println(list1.size());
			System.out.println(list1);
			for(String s:list1){
				System.out.print(s+" ");
			}
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}

直接在list1中使用add添加一个int数据不能成功,编译器会报错。之后使用Method中的getDeclaredMethod方法获得ArrayList<String>中的add方法,然后使用invoke调用这个方法添加一个int数据。这时,编译器没有报错。打印list1的长度和内容,检查是否添加成功。最后使用for循环遍历list1中的内容。结果如下:


长度为2,内容也正确,说明我们跳过了编译阶段的类型检查,在运行时成功添加了int类型数据。不过,使用for循环遍历是报错了,因为20不是String类型数据。

通过这个例子,可以知道,泛型会在编译时去泛型化。同时可以通过反射在运行阶段添加数据。

8 使用java.lang.reflect包中的Array类编写泛型数组

java.lang.reflect包中的Array类可以动态创建数组。比如,可以将这个特性应用到Array类的copyOf方法实现中。

如果要给一个Student[]数组复制,可以先将Student[]转换为Object[]数组,比如这样:

  1. public static Object[] badCopyOf(Object[] a,int newLength){  
  2.     Object[] newArray=new Object[newLength];  
  3.     System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength));  
  4.     return newArray;  
  5. }  
public static Object[] badCopyOf(Object[] a,int newLength){
	Object[] newArray=new Object[newLength];
	System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength));
	return newArray;
}
不过,这样的话,返回的类型是Object[],而Object[]不能转化为Student[]类型。

为了编写通用的方法,需要创建一个与原数组类型相同的新数组。为此,需要java.lang.reflect包中的Array类中的一些方法。

Array类中有一个newInstance方法,能够创建新数组。这个方法要两个参数,一个是数组的元素类型,另一个是数组的长度:

  1. Object newArray=Array.newInstance(componentType,newLength);  
Object newArray=Array.newInstance(componentType,newLength);
这样,就需要获得数组元素的类型和数组的长度。使用Array类中的getComponentType方法可以获得元素的类型,使用Array类中的getLength方法可以获得数组的长度。

下面是完整的代码:

  1. import java.lang.reflect.*;  
  2. import java.util.Arrays;  
  3. public class CopyOfTest {  
  4.     public static void main(String[] args) {  
  5.         int[] a={1,2,3};  
  6.         a=(int[])goodCopyOf(a,10);  
  7.         System.out.println(Arrays.toString(a));  
  8.           
  9.         String[] b={"Tom","Dick","Harry"};  
  10.         b=(String[])goodCopyOf(b,10);  
  11.         System.out.println(Arrays.toString(b));  
  12.           
  13.         b=(String[])badCopyOf(b,10);  
  14.     }  
  15.     public static Object[] badCopyOf(Object[] a,int newLength){  
  16.         Object[] newArray=new Object[newLength];  
  17.         System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength));  
  18.         return newArray;  
  19.     }  
  20.     public static Object goodCopyOf(Object a,int newLength){  
  21.         Class cl=a.getClass();  
  22.         if(!cl.isArray())return null;  
  23.         Class componentType=cl.getComponentType();  
  24.         int length=Array.getLength(a);  
  25.         Object newArray=Array.newInstance(componentType, newLength);  
  26.         System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));  
  27.         return newArray;  
  28.     }  
  29. }  
import java.lang.reflect.*;
import java.util.Arrays;
public class CopyOfTest {
	public static void main(String[] args) {
		int[] a={1,2,3};
		a=(int[])goodCopyOf(a,10);
		System.out.println(Arrays.toString(a));
		
		String[] b={"Tom","Dick","Harry"};
		b=(String[])goodCopyOf(b,10);
		System.out.println(Arrays.toString(b));
		
		b=(String[])badCopyOf(b,10);
	}
	public static Object[] badCopyOf(Object[] a,int newLength){
		Object[] newArray=new Object[newLength];
		System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength));
		return newArray;
	}
	public static Object goodCopyOf(Object a,int newLength){
		Class cl=a.getClass();
		if(!cl.isArray())return null;
		Class componentType=cl.getComponentType();
		int length=Array.getLength(a);
		Object newArray=Array.newInstance(componentType, newLength);
		System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
		return newArray;
	}
}

结果如下:


注意,应该将goodCopyOf的参数声明为Object类型,而不要声明成Object[]。因为正数数组类型int[]可以转换成Object,但不能转换成对象数组。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值