Chinaunix首页 | 论坛 | 博客
  • 博客访问: 89836
  • 博文数量: 26
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1030
  • 用 户 组: 普通用户
  • 注册时间: 2013-07-25 10:47
文章分类
文章存档

2013年(26)

我的朋友

分类: Java

2013-09-29 11:59:03


Technorati 标记: Field,Method,Constructor,java

    承接前篇对java Reflection的介绍 :

    Class对象的获取 , 详细讲解了四种获取Class对象的方法;

    获取class对象信息 , 详细讲解了如果获取class对象的信息,包括class对象的信息,class对象的成员信息。class对象的信息主要有修饰符,泛型参数,所实现的接口,继承的路径与及注解信息。class对象的成员信息主要有成员变量field,函数方法与构造器。

    这一接将继续探讨如何操纵Member。java Reflection 定义了一个接口Member,而它的实现就包括了Field、Method、Constructor。这一次将探讨如何如何使用这三个实现和其相关的API;
一、Field

    field包括了类型(type)和值(value)。Field提供提供相关的方法访问field类型和获取/设置filed的值。

    (1)访问field的类型

    曾经谈过,在java里,一个field要不就是8种基本的数据类型之一,要不就是一个引用。基本数据类型(boolean,byte,short,int,float,long,double,char);“引用”指的就是直接或者间接继承Object的类,同时还包括了接口(interface)、数组(arrays)、枚举(enum)。下面给出一个Demo,FieldDemo,演示如何获取field的类型。

public class FieldDemo {
    public int id = 1;
    public String name = "TianYa";
    public double[] d;
    public List lo;
    public T val;
    

    public static void main(String[] args) throws Exception{
        Class c = FieldDemo.class;
        Field field = c.getField("id");
//        Field field = c.getField("name");
//        Field field = c.getField("d");
//        Field field = c.getField("lo");
//        Field field = c.getField("val");
        
        out.println("field 类型 " + field.getType());
        out.println("field 泛型:" + field.getGenericType());;
    }
}


    使用field.getType()即可获取field类型,类似的field.getGenericType()可获取field的泛型类型,不同的是,当field不具有泛型参数时,getGenericType()会返回其field类型。以下是部分输出:

imageimage

imageimage

    大致的输出应该没有太多的疑问,在这里还要注意一下,“T”输出的field类型:Object,这是因为java 泛型的类型擦出导致的—在编译的时候不包含泛型信息,泛型类型信息将在编译处理是被擦除,这个过程即类型擦除。可以查阅相关资料,了解更多类型擦除的信息。

    在这里,可能还会有一个疑问,就是在FieldDemo里的field都是public的,那是否可以为private的??当然是可以的,可以使用filed.getDeclaredField(String name)获取,private修饰的field,更多的信息可以参考获取class对象信息。

    (2)获取/设置field值

    对于一个实例对象,可以通过反射来设置其值。在非必要的情况下,尽量不要使用反射来设置对象的值,因为这违反类的设计原则(封装性)。不过若你想深入了解java,反射是必须掌握的基础,因为其在各种架构里都会用到。

    Field对象提供了如下两组方法读取或设置Field值。

    getXxx(Object obj):获取obj对象该字段的属性值。此处的Xxx对象8个基本类型,如果该属性是引用类型,则取消get后面的Xxx。
    setXxx(Object obj, Xxx val):将obj对象该字段设置成val值。此处的Xxx对象8个基本类型,如果该属性是引用类型,则取消set后面的Xxx。

    在修改一些private field、没有访问权限或者final的field时,需要调用setAccessible(true)方法取消java语言的访问权限检查,否则会抛出IllegalAccessException。

    Demo People演示了如何去修改基本数据类型,数组,枚举的值。

enum Sex{MALE,FEMALE}

public class People {
    private int id = 1;
    private String name = "zhang san";
    private Sex sex = Sex.MALE;
    private String[] friends = {"李四","王五"};
    
    public static void main(String[] args) throws Exception{
        People p = new People();
        Class c = p.getClass();
        //-------------------------------
        Field Id = c.getDeclaredField("id");
        System.out.println("id原来的值:" + p.id);
        Id.setInt(p, 2);
        System.out.println("id改动后的值:" + p.id);
        //---------------------------------
        Field gender = c.getDeclaredField("sex");
        System.out.println("sex原来的值:" + p.sex);
        gender.set(p, Sex.FEMALE);
        System.out.println("sex改动后的值:" + p.sex);
        //----------------------------------
        Field fri = c.getDeclaredField("friends");
        System.out.println("friends原来的值:" + Arrays.asList(p.friends));
        String[] newFriends = {"赵六","小七"};
        fri.set(p, newFriends);
        System.out.println("friends改动后的值:" + Arrays.asList(p.friends));
    }
}


    其输出如下:

image

    细心的你,也许会发现,在People里修改了private 修饰的field(id,name…),可却没有抛出异常??为什么呢??不是说修改private filed时需要取消访问权限吗??问题出现在,把main函数也写在People类里了,如果把main函数写在一个类,还可以直接使用类对象访问field值(如People类里的p.id,而不需要调用get/set方法来访问。java定义里一样有:不能通过类对象直接访问field。),在此反射也是一样的道理。看一下FieldException ,类外的调用是需要使用方法setAccessable(true)来取消访问检查权限的,否则否抛出illegalAccessException。

public class FieldExceptionTest {
    private boolean b = true;

    public boolean isB() {
        return b;
    }
}

 

public class Test {
    
    public static void main(String[] args) throws Exception{
        FieldExceptionTest ft = new FieldExceptionTest();
        Class c = ft.getClass();
        Field f = c.getDeclaredField("b");
//         f.setAccessible(true); //取消访问权限
        f.setBoolean(ft, Boolean.FALSE); //IllegalAccessException
        System.out.println(ft.isB());
    }
}


    不调用setAccessible(true)会抛出illegalAccessException。

 

    (3)检索和解释field修饰符

    除了可以获取field的类型,还可以检索出field的修饰符,field修饰符主要包括:public,protected,private,transient,volatile,static,final和注解,需要了解修饰符更多的信息,可以参考java修饰符概述。

    可以使用field.getModifiers()获取field修饰符,而getModifiers()返回是特定的字节码。需要使用Modifier.toString()来解释以获取string类型的修饰符。想了解Modifier.tuString(int mod)的实现的,可以点击 这里,。Demo FieldModifier演示如何获取指定修饰符的field,同时也增加了一些判断,field.isSynthetic()判断field是否由编译器生成的,field.isEnumConstant()可判断field是否为枚举常量。

enum Gender {
    MALE, FEMALE
}

public class FieldModifier {
    private int id;
    public String name;

    public static void main(String[] args) {
        Class c = Gender.class;
        int searchMods = 0x0;
        //指定修饰符
        String[] midifiers = { "public" };
        for (int i = 0; i < midifiers.length; i++) {
            searchMods |= modifierFromString(midifiers[i]);
        }

        Field[] flds = c.getDeclaredFields();
        out.println("包含指定修饰符 " + Modifier.toString(searchMods) + "的field:");
        boolean found = false;    //判断是否找到指定修饰符的field
        for (Field f : flds) {
            int foundMods = f.getModifiers();
            //要求包含所有指定修饰符
            if ((foundMods & searchMods) == searchMods) {
                out.println(f.getName() + " synthetic?= " + f.isSynthetic() + "; 枚举常量: " + f.isEnumConstant());
                found = true;
            }
        }

        if (!found) {
            out.println("没有找到指定的field");
        }

    }
    
    private static int modifierFromString(String s) {
        int m = 0x0;
        if ("public".equals(s))
            m |= Modifier.PUBLIC;
        else if ("protected".equals(s))
            m |= Modifier.PROTECTED;
        else if ("private".equals(s))
            m |= Modifier.PRIVATE;
        else if ("static".equals(s))
            m |= Modifier.STATIC;
        else if ("final".equals(s))
            m |= Modifier.FINAL;
        else if ("transient".equals(s))
            m |= Modifier.TRANSIENT;
        else if ("volatile".equals(s))
            m |= Modifier.VOLATILE;
        return m;
    }
    
}


    其输出如下:

image

   在FieldModifier里作如下修改,找出FieldModifier里private修饰的field

Class c = FieldModifier.class;
//指定修饰符
String[] midifiers = { "private" };


    输出如下:

image

    类似的有:

image

image

    再看如下修改:

Class c = Gender.class;
//指定修饰符
String[] midifiers = { "private","static","finale" };



    其输出如下:

image

    发现输出了一些并没有在Gender定义的field,这些都是由编译器生成的,供运行时用的field

 

二、Method

    (1)在一个方法里,可以包含方法名,修饰符,返回类型,参数,注释符,或者异常。相对应地,java.lang,reflect.Method提供了各种API来获取这些信息,同时也提供了调用此方法的函数invoke()。Demo MethodTest演示了如何获取Method的信息:

public class MethodTest {
    public static void main(String[] args) throws Exception {
        Class c = Class.forName("java.io.PrintStream");
        Method[] allMethods = c.getDeclaredMethods();
        for (Method m : allMethods) {
            if (!m.getName().equals("printf")) {
                continue;
            }
            out.println("函数的泛型字符串表示:");
            out.println(m.toGenericString());//也可使用toString()

            out.println("返回值类型(泛型):");
            out.println(m.getReturnType());
            out.println(m.getGenericReturnType() + "(泛型)");

            Class[] pType = m.getParameterTypes();
            Type[] gpType = m.getGenericParameterTypes();
            out.println("参数:");
            for (int i = 0; i < pType.length; i++) {
                out.println(pType[i]);
                out.println(gpType[i] + "(泛型)");
            }

            Class[] eType = m.getExceptionTypes();
            Type[] geType = m.getGenericExceptionTypes();
            out.println("异常:");
            for (int i = 0; i < eType.length; i++) {
                out.println(eType[i]);
                out.println(geType[i] + "(泛型)");
            }

        }
    }
}


    首先来解释一下,方法里带有”Generic”字样的,表示此方法以泛型的格式返回,如果不存在泛型,则返回无泛型的形式。MethodTest的输出如下:

image

    在MethodTest是获取函数名为“printf”的函数,如果printf被重载了,则会通过for循环输出多个重载的函数;如果需要获取指定的函数,可以调用方法

getDeclaredMethod(String name, Class... parameterTypes) ,根据指定的参数类型返回指定的函数。补充说明一下的是,Method.getGenericException可以返回泛型异常,不过这个方法很少会用到。
    (2)调用Method方法

    java reflection为调用class对象的方法提供invoke函数,其方法签名如下:

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


    第一个参数obj为调用方法对应的实例对象,如果调用的方法为静态的,obj应该设置为null;args为调用方法所需要的参数。下面演示一下如何调用参数固定的方法,调用参数不固定的方法,和如何调用静态方法。(参数不固定的方法,会将参数封装成一个数组)

    假设有如下一个类MyClass

public class MyClass {
    
    public void print(String text) {
        System.out.println(text);
    }

    public void printGreeting() {
        System.out.println("how are you ?");
    }

    public double average(double[] values) {
        double sum = 0.0;
        for (double n : values) {
            sum += n;
        }
        return sum/values.length;
    }
    
    public static int sum(int[] values) {
        int sum = 0;
        for (int i : values) {
            sum += i;
        }
        return sum;
    }
}


    调用MyClass对象里的print()和printGreeting()方法(参数固定),如下:

MyClass object = new MyClass();
Class c = object.getClass();
//调用有参数函数
Method print = c.getDeclaredMethod("print", String.class);
print.invoke(object, "invoke method successfully.");
//调用无参数函数
Method printGreeting = c.getDeclaredMethod("printGreeting");
printGreeting.invoke(object);


    调用MyClass对象里的average()方法(参数不定),同时获取函数的返回值,如下:

//调用参数不固定的函数
Method average = c.getDeclaredMethod("average", double[].class);
double aveVal = (Double) average.invoke(object, new double[]{3.5,4.2,5.4,6.7});
System.out.println("average : " + aveVal);


    调用MyClass的静态方法sum(),并获取其返回值,如下:

//调用静态函数
Method staticMethod = c.getDeclaredMethod("sum", int[].class);
int sum = (Integer)staticMethod.invoke(null, new int[]{1,3,5,7});
System.out.println("sum :" + sum);


    所有调用的输出如下:

image

    有时候可能会遇到函数参数为泛型的情况,那又该如何调用呢??我们假设有这样泛型参数,方法如下:

public  print(T sequence){
    System.out.println(sequence)
}


    反射调用的代码如下:

Method print = clazz.getMethod(print, CharSequence.class);
print.invoke(instance);


    (3)获取与解释Method的修饰符

    一个方法的可以包含的修饰符有:public, protected, private,static,final,abstract,synchronized,native,strictfp和注解。如需了解各种修饰符的作用,可以点击 这里。

    获取一个方法的修饰符非常简单,如上MyClass,我们回去printStatic()的修饰符:

Method staticMethod = c.getDeclaredMethod("sum", int[].class);
System.out.println("修饰符:" + Modifier.toString(staticMethod.getModifiers()));


    其输出如下:修饰符: public static。

    顺带提一下:修饰符的输出是按一个顺序输出的:首先是public,protected或public,剩下的修饰符会按如下顺序输出:abstract,static,final,transient,volatile,synchronized,native,strictfp,interface。

   不过有时候仅仅是获取一个方法的修饰符是不够的,往往伴随着判断这个方法isSynthetic(由编译器生成),isVarArgs(参数数量是否可变),isBridge(是否是桥方法),桥方法指由编译器生成来支持泛型接口的方法。如下:

public class MethodModifierTest {
    
    public static void main(String[] args) throws ClassNotFoundException {
        Class c = Class.forName("java.lang.Class");
        Method[] allMethods = c.getDeclaredMethods();
        for (Method m : allMethods) {
            if (!m.getName().equals("getConstructor")) {
                continue;
            }
            out.println("函数泛型字符窜表示:\n" + m.toGenericString());
            out.println("修饰符: " + Modifier.toString(m.getModifiers()));
            out.println("synthetic?= " + m.isSynthetic() + "; 参数任意?: " + m.isVarArgs() + "; 桥方法?: " + m.isBridge());
        }
    }
}


输出如下:

image

    在此注意的是:在判断getConstructor()参数是否任意时,isVarArgs()返回true,说明其形式应该如下:

public Constructor getConstructor(Class... parameterTypes)


    而不是这样:

public Constructor getConstructor(Class [] parameterTypes)


    在MethodModifierTest,是会输出所有重载函数的,因为方法名相同的函数可以有多个(重载)。如,现在加载java.lang.Object,方法名为wait,改动如下

Class c = Class.forName("java.lang.Object");
if (!m.getName().equals("wait"))


    输出如下:(输出了wait的三种重载形式)

image

    再看如下的改动:

Class c = Class.forName("java.lang.String");
if (!m.getName().equals("compareTo"))


    其输出如下:

image

    在java.lang.String里只声明了“public int compareTo(String anotherString);”,可是却输出了另外一个由编译器生成的桥接方法。出现这种情况,是因为String实现了参数化接口Comparable。在类型擦除的状态下,Comparable.compareTo里的参数java.lang.Object会转化为java.lang.String。类型擦除后,java.lang.String不在与java.lang.Object匹配,也就没有重写Comparable.compareTo方法,而这是会引起编译错误的。而桥方法的使用就是为了防止这种编译错误的出现。
三、Constructor

    构造函数和Method基本一致,除了不包含返回值,构造函数也包括了修饰符,方法名,参数,注释符和异常。java.lang.reflect.Constructor提供各种API来获取相关信息,各个方法的使用和Method的类似,不在重复叙述。假设有一个类Person,如下:

public class Person {
    private int id;
    private String name;
    public Person() {
    }
    public Person(int id) {
        this.id = id;
    }
    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }
}


    如下代码可以找出包含某种类型(String)的参数的构造函数:

public static void main(String[] args) {
        //构造函数所需要包含的参数类型
        Class cArg = String.class;

        Class c = Person.class;
        Constructor[] allConstructors = c.getDeclaredConstructors();
        for (Constructor ctor : allConstructors) {
            Class[] pType = ctor.getParameterTypes();
            for (int i = 0; i < pType.length; i++) {
                if (pType[i].equals(cArg)) {
                    out.println("构造函数泛型表示:\n" + ctor.toGenericString());
                    //获取参数的泛型表示
                    Type[] gpType = ctor.getGenericParameterTypes();
                    for (int j = 0; j < gpType.length; j++) {
                        char ch = (pType[j].equals(cArg) ? '*' : ' ');
                        out.format("%7c%s[%d]: %s%n", ch, "GenericParameterType", j, gpType[j]);
                    }
                    break;
                }
            }
        }
    }


    其输出如下:

image_thumb[1]

 

    接着演示一下如何通过反射获取指定构造函数来创建新的对象,如下:

public static void main(String[] args) throws Exception {
        Class c = Person.class;
        //获取默认构造函数
        Constructor c1 = c.getConstructor();    
        //获取参数为String的构造函数
        Constructor c2 = c.getConstructor(String.class);
        //获取参数为int,String的构造函数
        Constructor c3 = c.getConstructor(int.class,String.class);
        //使用反射构造新的Person对象
        Person p1 = (Person) c1.newInstance();
        Person p2 = (Person) c2.newInstance("张三");
        Person p3 = (Person) c3.newInstance(1,"张三");
    }


    注意的是,无参函数newInstance()要求对应的类必须提供无参构造函数。

 

   关于如何获取与解释构造函数的修饰符,其方法的使用与Method类似,一样可以判断构造函数isSynthetic(),isVarArgs()。在此不再叙述,详情可参考Method类的介绍。

阅读(1803) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~