反射---java王国的武器大师

       反射是java高级开发中的核心部分,它是一把双刃剑,用得好勇猛无敌,用不好自损一百。本着对知识实例化的学习态度,本文将反射比作武器大师,他能通过武器本身鉴定武器的类别、属性拥有的技能等,下面开始我们的武器鉴别教程。

反射的定义

      反射是JAVA程序在运行过程中能够动态获取一个类的一些属性和方法,对于任意一个类对象都能够操控对象的属性和方法,这种动态获取属性方法并控制的行为就称为java反射。而在java帝国中咱们的武器大师也具有"反射"的能力,他学识渊博、经验丰富,你只要个随便给他说一种武器或是给他看一眼他就能把武器的属性和技能给你娓娓道来。那他是如何做到的呢?原来啊他的爷爷传给了他一本武器百科全书《武器葵花宝典》,上面记载有每种武器的样子、属性与技能说明等,通过这本《武器葵花宝典》咱们的鉴定大师就能轻松了解每种武器的特征了,那么在java反射中的葵花宝典是什么呢?这就是我们的Class类。

Class类--反射中的葵花宝典

Class类是java中的系统自定义类,其构造方法为私有方法,对象不能由用户创建得由java虚拟机在类加载时进行创建,它的定义大致如下:

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
    private static final int ANNOTATION= 0x00002000;
    private static final int ENUM      = 0x00004000;
    private static final int SYNTHETIC = 0x00001000;

    private static native void registerNatives();
    static {
        registerNatives();
    }

这里我们不对Class的实现细节作太多描述,我们只需要java虚拟机在加载某一个类型时,就会为其生成一个Class类的代理对象,这个对象就是程序中任意类型的访问接口,就如同葵花宝典一样,通过它我们就能拿到类型的属性、方法并且可以控制其行为,这本《武器葵花宝典》是武器大师的爷爷传给他的,那在程序中我们怎么去获得反射的葵花宝典呢?有三种方法:

1.Object.getClass()

当有某个对象实例时,可直接通过object.getClass()方法获取Class类实例,如下:

public class ReflectTest
{
    public class Gun{}

    public void test()throws Exception{
        Gun gun = new Gun();
        Class classz = gun.getClass();
    }
}
2.Class.class

如果只是知道某个具体的类型,那么通过.class方法可以获取对应的Classl类对象:

public class ReflectTest
{
    public class Gun{}

    public void test()throws Exception{
        Class classz = Gun.class;
    }
}
3.Class.forName("包名.类名")

如果既没有类型实例,也不能通过.class方法类对象,那么可以通过Class.forName()的方式获取类对象,这个在咋们的数据库驱动加载中最为常见:

public class ReflectTest
{
    public class Gun{}

    public void test()throws Exception{
        Class classz = Class.forName("basejava.annotation.ReflectTest");
    }
}

在我们找到武器在《葵花宝典》中对应的页数以后,便可以查看手册寻找武器的属性和技能了,同理通过Class的类对象就可以查找类型对应的属性和方法;

类名

我们关注一个事务的信息肯定首先关注它的名字,在java反射中也不例外,我们有下面三种方式获取一个类型的名字

1. Class.getName():    以String类型返回此Class对象所表示的实体名称

2.Class.getSimpleName();   返回源码中底层类给定的简称;

3.Class.getCanonicalName():  返回Java  Language Specialfication 中所定义的底层类的规范化名称;

第一眼读了肯定不明白咋回事,以例子来说明

public class ReflectTest
{
    public static void main(String[] args)throws Exception{
        Class classz = Class.forName("basejava.annotation.Gun");

        System.out.println("Class.getSimpleName():" + classz.getSimpleName());
        System.out.println("------------------------------------------");
        System.out.println("Class.getCanonicalName():" + classz.getCanonicalName());
        System.out.println("-------------------------------------------");
        System.out.println("Class.getName():" + classz.getName());
    }
}
输出如下:
Class.getSimpleName():Gun
        ------------------------------------------
        Class.getCanonicalName():basejava.annotation.Gun
        -------------------------------------------
        Class.getName():basejava.annotation.Gun

通过上面的例子可以发现:getSimpleName就是咋们的基本类名,getCanonicalName()和getName()是会带上包结构的,但是它们怎么是一样的呢,在引用类型中它们确实是一样的,但是在基础类型中它们有所区别,看下面例子

public class ReflectTest
{
    public static void main(String[] args)throws Exception{
        Class classz1 = Class.forName("basejava.annotation.Gun");
        Class classz2 = int.class;
        Class classz3 = new int[3].getClass();
        System.out.println("Class1.getSimpleName():" + classz1.getSimpleName());
        System.out.println("Class1.getCanonicalName():" + classz1.getCanonicalName());
        System.out.println("Class1.getName():" + classz1.getName());
        System.out.println("------------------------------------------------------------->");
        System.out.println("int.getSimpleName():" + classz2.getSimpleName());
        System.out.println("int.getCanonicalName():" + classz2.getCanonicalName());
        System.out.println("int.getName():" + classz2.getName());
        System.out.println("------------------------------------------------------------->");
        System.out.println("int[].getSimpleName():" + classz3.getSimpleName());
        System.out.println("int[].getCanonicalName():" + classz3.getCanonicalName());
        System.out.println("int[].getName():" + classz3.getName());
        System.out.println("------------------------------------------------------------->");
    }
}

输出如下:

Class1.getSimpleName():Gun 
Class1.getCanonicalName():basejava.annotation.Gun
Class1.getName():basejava.annotation.Gun
------------------------------------------------------------->
int.getSimpleName():int
int.getCanonicalName():int
int.getName():int
------------------------------------------------------------->
int[].getSimpleName():int[]
int[].getCanonicalName():int[]
int[].getName():[I
------------------------------------------------------------->

Process finished with exit code 0

通过以上示例可以看出,getCanonicalName()和getName()是获取类型的全限定名称,getName()就是简称,比如一把武器是产自东方雪山的‘大地之锤’,那么getName()就是它直接名字‘大地之锤’,而getCanonicalName()和getName()就是东方雪山.大地之锤,那么getCanonicalName()和getName()又有什么区别呢?那就是表示方法不一样,如上面最后一个整形数组的例子,getCanonicalName()返回的是int[],getName()返回的是[I,前面一种是规范化表示,第二种是java规定名称,[代表数组纬度几个[就是几维,后面的I是具体类型,对应表如下:


光获取武器的名称还没多大用,我们还需要知道此类武器的级别,如果它是一把神器,那么就算它的名字再难听也是好东西,如果是一把木剑那名字再华丽也是^-^,如何获取武器级别也就是我们怎么获取类的修饰符呢?这需要通过getModifers()方法

Class.getModifers()

先来个例子看一哈效果

public class ReflectTest
{
    public static void main(String[] args)throws Exception
    {
        Class classz1 = Class.forName("basejava.annotation.Gun");

        System.out.println("Gun类的修饰符:" + classz1.getModifiers());
    }
}

输出:
Gun类的修饰符:1

啊!怎么不是public、private、protected、和default等等这些,咋返回是个1呢,哦原来在java中对类的修饰符可能有多种类别,不只是访问级别修饰符还有一些其它关键字,那么如何做到返回多种类型的修饰符呢?这里采用了按位与的运算符,通过在Modifer类中设定修饰符的静态常量,每一个特定的整数代表相应修饰符之间的整形与运算,其定义如下:

public class Modifier {


    public static boolean isPublic(int mod) {
        return (mod & PUBLIC) != 0;
    }

    public static boolean isPrivate(int mod) {
        return (mod & PRIVATE) != 0;
    }

    public static boolean isProtected(int mod) {
        return (mod & PROTECTED) != 0;
    }

    public static boolean isStatic(int mod) {
        return (mod & STATIC) != 0;
    }

    public static boolean isFinal(int mod) {
        return (mod & FINAL) != 0;
    }

 
    public static boolean isSynchronized(int mod) {
        return (mod & SYNCHRONIZED) != 0;
    }


    public static boolean isVolatile(int mod) {
        return (mod & VOLATILE) != 0;
    }


    public static boolean isTransient(int mod) {
        return (mod & TRANSIENT) != 0;
    }

  
    public static boolean isNative(int mod) {
        return (mod & NATIVE) != 0;
    }

   
    public static boolean isInterface(int mod) {
        return (mod & INTERFACE) != 0;
    }


    public static boolean isAbstract(int mod) {
        return (mod & ABSTRACT) != 0;
    }


    public static boolean isStrict(int mod) {
        return (mod & STRICT) != 0;
    }

    public static String toString(int mod) {
        StringBuilder sb = new StringBuilder();
        int len;

        if ((mod & PUBLIC) != 0)        sb.append("public ");
        if ((mod & PROTECTED) != 0)     sb.append("protected ");
        if ((mod & PRIVATE) != 0)       sb.append("private ");

        /* Canonical order */
        if ((mod & ABSTRACT) != 0)      sb.append("abstract ");
        if ((mod & STATIC) != 0)        sb.append("static ");
        if ((mod & FINAL) != 0)         sb.append("final ");
        if ((mod & TRANSIENT) != 0)     sb.append("transient ");
        if ((mod & VOLATILE) != 0)      sb.append("volatile ");
        if ((mod & SYNCHRONIZED) != 0)  sb.append("synchronized ");
        if ((mod & NATIVE) != 0)        sb.append("native ");
        if ((mod & STRICT) != 0)        sb.append("strictfp ");
        if ((mod & INTERFACE) != 0)     sb.append("interface ");

        if ((len = sb.length()) > 0)    /* trim trailing space */
            return sb.toString().substring(0, len-1);
        return "";
    }

    public static final int PUBLIC           = 0x00000001;
    public static final int PRIVATE          = 0x00000002;
    public static final int PROTECTED        = 0x00000004;
    public static final int STATIC           = 0x00000008;
    public static final int FINAL            = 0x00000010;
    public static final int SYNCHRONIZED     = 0x00000020;
    public static final int VOLATILE         = 0x00000040;
    public static final int TRANSIENT        = 0x00000080;
    public static final int NATIVE           = 0x00000100;
    public static final int INTERFACE        = 0x00000200;
    public static final int ABSTRACT         = 0x00000400;
    public static final int STRICT           = 0x00000800;
   

相信大家看了上面的代码也就明白了返回整数的意义,就是通过一个数值来表示多个值。

知悉了如何获取类型的修饰符后,下面就是咋们反射的重要内容了,属性和方法的获取与操控。

属性

---获取属性---

对于一把武器,咋们可能想要知道其是什么材质、镶嵌了几颗宝石、器魂是什么等等诸如此类的属性,那么在反射中我们可以通过以下API来获取我们的属性成员:

获取指定的属性:Class.getField("fieldName")与Class.getDeclaredField("FieldName");

public class ReflectTest
{
    public static void main(String[] args)throws Exception
    {
        Class classz1 = Class.forName("basejava.annotation.Gun");
        //获取特定名称的非私有成员属性
        Field  field = classz1.getField("fieldName");        
        Field  field1 = classz1.getDeclaredField("fieldName");//获取当前类定义的成员属性
    }
}

Field是java中自定义类,用于表示类型属性,这里我们只需要知道其可以存储反射获取的对象成员就可以了。那么getField()和getDeclaredField()有和区别呢?getField()只能获取非private修饰符修饰的成员,并且包括从超类继承下来的成员属性;getDeclareField()能够获取所有访问级别的成员属性,但是不包含从父类继承下来的成员;

---获取所有成员---

同上面类似

//获取非私有的所有成员
Field[] fields = classz.getFields();
//获取本类中声明的所有成员
Field[] fields1 = classz.getDeclaredFields();

举个例子给大家加深印象:

public class ReflectTest
{
    public static void main(String[] args)throws Exception
    {
        Class classz = Son.class;

        Field[] fields = classz.getFields();
        Field[] fields1 = classz.getDeclaredFields();

        for(Field f:fields){
            System.out.println("getFields()获取的属性名称:"+ f.getName());
        }
        System.out.println("------------------------------");
        for (Field f:fields1
             )
        {
            System.out.println("getDeclaredFields()获取的属性名称:" + f.getName());
        }
    }
}
        输出:
        getFields()获取的属性名称:sonAge
        getFields()获取的属性名称:name
        getFields()获取的属性名称:age
        ------------------------------
        getDeclaredFields()获取的属性名称:sonName
        getDeclaredFields()获取的属性名称:sonAge

        Process finished with exit code 0

通过示例我们就能清晰的明白getFields()和getDeclaredFields()的区别了;

现在知道武器有哪些属性了,但是这些属性的类别还不知道呢,也就是我们还需要知道属性的类型:

public Type getGenericType();

public Class<?> getType();
这两种有什么区别?简单来说getType获取的是对应类型的Class对象,不能获取泛型类型;getGenericType()获取的是Type对象可以包含泛型类型,示例:
class Father{
    public String name;
    public int age;
}

class Son extends Father{
    private String sonName;
    public List<Son> sonList;
}


public class ReflectTest
{
    public static void main(String[] args)throws Exception
    {
        Class classz = Son.class;

        Field[] fields = classz.getFields();

        for(Field f:fields){
            System.out.println("getType获取属性的类型:"+ f.getType());
            System.out.println("getGenericType获取属性的类型:" + f.getGenericType());
        }
    }
}

        输出:
        getType获取属性的类型:interface java.util.List
        getGenericType获取属性的类型:java.util.List<basejava.annotation.Son>
        getType获取属性的类型:class java.lang.String
        getGenericType获取属性的类型:class java.lang.String
        getType获取属性的类型:int
        getGenericType获取属性的类型:int

得到属性类型还不是我们的最终目标,能不能获取和设置属性的值才是我们最关心的,放心在《葵花宝典》中已经为我们准备好了方法:

Field中定义的获取属性值API列表:

public Object get(Object obj);

public int getInt(Object obj);

public long getLong(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public float getFloat(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public short getShort(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public double getDouble(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public char getChar(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public byte getByte(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public boolean getBoolean(Object obj)
        throws IllegalArgumentException, IllegalAccessException

Field中定义设置属性值API:

public void set(Object obj, Object value);

public void getInt(Object obj,int value);

public void getLong(Object obj,long value)
        throws IllegalArgumentException, IllegalAccessException;

public void getFloat(Object obj,float value)
        throws IllegalArgumentException, IllegalAccessException;

public void getShort(Object obj,short value)
        throws IllegalArgumentException, IllegalAccessException;

public void getDouble(Object obj,double value)
        throws IllegalArgumentException, IllegalAccessException;

public void getChar(Object obj,char value)
        throws IllegalArgumentException, IllegalAccessException;

public void getByte(Object obj,byte b)
        throws IllegalArgumentException, IllegalAccessException;

public void getBoolean(Object obj,boolean b)
        throws IllegalArgumentException, IllegalAccessException

这个还是很好理解就不举例说明了,需要注意的是方法中的第一个Object对象是我们操控的目标对象,也就是说你要获取或者设置哪个对象的属性值,就是这么Easy.....

好了介绍完了咋们的对象属性,下面就是我们反射的重头戏了----方法。

【方法】

对于一把武器来说我们最想知道的是它具有哪些厉害的技能,同反射一样我们最想知道一个类型提供了哪些方法,如何使用这些方法,我们先来看一个方法的基本结构:

public void say(String name)throws Exception{}

可以看出,方法包含的要素有

  • 方法名;
  • 返回值;
  • 方法参数;
  • 修饰符;
  • 抛出异常类型;

那么我们如何去获取这些方法要素呢,下面慢慢将来

方法名

Method.getName();//获取方法的名称

举个简单例子:

class Son {
    private String sonName;
    public List<Son> sonList;

    public void sayHello(String name){
        System.out.println(name + "您好,欢迎光临我的反射小屋。");
    }
}


public class ReflectTest
{
    public static void main(String[] args)throws Exception
    {
        Class classz = Son.class;
        Method[] methods = classz.getDeclaredMethods();
        for (Method method:
             methods)
        {
            System.out.println("方法名称:" + method.getName());
        }
    }
}

        输出:
        方法名称:sayHello

        Process finished with exit code 0
返回值
// 获取返回值类型
public Class<?> getReturnType() {}


// 获取返回值类型包括泛型
public Type getGenericReturnType() {}

这个同咋们前面获取属性类型相似,就不在赘述了;

对于一个对象方法,我们可能比较关心它有哪些输入参数,那这个又如何获取呢?放心有办法做到

public Parameter[] getParameters() {}   //JDK1.8才提供此方法

通过此方法可以获取方法参数数组Parameter[],同时Parameter方法也提供了一系列的API来获取参数名称、参数类型、参数修饰符

// 获取参数名字
public String getName() {}

// 获取参数类型
public Class<?> getType() {}

// 获取参数的修饰符
public int getModifiers() {}
// 获取所有的参数类型
public Class<?>[] getParameterTypes() {}

// 获取所有的参数类型,包括泛型
public Type[] getGenericParameterTypes() {}

这个API同前面属性名称、类型获取类似就不多讲了,举例来说明:

public class ReflectTest
{
    public static void main(String[] args)throws Exception
    {
        Class classz = Son.class;
        Method[] methods = classz.getDeclaredMethods();
        for (Method method:
             methods)
        {
            System.out.println("方法名称:" + method.getName());
            Type[] types = method.getGenericParameterTypes();
            for (Type type:
                 types)
            {
                System.out.println("参数类型:" + type.toString());
            }
            System.out.println("-------------------------------------》");
        }
    }
}

        输出:
        方法名称:sayHello
        参数类型:class java.lang.String
        -------------------------------------》
需要注意的是Parameter类是在JDK1.8中才引入的,在1.8以前需要只能获取参数的类型; 修饰符
public int getModifiers() {}

此方法和前面类似,就不再多讲

异常类型

public Class<?>[] getExceptionTypes() {}

public Type[] getGenericExceptionTypes() {}

方法操控

终于说到了我们的重点,我们研究一把武器最想干什么?当然是用来攻击了嘛,看看它有哪些厉害的技能可以施放,顺带操作一把。那么对于的反射,我们最想要的就是执行对象的方法,使用方式:

public Object invoke(Object obj, Object... args) {}

同我们的属性值获取与设置类似,方法的操控第一个参数object为我们想要操控的目标对象,后面为我们的方法参数....,先看例子

class Son {
    private String sonName;
    public List<Son> sonList;

    public void sayHello(String name){
        System.out.println(name + "您好,欢迎光临我的反射小屋。");
    }
}


public class ReflectTest
{
    public static void main(String[] args)throws Exception
    {
        Class classz = Son.class;
        Son son = new Son();
        Method[] methods = classz.getDeclaredMethods();
        for (Method method:
             methods)
        {
            System.out.println("方法名称:" + method.getName());
            Type[] types = method.getGenericParameterTypes();
            for (Type type:
                 types)
            {
                System.out.println("参数类型:" + type.toString());
            }
            System.out.println("-------------------------------------》");
            System.out.println("方法执行结果");
            method.setAccessible(true);//提供访问Private私有方法的接口
            method.invoke(son,"小王");
        }
    }
}

        输出:
        方法名称:sayHello
        参数类型:class java.lang.String
        -------------------------------------》
        方法执行结果
        小王您好,欢迎光临我的反射小屋。

        Process finished with exit code 0

方法执行理解也是比较容易的,这里需要注意的是method.setAccessible(true);如果你是想要操控私有方法,那么就需要打开它的访问权限,第二点需要注意的是如果是操控静态方法,那么invoke()方法中第一参数即为null,形如invoke(null,arg1,arg2....)。

【Constructor】

咋们的构造方法有点特殊,不能像普通方法那样去获取操控,它有专门的获取API:

public Constructor getConstructor(Class<?>...ParameterTypes)

public Constructor getDeclaredConstructor(Class<?>...ParameterTypes)

为什么要将我们的构造方法同我们的普通方法分开来呢?因为我们需要通过构造方法来创建对象,一般推荐通过构造方法来创建对象,通过class.newInstance()方法只能调用我们的无参构造,而通过我们的Constructor.newInstance(args1...)可以调用我们任意的构造方法,举例说明:

class Son {
    private String sonName;
    public List<Son> sonList;
    public Son(){
        System.out.println("这是son的无参构造函数!");
    }
    public Son(String name){
        System.out.println("这是Son的带参构造方法");
    }
    public void sayHello(String name){
        System.out.println(name + "您好,欢迎光临我的反射小屋。");
    }
}


public class ReflectTest
{
    public static void main(String[] args)throws Exception
    {
        Class classz = Son.class;
        Constructor constructor = classz.getConstructor(String.class);
        Son son1 = (Son) classz.newInstance();
        Son son2 = (Son) constructor.newInstance("misli");

    }
}
        输出:
        这是son的无参构造函数!
        这是Son的带参构造方法

        Process finished with exit code 0

通过上面例子可以明白通过class构造对象与通过构造方法构造对象的区别;

【总结】

1.反射是通过Class类对象来完成的,它是反射的入口,获取方式有Class.forName()、object.getClass()、class.class三种;

2.通过对应Class对象可以获取类型名、属性Field、方法method以及构造方法Constructor和它们相关联的信息修饰符、返回值、类型、名称等;

3.通过Field的set/get方法获取和设置属性的值,通过method的invoke()方法操控对象的方法运行;

4.Constructor.newInstance()方法可以调用带参构造方法创建对象,相比class.newInstance()方法只能调用无参构造方法更具有灵活性;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值