反射机制


本文章参照:魔乐java视频教程讲解,感谢


1、认识反射

反射之中包含一个“反”的概念,所以就必须先从“正”开始解释,一般而言,当用户使用一个类的时候,应该先知道这个类,而后通过这个类产生实例化对象,但是“反”指的是通过对象找到类



package​ cn.test;


class​ Person{};  //定义一个Person类


public class​ ReflectDemo {

    ​public static void​ main(String[] args) {

       System.​out​.println(Person.​class​.getName());  //得到类名

    }

}




打印



cn.test.Person



以上代码使用一个getClass( )方法,而后就可以得到对象所在的“包”类名称,就属于“反了”,但是在这个“反”的操作之中有一个getClass( )就作为发起一切反射操作的开端。


1.1、取得类的实例化对象

Person的父类是Object类,而上面所使用的getClass()就是Object类中所定义的方法

l  ​Class对象:​public final ​​ Class​​<?> ​getClass​()

返回此 ​​Object​​​ 的运行时类。返回的 ​​Class​​​ 对象是由所表示类的 ​​static synchronized​​ 方法锁定的对象,反射中的所有泛型都定义为?,返回值都是Object,而这个getClass( )方法返回的对象是Class类的对象,所以这个Class就是所有反射操作的源头。这个类最为重要,而如果想要取得这个类的实例化对象,Java中定义了三种方式:

1)        ​通过Object的getClass( )方法

2)        ​通过“类名.class”方式

3)        ​使用Class类内部定义的一个static方法,如下描述



public static ​​ Class​​<?>​forName​(​​String​​​ className) throws​​ClassNotFoundException​

返回与带有给定字符串名的类或接口相关联的 Class 对象。调用此方法等效于:

  Class.forName(className, true, currentLoader)


其中 currentLoader 表示当前类的定义类加载器。

例如,以下代码片段返回命名为 java.lang.Thread 的类的运行时 Class 描述符。

   Class t = Class.forName("java.lang.Thread")

调用 forName("X") 将导致命名为 X 的类被初始化。

参数:

className - 所需类的完全限定名。

initialize - 是否必须初始化类

loader - 用于加载类的类加载器

返回:

具有指定名的类的 Class 对象。

抛出:

​LinkageError​​ - 如果链接失败

​ExceptionInInitializerError​​ - 如果此方法所激发的初始化失败

​ClassNotFoundException​​ - 如果无法定位该类





package​ cn.test;


class​ Person{};  //定义一个Person类


public class​ ReflectDemo {

    ​public static void​ main(String[] args) {


       /* 方式一:使用Object类的 getClass()方法,基本不用 */

       Person p = ​new​ Person();

       System.​out​.println(p.getClass().getName());

       // 或

       Class<?> c = p.getClass();

       System.​out​.println(c.getName());



       /* 方式二:使用类.Class方式,在日后学习Hibernate开发时候使用 */

       Class<?> cls = Person.​class​;   //取得 Class对象

       System.​out​.println(cls.getName());//得到类名

       // 或

       System.​out​.println(Person.​class​.getName());



       /*方式三:使用Class类内部定义的一个static方法,主要使用 */

       ​try​ {

           Class<?> cl = Class.​forName​("cn.test.​Person​");

           System.​out​.println(cl.getName());

       } ​catch​ (Exception e) {

           e.printStackTrace();

       }


    }

}



1.2、通过反射实例化对象

费了这个大的周章,去到了Class类的对象又有什么用呢?对于对象的实例化操作之前一直依靠构造方法和关键字New来完成,可是有了Class类对象之后现在又提供了另一种对象实例化方法

l  ​通过反射实例化对象​:使用newInstance()方法


public ​​T​​​ newInstance()  throws​​InstantiationException​​​,  ​​IllegalAccessException​

创建此 Class 对象所表示的类的一个新实例。如同用一个带有一个空参数列表的 new 表达式实例化该类。如果该类尚未初始化,则初始化这个类。


package​ cn.test;


/*

 * 定义一个Person类

 */

class​ Person{

    ​public​ String toString() {

       ​return​"Person Class Instance";

    }

}; 


public class​ ReflectDemo {

    ​public static void​ main(String[] args) ​throws​ Exception {

       Class<?> cls = Class.​forName​("cn.test.Person");

       Person p = (Person)cls.newInstance();  // 实例化对象,相当于new关键字,然后向下转型

       System.​out​.println(p);

    }

}




打印:



Person Class Instance



可是我们发现,对于对象的实例化操作,除了使用关键字new之外又多了一个反射机制操作,而对于这个操作要比之前使用的new复杂一些,可是这有什么用呢?

1.3、反射有什么用呢?

对于程序开发模式之前一直强调:尽量减少耦合,而减少耦合最好的做法就是使用接口,但是就算使用了接口也逃不出关键字new,所以实际上new就是造成耦合的关键元凶。


范例:​回顾一下之前所编写的工厂设计模式


package​ cn.test;


interface​ Fruit{ //水果接口类

    ​public void​ eat();

}


class​ Apple ​ implements​ Fruit{   // 苹果类 


    @Override

    ​public void​ eat() {

       System.​out​.println("吃苹果");

    }


}


class​ Factory{   //工厂类

    ​public static​ Fruit getInstance(String className){

       ​if​("apple".equals(className)){

           ​return new​ Apple();

       }

       ​return null​;

    }

}


public class​ ReflectDemo {

    ​public static void​ main(String[] args)​throws​ Exception {

       Fruit f = Factory.​getInstance​("apple");

       f.eat();

    }

}




打印:​



吃苹果



以上为之前所编写的最简单的工厂设计模式,但是在这个弓藏设计模式之中有一个最大的问题,如果现在接口的子类增加了,那么工厂类肯定需要修改,这就是它所面临的最大问题,而这个最大问题造成的关键字病因就是new,那么如果说不使用关键字new,变为了反射机制呢?

反射机制实例化对象的时候实际上只需要“包类”就可以了,于是根据此操作,修改工厂设计模式。


package​ cn.test;


interface​ Fruit{ //水果接口类

    ​public void​ eat();

}


class​ Apple ​ implements​ Fruit{   // 苹果类 


    @Override

    ​public void​ eat() {

       System.​out​.println("吃苹果");

    }


}


class​ Factory{   //工厂类

    ​public static​ Fruit getInstance(String className){

       Fruit f = ​null​;

       ​try​ {

           f = (Fruit)Class.​forName​(className).newInstance();

       } ​catch​ (Exception e) {

           e.printStackTrace();

       }

       ​return​ f;

    }

}


public class​ ReflectDemo {

    ​public static void​ main(String[] args)​throws​ Exception {

       Fruit f = Factory.​getInstance​("cn.test.Apple");

       f.eat();

    }

}




打印:



吃苹果



发现,这个时候即使增加了接口子类,工厂类照样完成实例化操作,这才是真正的工厂类,可以应对于所有的变化,如果单独开发角度而已,开发者关系不大,但是对于日后学习的一些框架技术这个就是它实现的命脉,在实现的程序开发上,如果发现操作过程中需要传递一个完整的“包类”名称的时候几乎都是反射机制作用


2、反射的深入应用

以上知识利用Class类作为反射实例化对象的基本应用,但是对于一个实例化对象而言,它需要调用类之中的构造方法、普通方法、属性等,而这些操作都可以通过反射机制完成。

2.1、Class类提供的相关接口

         那么在得到对应类的Class对象对应后,我们就可以通过该Class对象得到它所对应的类的一些信息,比如该类的构造函数、成员(属性)、方法(函数); 

Class类提供的相关接口介绍:(注:在表中,Class对象对应的类,姑且称为目标类)


接口



返回类型



接口功能实现



getPackage()



Package



得到目标类的包名对应的Package对象



getCanonicalName()



String



得到目标类的全名(包名+类名)



getName()



String



同getCanonicalName()



getClassLoader()



ClassLoader



得到加载目标类的ClassLoader对象



getClasses()



Class<?>[]



得到目标类中的所有的public内部类以及public内部接口所对应的Class对象



getDeclaredClasses()



Class<?>[]



同getClasses(),但不局限于public修饰,只要是目标类中声明的内部类和接口均可



getConstructors()



Constructor<?>[]



得到目标类的所有public构造函数对应的Constructor对象



getDeclaredConstructors()



Constructor<?>[]



同getConstructors(),但不局限于public修饰,只要是目标类中声明的构造函数均可



getField(String arg)



Field



得到目标类中指定的某个public属性对应的Field对象



getDeclaredField(String arg)



Field



同getField,但不局限于public修饰,只要是目标类中声明的属性均可



getFields()



Field[]



得到目标类中所有的public属性对应的Field对象



getDeclaredFields()



Field[]



同getFields(),但不局限于public修饰的属性



getMethod(String arg0, Class<?>... arg1)



method



得到目标类中指定的某个public方法对应的Method对象



getDeclaredMethod(String arg0, Class<?>... arg1)



Method



同getMethod,但不局限于public修饰的方法



getMethods()



Method[]



得到目标类中所有的public方法对应的Method对象



getDeclaredMethods()



Method[]



同getMethods(),但不局限于public修饰的方法



getEnclosingClass()



Class



得到目标类所在的外围类的Class对象



getGenericInterfaces()



Type[]



得到目标类实现的接口对应的Type对象



getGenericSuperclass()



Type



得到目标类继承的父类所对应的Type对象



getInterfaces()



Class<?>[]



得到目标类实现的接口所对应的Class对象



getSuperclass()



Class



得到目标类继承的父类所对应的Class对象



isMemberClass()



boolean



目标类是否为成员类



cisAnonymousClass()



boolean



目标类是否为匿名类



2.2、调用所有构造方法


package​ cn.test;


import​ java.lang.reflect.Constructor;


/*

 * 定义一个Person内部类,包含3个构造函数

 */

class​ Person{

    ​public​ Person(){};

    ​public​ Person(String name){};

    ​public​ Person(String name,​int​ age){};

}; 


public class​ ReflectDemo2 {

    ​public static void​ main(String[] args)​throws​ Exception {

       Class<?> cls = Class.​forName​("cn.test.Person");

       Constructor<?> cons[] = cls.getConstructors();

       ​for​ (​int​ i = 0; i < cons.length; i++) {

           System.​out​.println(cons[i]);

       }

    }

}




打印:



public cn.test.Person()

public cn.test.Person(java.lang.String)

public cn.test.Person(java.lang.String,int)



验证:​一个简单的Java类必须存在一个无参构造函数。

范例:​ 无参构造函数


package​ cn.test;


/*

 * 定义一个Person内部类,没有无参构造函数

 */

class​ Person{

    String name;

    ​public​ Person(String name){

       ​this​.name = name;

    };

    ​public​ String toString(){

       ​return​"Person name = "+name;

    }

}; 


public class​ ReflectDemo2 {

    ​public static void​ main(String[] args)​throws​ Exception {

       Class<?> cls = Class.​forName​("cn.test.Person");

       Person p = (Person)cls.newInstance();

       System.​out​.println(p);

    }

}




打印:



Exception in thread "main" ​java.lang.InstantiationException​: cn.test.Person

    at java.lang.Class.newInstance0(​Class.java:340​)

    at java.lang.Class.newInstance(​Class.java:308​)

    at cn.test.ReflectDemo2.main(​ReflectDemo2.java:21​)



因为以上的方式使用反射实例化对象时需要的是类之中要提供无参构造方法,但是及软没有无参构造方法,那么就必须明确找到一个构造方法,而后使用Constructor类之中的新方法实例化对象


public ​​ Constructor​​​<​​T​​>​getConstructor​(​​Class​​<?>... parameterTypes)

                              throws ​​ NoSuchMethodException​​​, ​​ SecurityException​

返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。parameterTypes 参数是 Class 对象的一个数组,这些 Class 对象按声明顺序标识构造方法的形参类型。 如果此 Class 对象表示非静态上下文中声明的内部类,则形参类型作为第一个参数包括显示封闭的实例。

要反映的构造方法是此 Class 对象所表示的类的公共构造方法,其形参类型与 parameterTypes 所指定的参数类型相匹配。

参数:

parameterTypes - 参数数组

返回:

与指定的 parameterTypes 相匹配的公共构造方法的 Constructor 对象

抛出:

​NoSuchMethodException​​ - 如果找不到匹配的方法。

​SecurityException​​ - 如果存在安全管理器​s​,并满足下列任一条件:

·         调用 ​​s.checkMemberAccess(this, Member.PUBLIC)​​ 拒绝访问构造方法

·         调用者的类加载器不同于也不是当前类的类加载器的一个祖先,并且对 ​​ s.checkPackageAccess()​​ 的调用拒绝访问该类的包

package​ cn.test;


import​ java.lang.reflect.Constructor;


/*

 * 定义一个Person内部类,没有无参构造函数

 */

class​ Person{

    String name;

    ​int​ age;

    ​public​ Person(String name,​int​ age){

       ​this​.name = name;

       ​this​.age = age;

    };

    ​public​ String toString(){

       ​return​"Person name = "+name+" ,age="+age;

    }

}; 


public class​ ReflectDemo2 {

    ​public static void​ main(String[] args)​throws​ Exception {

       Class<?> cls = Class.​forName​("cn.test.Person"); //取得Class对象

       // 取得指定参数类型的构造方法

       Constructor<?>  cons = cls.getConstructor(String.​class​,​int​.​class​);

       Object obj = cons.newInstance("PrettyGril",20); //为构造方法传递参数

       System.​out​.println(obj);

    }

}



打印:



Person name = PrettyGril ,age=20



很明显,调用无参构造方法实例化对象要比调用有参构造更加简单,方便,所以在日后所有开发中,凡是有简单Java类出现的地方,都一定要有无参构造方法


2.3、调用普通方法

1)  取得全部方法

           public ​​Method​​[]​getMethods​() throws ​​SecurityException​

2)        取得指定方法

    public ​​Method​​​getMethod​(​​String​​​ name,​​Class​​<?>... parameterTypes)

                           throws​​NoSuchMethodException​​​,​​SecurityException​


2.3.1 取得一个类中所有定义的全部方法


package​ cn.test;


import​ java.lang.reflect.Method;


/*

 * 定义一个Person内部类

 */

class​ Person {

    String name;


    ​public​ String getName() {

       ​return​name;

    }


    ​public void​ setName(String name) {

       ​this​.name = name;

    }


};


public class​ ReflectDemo2 {

    ​public static void​ main(String[] args)​throws​ Exception {

       Class<?> cls = Class.​forName​("cn.test.Person");//取得Class对象

       Method met[] = cls.getMethods();   // 取得全部方法

       ​for​ (​int​ i = 0; i < met.length; i++) {

           System.​out​.println(met[i]);

       }

    }

}




打印:



public java.lang.String cn.test.Person.getName()

public void cn.test.Person.setName(java.lang.String)

public final void java.lang.Object.wait() throws ​java.lang.InterruptedException

public final void java.lang.Object.wait(long,int) throws ​java.lang.InterruptedException

public final native void java.lang.Object.wait(long) throws ​java.lang.InterruptedException

public native int java.lang.Object.hashCode()

public final native java.lang.Class java.lang.Object.getClass()

public boolean java.lang.Object.equals(java.lang.Object)

public java.lang.String java.lang.Object.toString()

public final native void java.lang.Object.notify()

public final native void java.lang.Object.notifyAll()



对于取得了Method类对象之后还有一个最大的功能,就是可以利用反射调用类中的方法;

l  调用方法:使用Method类中的​Invoke​方法

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

              throws ​​IllegalAccessException​​​,​​IllegalArgumentException​​,

                     ​​InvocationTargetException​

        之前调用类中方法时候都是“对象.方法”,但是现在有了反射之后,可以直接利用Object类调用指定子类的操作方法(同时解释一下,为什么setter、getter方法的命名要求如此严格)

范例:​利用反射调用Person类之中的setName()、getName()方法


package​ cn.test;


import​ java.lang.reflect.Method;


/*

 * 定义一个Person内部类

 */

class​ Person {

    String name;


    ​public​ String getName() {

       ​return​name;

    }


    ​public void​ setName(String name) {

       ​this​.name = name;

    }


};


public class​ ReflectDemo2 {

    ​public static void​ main(String[] args)​throws​ Exception {

       Class<?> cls = Class.​forName​("cn.test.Person");//取得Class对象

       Object obj = cls.newInstance();

       String attribute = "Name";

       Method setMet = cls.getMethod("set"+attribute, String.​class​);

       Method getMet = cls.getMethod("get"+attribute);

       setMet.invoke(obj, "PrettyGirl");

       System.​out​.println(getMet.invoke(obj));

    }

}




打印:



PrettyGirl



在日后的所有框架技术开发之中,简单Java类都是如此应用的,所以必须按照标准进行


3、 调用成员变量

类之中最后一个组成部分就是成员(Field,也称属性)

l  取得本类的全部成员:

                                         public ​​ Field​​[] ​getDeclaredFields​() throws ​​ SecurityException​

l  取得指定的成员:

        public ​​ Field​​ ​getDeclaredField​(​​String​​ name)

                throws​​NoSuchFieldException​​​,​​SecurityException​

3.1 获得全部属性


package​ cn.test;


import​ java.lang.reflect.Field;


/*

 * 定义一个Person内部类

 */

class​ Person {

    String name;

};


public class​ ReflectDemo2 {

    ​public static void​ main(String[] args)​throws​ Exception {

       Class<?> cls = Class.​forName​("cn.test.Person");//取得Class对象

       Field fie[] = cls.getDeclaredFields(); // 获得全部属性

       ​for​ (​int​ i = 0; i < fie.length; i++) {

           System.​out​.println(fie[i]);

       }

    }

}




打印



java.lang.String cn.test.Person.name



在Field中有2个操作方法


(1)设置属性内容(类似于:对象.属性 = 内容)

                    public void​set​(​​Object​​​ obj,​​Object​​ value)

                         throws​​IllegalArgumentException​​​,​​IllegalAccessException​

(2)取得属性内容(类似于:对象.属性)

                    public ​​Object​​​get​(​​Object​​ obj)

                         throws​​IllegalArgumentException​​​,​​IllegalAccessException​


package​ cn.test;


import​ java.lang.reflect.Field;


/*

 * 定义一个Person内部类

 */

class​ Person {

    ​private​ String​name​;

};


public class​ ReflectDemo2 {

    ​public static void​ main(String[] args)​throws​ Exception {

       Class<?> cls = Class.​forName​("cn.test.Person");//取得Class对象

       Object obj = cls.newInstance();

       Field fie = cls.getDeclaredField("name");//获得属性

       fie.set(obj, "PrettyGril");//赋值

       System.​out​.println(fie.get(obj));

    }

}




打印:



Exception in thread "main" ​java.lang.IllegalAccessException​: Class cn.test.ReflectDemo2 can not access a member of class cn.test.Person 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.set(​Field.java:657​)

    at cn.test.ReflectDemo2.main(​ReflectDemo2.java:17​)



因为设置的name访问权限是私有的,所以不能进行赋值,可是从类的开发要求而言,一直都强调类之中属性必须封装,所以现在调用之前要想办法解除封装,只需一句代码: fie.setAccessible(​true​); //解除封装



package​ cn.test;


import​ java.lang.reflect.Field;


/*

 * 定义一个Person内部类

 */

class​ Person {

    ​private​ String​name​;

};


public class​ ReflectDemo2 {

    ​public static void​ main(String[] args)​throws​ Exception {

       Class<?> cls = Class.​forName​("cn.test.Person");//取得Class对象

       Object obj = cls.newInstance();

       Field fie = cls.getDeclaredField("name");//获得属性

       fie.setAccessible(​true​);//解除封装

       fie.set(obj, "PrettyGril");//赋值

       System.​out​.println(fie.get(obj));

    }

}




打印:



PrettyGril



虽然反射机制运行直接操作类之中的属性,可是不会有任何一种程序直接操作属性,都会通过setter、getter方法调用。