进阶Java编程(9)反射与类操作

1,反射获取类结构信息

  在反射机制的处理过程之中不仅仅只是一个实例化对象的处理操作,更多的情况下还有类的组成结构操作,任何一个类的基本组成结构:父类(父接口)、包、属性、方法(构造方法与普通方法)。

获取类的基本信息

  一个类的基本信息主要包括所在的包名称、父类的定义、父接口的定义。

  ·范例:定义一个程序类

 1 package cn.mufasa.vo;
 2 public interface IMessageService {
 3     public void send();
 4 }
 5 public interface IChannelService {
 6     public boolean connect();
 7 }
 8 import cn.mufasa.abs.AbstractBase;
 9 public class Person extends AbstractBase implements IMessageService,IChannelService {
10     @Override
11     public void send() {
12         if(this.connect()){
13             System.out.println("【消息发送】www.cnblogs.com");
14         }
15     }
16     @Override
17     public boolean connect() {
18         return true;
19     }
20 }
1 package cn.mufasa.abs;
2 public abstract class AbstractBase {
3 }

  如果此时想要获取类的基础信息则可以通过Class类中如下方法:

  ①获取包名称:public Package getPackage();

 1 package cn.mufasa.demo;
 2 import cn.mufasa.vo.Person;
 3 public class JavaAPIDemo17 {
 4     public static void main(String[] args) {
 5         Class<?> cls=Person.class;//获取指定类的Class对象
 6         Package pack=cls.getPackage();//获取指定类的包定义
 7         System.out.println(pack.getName());
 8     }
 9 }
10 /*输出
11 cn.mufasa.vo
12  */

  ②获取继承父类:

 1 package cn.mufasa.demo;
 2 import cn.mufasa.vo.Person;
 3 public class JavaAPIDemo18 {
 4     public static void main(String[] args) {
 5         Class<?> cls=Person.class;
 6         Class<?> parent=cls.getSuperclass();//获取继承父类的Class对象
 7         System.out.println(parent.getName());
 8         System.out.println(parent.getSuperclass().getName());
 9         System.out.println(parent.getSuperclass().getSuperclass());
10     }
11 }
12 /*输出
13 cn.mufasa.abs.AbstractBase
14 java.lang.Object
15 null
16  */

  ③获取实现父接口:

 1 package cn.mufasa.demo;
 2 import cn.mufasa.vo.Person;
 3 public class JavaAPIDemo19 {
 4     public static void main(String[] args) {
 5         Class<?> cls=Person.class;
 6         Class<?> [] classes=cls.getInterfaces();//获取继父接口
 7         for(Class<?> temp : classes){
 8             System.out.println(temp.getName());
 9         }
10     }
11 }
12 /*输出
13 cn.mufasa.vo.IMessageService
14 cn.mufasa.vo.IChannelService
15  */

  当获取了一个类的Class对象之后就意味着这个对象可以获取类之中的一切继承结构信息。

2,反射调用构造方法

  在一个类之中除了有继承的关系之外最重要的操作就是类中的结构处理了,而类中的结构里面首先需要观察的就是构造方法的使用问题。实际上在之前通过反射实例化对象的时候就已经接触到了构造方法的问题了:

    ·实例化方法替代:clazz.getDeclaredConstructor().newInstance();

  所有类的构造方法的获取都可以直接通过Class类来完成,该类中定义有如下几个方法:

    ·获取所有构造方法:public Constructor<?>[] getDeclaredConstructors() throws SecurityException

    ·获取指定构造方法:public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) throws NoSuchMethodException,SecurityException

    ·获取所有构造方法:public Constructor<?>[] getConstructors() throws SecurityException

    ·获取指定构造方法:public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException,SecurityException

  ·范例:修改Person类的定义

1 package cn.mufasa.abs;
2 public abstract class AbstractBase1 {
3     public AbstractBase1(){}//无参
4     public AbstractBase1(String msg){}//有参
5 }
 1 package cn.mufasa.vo;
 2 import cn.mufasa.abs.AbstractBase;
 3 public class Person1 extends AbstractBase implements IMessageService,IChannelService {
 4     public Person1(){}
 5     public Person1(String name,int age){
 6     }
 7     @Override
 8     public void send() {
 9         if(this.connect()){
10             System.out.println("【消息发送】www.cnblogs.com");
11         }
12     }
13     @Override
14     public boolean connect() {
15         return true;
16     }
17 }

  ·范例:获取构造

 1 package cn.mufasa.demo;
 2 import cn.mufasa.vo.Person1;
 3 import java.lang.reflect.Constructor;
 4 public class JavaAPIDemo20 {
 5     public static void main(String[] args) {
 6         Class<?> cls=Person1.class;
 7 //        Constructor<?>[] constructors=cls.getDeclaredConstructors();
 8         Constructor<?>[] constructors=cls.getConstructors();
 9         for(Constructor<?> cons : constructors){
10             System.out.println(cons);
11         }
12     }
13 }
14 /*输出
15 public cn.mufasa.vo.Person1()
16 public cn.mufasa.vo.Person1(java.lang.String,int)
17 public cn.mufasa.vo.Person1()
18 public cn.mufasa.vo.Person1(java.lang.String,int)
19  */

  此时获取的是类之中的全部构造方法,但是也可以获取一个指定参数的构造,例如:现在的Person1类之中提供有两个构造方法

 1 package cn.mufasa.vo;
 2 import cn.mufasa.abs.AbstractBase;
 3 public class Person1 extends AbstractBase implements IMessageService,IChannelService {
 4     private String name;
 5     private int age;
 6     public Person1(){}
 7     public Person1(String name,int age){
 8         this.name=name;
 9         this.age=age;
10     }
11     public String toString(){
12         return "姓名:"+this.name+",年龄:"+this.age;
13     }
14     @Override
15     public void send() {
16         if(this.connect()){
17             System.out.println("【消息发送】www.cnblogs.com");
18         }
19     }
20     @Override
21     public boolean connect() {
22         return true;
23     }
24 }

  此时打算调用Person类之中的有参构造方法进行Person1类的实例化处理,这个时候就必须指明要调用的构造,而后通过Constractor类之中提供的实例化方法操作:

  public T newInstance(Object... initargs) throws InstantiationException,IllegalAccessException,IllegalArgumentException,InvocationTargetException

  ·范例:调用指定构造实例化对象

 1 package cn.mufasa.demo;
 2 import cn.mufasa.vo.Person1;
 3 import java.lang.reflect.Constructor;
 4 import java.lang.reflect.InvocationTargetException;
 5 public class JavaAPIDemo21 {
 6     public static void main(String[] args) throws Exception {
 7         Class<?> cls=Person1.class;
 8         Constructor<?>constructor=cls.getConstructor(String.class,int.class);
 9         Object obj=constructor.newInstance("万雨",25);
10         System.out.println(obj);
11     }
12 }
13 /*输出
14 姓名:万雨,年龄:25
15  */

  虽然代码本身允许开发者调用有参构造处理,但是从实际的开发来讲,所有的使用反射的类中最好提供有无参构造,因为这样的实例化可以达到统一性。

3,反射调用普通方法

   在进行反射处理的时候也可以获取类中的全部方法,但是需要提醒的是,如果想要通过反射调用这些方法必须有一个前提条件:类之中要提供实例化对象。

  在Class类里面提供有如下的操作可以获取对象的方法:

  ①获取全部方法:public Method[] getMethods() throws SecurityException

  ②获取指定方法:public Method getMethod(String name,Class<?>... parameterTypes) throws NoSuchMethodException,SecurityException

  ③获取本类全部方法:public Method[] getDeclaredMethods() throws SecurityException

  ④获取本类指定方法:public Method getDeclaredMethod(String name,Class<?>... parameterTypes) throws NoSuchMethodException,SecurityException

   ·范例:获取全部方法

 1 package cn.demo8.demo;
 2 import cn.mufasa.vo.Person1;
 3 import java.lang.reflect.Method;
 4 public class JavaAPIDemo {
 5     public static void main(String[] args) throws Exception {
 6         Class<?> cls=Person1.class;
 7         {//获取全部方法【包括父类的方法】
 8             Method[] methods=cls.getMethods();
 9             for(Method met:methods){
10                 System.out.println(met);
11             }
12         }
13         System.out.println("********分割线*********");
14         {//获取本类方法【包括父类的方法】
15             Method[] methods=cls.getDeclaredMethods();
16             for(Method met:methods){
17                 System.out.println(met);
18             }
19         }
20     }
21 }
22 /*输出
23 public java.lang.String cn.mufasa.vo.Person1.toString()
24 public boolean cn.mufasa.vo.Person1.connect()
25 public void cn.mufasa.vo.Person1.send()
26 public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
27 public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
28 public final void java.lang.Object.wait() throws java.lang.InterruptedException
29 public boolean java.lang.Object.equals(java.lang.Object)
30 public native int java.lang.Object.hashCode()
31 public final native java.lang.Class java.lang.Object.getClass()
32 public final native void java.lang.Object.notify()
33 public final native void java.lang.Object.notifyAll()
34 ********分割线*********
35 public java.lang.String cn.mufasa.vo.Person1.toString()
36 public boolean cn.mufasa.vo.Person1.connect()
37 public void cn.mufasa.vo.Person1.send()
38  */

  但是需要注意的是,这个时候方法信息的获取是根据Method类提供的toString()方法完成的,很多时候也可以由用户自己来拼凑方法信息的展示形式。

   ·范例:自定义方法信息显示

 1 package cn.demo8.demo;
 2 import cn.mufasa.vo.Person1;
 3 import java.lang.reflect.Method;
 4 import java.lang.reflect.Modifier;
 5 public class JavaAPIDemo1 {
 6     public static void main(String[] args) throws Exception {
 7         Class<?> cls=Person1.class;
 8         {
 9             Method[] methods=cls.getMethods();//获取全部方法【包括父类的方法】
10             for(Method met:methods){
11                 int mod=met.getModifiers();//修饰符【编码!!!】求和!
12                 System.out.print(Modifier.toString(mod)+" ");//打印修饰符
13                 System.out.print(met.getReturnType().getName()+" ");//打印返回值
14                 System.out.print(met.getName()+"(");//打印方法名
15                 Class<?>[] params=met.getParameterTypes();//获取参数类型
16                 for(int x=0;x<params.length;x++){
17                     System.out.print(params[x].getName()+" "+"arg-"+x);
18                     if(x<params.length-1){
19                         System.out.print(",");
20                     }
21                 }
22                 System.out.print(")");
23                 Class<?>[] exp=met.getExceptionTypes();
24                 if(exp.length>0){
25                     System.out.print(" throws ");
26                 }
27                 for(int x=0;x<exp.length;x++){
28                     System.out.print(exp[x].getName());
29                     if(x<exp.length-1){
30                         System.out.print(",");
31                     }
32                 }
33                 System.out.println();
34             }
35         }
36     }
37 }
38 /*输出
39 public java.lang.String toString()
40 public boolean connect()
41 public void send()
42 public final native void wait(long arg-0) throws java.lang.InterruptedException
43 public final void wait(long arg-0,int arg-1) throws java.lang.InterruptedException
44 public final void wait() throws java.lang.InterruptedException
45 public boolean equals(java.lang.Object arg-0)
46 public native int hashCode()
47 public final native java.lang.Class getClass()
48 public final native void notify()
49 public final native void notifyAll()
50  */

  这种代码你只需要清楚可以根据反射获取方法的结构即可,不需要做过多的深入理解,但是在Method类里面有一个致命的重要方法:

  public Object invoke(Object obj,Object... args)throws IllegalAccessException,IllegalArgumentException,InvocationTargetException

  在Person类里面追加有setter和getter方法。

 1 package cn.demo8.demo;
 2 import cn.mufasa.abs.AbstractBase;
 3 public class Person2 extends AbstractBase implements IMessageService, IChannelService {
 4     private String name;
 5     private int age;
 6     public Person2(){}
 7     public Person2(String name, int age){
 8         this.name=name;
 9         this.age=age;
10     }
11     public String getName() {
12         return name;
13     }
14     public void setName(String name) {
15         this.name = name;
16     }
17     public String toString(){
18         return "姓名:"+this.name+",年龄:"+this.age;
19     }
20     @Override
21     public void send() {
22         if(this.connect()){
23             System.out.println("【消息发送】www.cnblogs.com");
24         }
25     }
26     @Override
27     public boolean connect() {
28         return true;
29     }
30 }

  随后通过反射机制来实现Person类之中的setter与getter方法的调用处理。

  ·范例:在不导入指定类开发包的情况下实现属性的配置

 1 package cn.demo8.demo;
 2 import java.lang.reflect.Method;
 3 public class JavaAPIDemo2 {
 4     public static void main(String[] args) throws Exception {
 5         Class<?> cls=Class.forName("cn.demo8.demo.Person2");
 6 //        String attribute="name";//要操作的属性
 7         String value="红红";//要设置的属性内容
 8         //①任何情况下如果想要保存类中的属性或者调用类中的方法都必须保证存在有实例化对象,既然不允许导入包,那么就反射实例化
 9         Object obj=cls.getDeclaredConstructor().newInstance();//调用无参构造
10         //②如果想要进行方法的调用,一定要获取方法的名称
11         String setMethodName="setName";//方法名称
12         Method setMethod=cls.getDeclaredMethod(setMethodName,String.class);//获取指定方法
13         setMethod.invoke(obj,value);//等价于:Person对象.setName(value);
14         String getMethodName="getName";
15         Method getMethod=cls.getDeclaredMethod(getMethodName);//getter方法没有传入参数
16         System.out.println(getMethod.invoke(obj));
17     }
18 }
19 /*输出
20 红红
21  */

  利用此类操作整体的形式上不会有任何的明确类对象的产生,一切都是依靠反射机制处理的,这样的处理避免了与某一个类的耦合问题。

4,反射调用成员

   类结构之中的最后一个核心的成员就是成员【Feild】,大部分情况下都会将其称为成员属性,对于成员信息的获取也是通过Class类完成的,在这个类中提供有如下两组操作方法:

  ①获取本类成员:public Field[] getDeclaredFields() throws SecurityException

  ②获取指定成员:public Field getDeclaredField(String name) throws NoSuchFieldException,SecurityException

  ③获取父类全部成员:public Field[] getFields() throws SecurityException

  ④获取父类指定成员:public Field getField(String name) throws NoSuchFieldException,SecurityException

  ·范例:修改要操作的父类结构

1 package cn.demo9.demo;
2 
3 public abstract class AbstractBase {
4     protected static final String NAME="www.cnblog.com";
5     private String info="Hello 万雨";
6     public AbstractBase(){}//无参
7     public AbstractBase(String msg){}//有参
8 }
1 package cn.demo9.demo;
2 
3 public interface IChannelService {
4     public static final String NAME="万雨";
5     public boolean connect();
6 }

  ·范例:获取类中的成员

 1 package cn.demo9.demo;
 2 import java.lang.reflect.Field;
 3 
 4 public class JavaAPIDemo {
 5     public static void main(String[] args) throws Exception {
 6         Class<?> cls=Class.forName("cn.demo9.demo.Person");
 7         {//获取父类中的公共信息
 8             Field[] fields=cls.getFields();//获取父类成员
 9             for(Field fie:fields){
10                 System.out.println(fie);
11             }
12         }
13         System.out.println("*** 分割线 ***");
14         {//获取子类中的公共信息
15             Field[] fields=cls.getDeclaredFields();//获取成员
16             for(Field fie:fields){
17                 System.out.println(fie);
18             }
19         }
20     }
21 }
22 /*输出
23 public static final java.lang.String cn.demo9.demo.IChannelService.NAME
24 *** 分割线 ***
25 private java.lang.String cn.demo9.demo.Person.name
26 private int cn.demo9.demo.Person.age
27  */

  但是在Field类里面最为重要的操作形式并不是获取全部的成员,而是如下的三个方法:

  ①设置属性内容:public void set(Object obj,Object value)throws IllegalArgumentException,IllegalAccessException

  ②获取属性内容:public Object get(Object obj) throws IllegalArgumentException,IllegalAccessException

  ③解除封装:public void setAccessible(boolean flag) throws SecurityException,备注:构造可以解除封装、方法也可以解除封装

  所有的成员是在对象实例化之后进行空间分配的,所以此时一定要先有实例化对象之后才可以进行成员的操作;

  ·范例:直接调用Person类中的name私有成员

 1 package cn.demo9.demo;
 2 import java.lang.reflect.Field;
 3 
 4 public class JavaAPIDemo1 {
 5     public static void main(String[] args) throws Exception {
 6         Class<?> cls=Class.forName("cn.demo9.demo.Person");
 7         Object obj=cls.getConstructor().newInstance();//实例化对象【分配成员空间】
 8         Field nameField=cls.getDeclaredField("name");//获取成员对象
 9         nameField.setAccessible(true);//解除了封装
10         nameField.set(obj,"红红");//等价于:Person对象.name="红红";
11         System.out.println(nameField.get(obj));//等价于:Person对象.name;
12     }
13 }
14 /*输出
15 ①解除封装之前
16 Exception in thread "main" java.lang.IllegalAccessException: class cn.demo9.demo.JavaAPIDemo1 cannot access a member of class cn.demo9.demo.Person with modifiers "private"
17  ②解除封装之后
18  红红
19  */

  通过一系列的分析可以发现,类之中的构造方法、成员属性都可以反射实现调用,但是对于成员的反射调用我们很少这样直接处理,大部分操作都应该通过setter或getter处理,所以说对于以上的代码只能够说是反射的一个特色,但是不具备有实际的使用能力,而对于Field类在实际开发之中只有一个方法最为常用:

  获取成员类型:public Class<?> getType()

  ·范例:获取Person类中的name成员类型

 1 package cn.demo9.demo;
 2 import java.lang.reflect.Field;
 3 
 4 public class JavaAPIDemo2 {
 5     public static void main(String[] args) throws Exception {
 6         Class<?> cls = Class.forName("cn.demo9.demo.Person");
 7         Field nameField = cls.getDeclaredField("name");//获取成员对象
 8         System.out.println(nameField.getType().getName());//获取完整类名称【包.类】
 9         System.out.println(nameField.getType().getSimpleName());//获取类名称
10     }
11 }
12 /*输出
13 java.lang.String
14 String
15  */

  在以后的开发中进行反射处理的时候,往往会利用Field与Method类实现类中setter方法的调用。

5,Unsafe工具类

  反射是Java的第一大特点,一旦打开了反射的大门就可以有了更加丰富的类设计形式。除了JVM本身支持的反射处理之外,子Java里面也提供有一个Unsafe类【不安全的操作】,这个类的主要特点是可以利用反射来获取对象,并且直接使用底层的C++来代替Java来执行,即:可以绕过JVM底层的相关的对象管理机制,如果你一旦使用了Unsafe类,那么你的项目之中将无法继续使用JVM的内存管理机制以及垃圾回收处理。

  但是如果要想使用Unsafe类首先就需要确认一下这个类之中定义的构造方法与常量问题:

构造方法
private Unsafe() {}
私有常量
private static final Unsafe theUnsafe = new Unsafe();

  但是需要注意的是,在这个Unsafe类里面并没有提供static方法,即:不能够通过类似于传统的单例设计模式之中提供的样式来进行操作,如果要想获得这个对象,就必须利用反射机制来完成。

1         Field field=Unsafe.class.getDeclaredField("theUnsafe");
2         field.setAccessible(true);//解除封装
3         Unsafe unsafeObject=(Unsafe) field.get(null);//static属性不需要传递实例化对象

  在传统的开发之中,一个程序类必须要通过实例化对象后才可以调用类中个的普通方法,尤其是以单例设计模式为例

  ·范例:使用Unsafe类绕过实例化对象管理

 1 package cn.demo10.demo;
 2 
 3 public class Singleton {
 4     private Singleton(){
 5         System.out.print("【单例-私有构造】");
 6     }
 7     public void print(){
 8         System.out.print("www.mldn.cn");
 9     }
10 }
 1 package cn.demo10.demo;
 2 
 3 import sun.misc.Unsafe;
 4 
 5 import java.lang.reflect.Field;
 6 
 7 public class JavaAPIDemo2 {
 8     public static void main(String[] args) throws Exception {
 9         Field field=Unsafe.class.getDeclaredField("theUnsafe");
10         field.setAccessible(true);//解除封装
11         Unsafe unsafeObject=(Unsafe) field.get(null);//static属性不需要传递实例化对象
12         //利用Unsafe类绕过了JVM的管理机制,可以在没有实例化对象的情况下获取一个Singleton类对象
13         Singleton singleton=(Singleton) unsafeObject.allocateInstance(Singleton.class);
14         singleton.print();
15 
16     }
17 }
18 /*输出
19 www.mldn.cn
20  */

  Unsafe只能够说为我们的开发提供了一些更加方便的处理机制,但是这种操作由于不受JVM的管理所以如果不是必须的情况下不建议使用,而讲解这个类主要的目的是帮助大家巩固对于反射的理解,同时也帮助大家在笔试的时候如果有人问到单例模式的情况下,也可以追加一个Unsafe以加深你对这一概念的理解。

原文地址:https://www.cnblogs.com/Mufasa/p/11177237.html