__5_6 反射

1)Class类

程序在运行起见,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。保存这些信息的类被称为Class,Object类中的getClass()方法会返回一个Class类型的实例。
最常用的Class方法是getName,这个方法将返回类的名字(包括包名),可以调用静态方法forName(String)获得类名字符串对应的Class对象。forName方法只有在参数是类名或接口名时才能执行,否则将抛出异常。
在启动时,包含main方法的类将被加载,它会加载所有需要的类,这些被加载的类又要加载它们需要的类,以此类推。
T为任意Java类型,T.class将代表匹配的类对象。例如

Class cl1 = Date.class;//if you import java.util.*;
        Class cl2 = int.class;
        Class cl3 = Double[].class;

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

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

使用newInstance()快速创建一个类的实例,该方法使用默认的构造器,如果这个类没有默认的构造器,会抛出一个异常。
将forName与newInstance配合起来使用,可以根据类名创建一个对象。

String s = "java.util.Date";
    Object m = Class.forName(s).newInstance();

如果需要以这种方式向希望按名称创建的类的构造器提供参数,就需要使用Constructor类中的newInstance方法。

2)捕获异常

3)利用反射分析类的能力

在java.lang.reflect包中有三个类Field,Method和Constructor分别描述域,方法和构造器。Class类的getFields,getMethods和getConstructors方法将分别返回类提供的public域,方法和构造器数组,其中包括超类的公有成员。Class类的getDeclareFields,getDeclareMethods和getDeclaredConstructors方法将分别返回类中声明的全部域,方法和构造器,其中包括私有和搜保护成员,但不包括超类的成员。
练习1,读取用户输入的字符串参数,第一个参数为类名,第二个参数为”all”时打印类中全部成员,否则打印该类和其父类的共有成员。两个参数均为”quit”时退出。

package learn.test.object;

import java.lang.reflect.*;
import java.util.*;

public class reflectTest 
{
    public static void main(String[] args)
    {
        ObjectBasicInfoPrinter aPrinter = new ObjectBasicInfoPrinter();
        Scanner in = new Scanner(System.in);
        String className = "";
        String param = "";
        boolean all = false;
        while(true)
        {
            System.out.println("Please input class name");
            String[] input = in.nextLine().trim().split(" ");
            className = input[0].trim();

            if(input.length > 1)
                param = input[1].trim();
            else
                param = "";

            if(param.equalsIgnoreCase("quit") && className.equalsIgnoreCase("QUIT"))
                break;

            if(param.equalsIgnoreCase("aLl"))
                all = true;
            else
                all = false;

            try 
            {
                Class theClass = Class.forName(className);
                aPrinter.printInfo(theClass, all);
            }    
            catch (ClassNotFoundException e) 
            {
                System.out.println("no such class");
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}


class ObjectBasicInfoPrinter
{
    public int printInfo(Class aClass, boolean all)
    {
        if(aClass == null)
            return -1;

        System.out.println(getTitle(aClass));
        System.out.println("{");
        System.out.println(getConstructors(aClass, all));
        System.out.println(getMethods(aClass, all));
        System.out.println(getFields(aClass, all));
        System.out.println("}\n");
        return 0;
    }

    public String getTitle(Class aClass)
    {
        if(aClass == null)
            return "";
        Class superClass = aClass.getSuperclass();
        StringBuilder sb = new StringBuilder();

        int modifiers = aClass.getModifiers();
        String modifierStr = Modifier.toString(modifiers);
        sb.append(modifierStr).append(" class ").append(aClass.getName());

        if(superClass != null && superClass != Object.class)
            sb.append(" extends ").append(superClass.getName());
        return sb.toString();

    }

    public String getConstructors(Class aClass, boolean all)
    {
        if(aClass == null)
            return "";
        Constructor[] constructors = new Constructor[0];
        if(all)
        constructors = aClass.getDeclaredConstructors();
        else
            constructors = aClass.getConstructors();
        StringBuilder sb = new StringBuilder("//Constructors of class " + aClass.getName() + "\n");

        for(Constructor con : constructors)
        {
            sb.append("\t");
            String modifierStr = Modifier.toString(con.getModifiers());
            if(!modifierStr.equals(""))
                sb.append(modifierStr).append(" ");
            sb.append(con.getName());
            Class[] params = con.getParameterTypes();
            sb.append("(");
            for(int i = 0; i < params.length; i++)
            {
                if(i != 0)
                    sb.append(", ");
                sb.append(params[i].getName());
            }
            sb.append(");\n");
        }
        return sb.toString();
    }

    public String getFields(Class aClass, boolean all)
    {
        if(aClass == null)
            return "";
        StringBuilder sb = new StringBuilder("//Fields of class " + aClass.getName() + "\n");

        Field[] fields = new Field[0];
        if(all)
        {
            fields = aClass.getDeclaredFields();
        }
        else
        {
            fields = aClass.getFields();
        }
        for(Field aField : fields)
        {
            sb.append("\t");
            String modifierStr = Modifier.toString(aField.getModifiers());
            if(!modifierStr.equals(""))
                sb.append(modifierStr).append(" ");
            sb.append(aField.getType()).append(" ");
            sb.append(aField.getName()).append(";\n");
        }
        return sb.toString();
    }

    public String getMethods(Class aClass, boolean all)
    {
        if(aClass == null)
            return "";
        Method[] methods = new Method[0];

        if(all)
            methods = aClass.getDeclaredMethods();
        else
            methods = aClass.getMethods();

        StringBuilder sb = new StringBuilder("//Methods of class " + aClass.getName() + "\n");
        for(Method aMethod : methods)
        {
            sb.append("\t");
            String modifierStr = Modifier.toString(aMethod.getModifiers());
            sb.append(modifierStr).append(" ");
            sb.append(aMethod.getReturnType().toString()).append(" ");
            sb.append(aMethod.getName()).append("(");
            Parameter[] params = aMethod.getParameters();
            for(int i=0; i<params.length; i++)
            {
                if(i!=0)
                    sb.append(", ");
                sb.append(params[i].getType());
            }
            sb.append(");\n");
        }
        return sb.toString();
    }
}
4)在运行时使用反射分析对象
Field类中的get与set方法

obj使某个包含f域的类的对象,f.get(obj)将返回一个对象,其值为obj域的当前值。例如

Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989);
    Class cl = harry.getClass();
    Field f  = cl.getDeclaredField("name");
    Object v = f.get(harry);
    //the value of the name field of the harry object
    //i.e., the String object "Harray Hacker"

若name为私有域,调用f.setAccessible(true);访问之。也可使用AccessibleObject.setAccessible(fileds,true);访问。
调用f.set(obj,value)可以将obj对象的f域设置成新值。
练习2,输出ArrayList中的数据。(仿照书中示例程序编写,但细节处并不完善)

package learn.test.object;

import java.lang.reflect.*;
import java.util.ArrayList;

public class ToStringTest 
{
    public static void main(String[] args)
    {
        ArrayList<Integer> squares = new ArrayList<Integer>();
        for( int i=1; i<=5; i++)
            squares.add(i*i);
        System.out.println(new ToStringTool().toString(squares));
    }
}

class ToStringTool
{
    public String toString(Object obj)
    {
        if(obj == null)
            return "null";
        if(visitedObj.contains(obj))
            return "...";
        StringBuilder sb = new StringBuilder();
        Class objClass = obj.getClass();

        sb.append(objClass.getName() + "{\n");

        if(objClass == String.class)
            return (String)obj;
        if(objClass.isPrimitive())
            return "" + obj;

        if(objClass.isArray())
        {
            //Class.getComponentType();
            sb.append(objClass.getComponentType() + " [");
            int len = Array.getLength(obj);
            for(int i=0; i<len; i++)
            {
                sb.append("\nELEMENT " + i + " = ");
                //Array.get(obj,i);
                Object subObj = Array.get(obj,i);
                if(subObj != null && objClass.getComponentType().isPrimitive())
                    sb.append(subObj);
                else
                    sb.append(toString(subObj));
                if(i != len - 1)
                    sb.append(";");
            }
            sb.append("]}\n");
            return sb.toString();
        }

        Field[] fields = objClass.getDeclaredFields();
        //AccessibleObject.setAccessible(Field[], boolean);
        AccessibleObject.setAccessible(fields, true);
        int j = 0;
        for(int i = 0; i < fields.length; i++)
        {
            if(j > 0)
                sb.append(", \n");
            try
            {
                if(!Modifier.isStatic(fields[i].getModifiers()))
                {

                    Object val = fields[i].get(obj);
                    if(!visitedObj.contains(val))
                    {
                        visitedObj.add(val);
                        //Field.get方法自动打包基本类型,故不能用val.getClass判断
                        //Field.getType();可获得基本类型的Class对象
                                                if(fields[i].getType().isPrimitive())
                            sb.append(fields[i].getName()+ " = " + val);
                        else
                            sb.append(fields[i].getName()+ " = " + toString(val));
                    }
                    else
                    {
                        sb.append("...");
                    }
                    j++;
                }
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
        }
        sb.append("}\n");

        return sb.toString();
    }

    ArrayList<Object> visitedObj = new ArrayList<Object>();
}
5)使用反射编写泛型数组代码

java数组会记住每个元素的类型,即创建数组时new表达式中使用的元素类型。将一个Employee[]临时地转换成Object[]数组,然后再把它转换回来是可以的,但一个从开始就是Object[]的数组却永远不能转换成Employee[]数组,故下例不能达到拓展数组大小的效果。

static Object[] badArrayGrow(Object[] a) //not useful
    {
        int newLength = a.length * 11 / 10 + 10;
        Object[] newArray = new Object[newLength];
        System.arraycopy(a, 0, newArray, 0, a.length);
        return newArray;
    }

使用Object newArray = Array.newInstance(componentType, newLength);来构造新数组。如下例。其中,整形数组类型int[]可以被转换成Object,但不能转换为对象数组,如Integer[]。故传入参数为Object类型。

static Object goodArrayGrow(Object a) //useful
    {
        Class cl = a.getClass();
        if(!cl.isArray())
            return null;
        Class componentType = cl.getComponentType();
        int length = Array.getLength(a);
        int newLength = length * 11 / 10 + 10;
        Object newArray = Array.newInstance(componentType, new Length);
        System.arraycopy(a, 0, newArray, 0, length);
        return newArray;
    }

以上例子只为展示泛型数组的工作过程,实际希望扩大数组,可利用Arrays类的copyOf方法。

Employee[] a = new Employee[100];
    ...
    // array is full
    a = Arrays.copyOf(a, a.length * 11 / 10 + 10);

另,使用java.lang.reflect.Array类中的static xxx getXXX(Object array, int index);和对应的set方法。

6)方法指针

Method类有一个invoke方法,允许调用包装在当前Method对象中的方法。其签名为Object invode(Object obj, Object... args);第一个参数是隐式参数,其余的对象提供了显式参数。对于静态方法,第一个参数可以被忽略,即传入null。如果返回类型是基本类型,invoke方法会返回包装器类型,可使用

double s = (Double) m2.invoke(harry);

通过自动拆包来完成类型转换。

getMethod的方法签名是Method getMethod(String name, Class... parameterTypes)对于Java SE 5.0前的版本,需要传入数组或null作为显式参数。

invoke的参数和返回类型都是Object型,这意味着进行多次类型转换,这会使编译器错过检查代码的机会,错误会到测试阶段才能被发现,找到并改正它们会更加困难。使用反射获得方法指针的代码要比仅仅直接调用方法明显慢一些。因此,建议仅在必要的时候才使用Method对象,最好使用接口和内部类。

8)继承设计的技巧

1)将公共操作和域放在超类
2)不要使用受保护的域
3)使用继承实现”is-a”关系
4)除非所有继承的方法都有意义,否则不要使用继承
5)在覆盖方法时不要改变预期的行为
6)使用多态而非类型信息,如果可以,将需要类型判断后执行的相同概念的分支放在两个类的超类或接口中。如下例

//原代码
    if( x is of type 1)
        action1(x);
    else if (x is of type 2)
        action2(x);
    //修改为
    x.action();

7)不要过多的使用反射