Java反射机制

发布时间 2023-09-02 20:03:17作者: e路有你

Java反射机制

学习目标:

  1. 了解反射的基本原理
  2. 掌握Class类的使用
  3. 使用Class类并结合java.lang.reflect包取得一个类的完整结构。
  4. 通过反射机制动态的调用类中的指定方法,并能向这些方法中传递参数。

​ 在Java中较为重要的就是反射机制,那么什么是反射机制呢?例如,正常情况下如果已经有一个类,则肯定可以通过类创建对象;那么如果现在要求通过一个对象找到一个类的名称,此时就需要用到反射机制。如果要完成反射操作,则首先应该认识的就是Class类。

​ 在反射操作的学习中,读者一定要把握住一个核心的概念:"一切的操作都将使用 Object完成,类、数组的引用都可以使用 Object进行接收",只有把握了这个概念才能更清楚地掌握反射机制的作用。

1.认识Class类

​ 在Java的世界里,一切皆是对象,所有的类都是继承于 Object 类,而记录对象的类型的信息是由Class类来完成的。

Object类和Class类没有直接的关系。

​ Object类是一切java类的父类,对于普通的java类,即便不声明,也是默认继承了Object类。典型的,可以使用Object类中的toString()方法。

​ Class类是用于java反射机制的,一切java类,都有一个对应的Class对象,他是一个final类。Class 类的实例表示,正在运行的 Java 应用程序中的类和接口。 所以所有的对象都可以转变为java.lang.Class类型表示。

​ 在Java中,每个class都有一个相应的Class对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息


​ 在正常情况下,需要现有一个类的完整路径引入之后才可以按照规定的格式产生实例化对象,但是在Java中也运行通过一个实例化对象找到一个类的完整信息,那么这就是Class类的功能。

通过一个对象得到完整的"包.类"名称:

package kw;

class X{

}

public class Class_01 {
    public static void main(String[] args) {
        X x = new X();
        System.out.println(x.getClass().getName());  //得到对象所在的类
    }
}

输出:
kw.X

从程序的运行结果来看我们通过一个对象得到了对象所在的完整的"包.类"名称,现在有一个问题就是,getClass()方法是在哪里定义的呢?任何一个类如果没有明确的声明继承自哪个父类时,则默认继承Object类,所以getClass()方法是从Object类中继承而来的。

getClass() 方法定义:

public final native Class<?> getClass()

​ Class 表示一个类的本身,通过Class可以完整的得到一个类中的完整结构,包括此类中的方法定义、属性定义等。

Class 类的常用方法

序号 方法 类型 描述
1 public static Class<?> forName(String className) throws ClassNotFoundException 普通 传入完整的"包.类"名称实例化Class对象
2 public Constructor<?>[] getConstructors() throws SecurityException 普通 得到一个类中的全部构造方法
3 public Field[] getDeclaredFields() throws SecurityException 普通 得到本类中单独定义的全部属性
4 public Field[] getFields() throws SecurityException 普通 取得本类继承而来的全部属性
5 public Method[] getMethods() throws SecurityException 普通 得到一个类中的全部方法
6 public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException 普通 返回一个Method对象,并设置一个方法中的所有参数类型
7 public Class<?>[] getInterfaces() 普通 得到一个类中所实现的全部接口
8 public String getName() 普通 得到一个类完整的"包.类"名称
9 public Package getPackage() 普通 得到一个类的包
10 public native Class<? super T> getSuperclass() 普通 得到一个类的父类
11 public T newInstance()throws InstantiationException, IllegalAccessException 普通 根据Class定义的类实例化对象
12 public native Class<?> getComponentType() 普通 返回表示数组类型的
13 public native boolean isArray() 普通 判断此Class是否是一个数组

​ 在Class类中本身没有定义任何的构造方法,所以如果要使用则首先必须通过 forName()的静态方法实例化对象。除此之外,也可以使用"类.class" 或 "对象.getClass()"方法实例化。

.class解释

问:java中 .class 属性从哪里来?哪个类中的属性?

答:.class就是java文件被虚拟机编译后的文件

实例化Class类对象:

package kw;

public class Class_02 {
    public static void main(String[] args) {
        Class<?> c1 = null;
        Class<?> c2 = null;
        Class<?> c3 = null;
        try {
            c1 = Class.forName("kw.X");
        }catch(Exception e){
            e.printStackTrace();
        }

        c2 = new X().getClass();
        c3 = X.class;

        System.out.println("类名称:" + c1.getName());
        System.out.println("类名称:" + c2.getName());
        System.out.println("类名称:" + c3.getName());
    }
}

输出:

类名称:kw.X
类名称:kw.X
类名称:kw.X

2.Class 类的使用

​ 实际上Class类在开发中最常见的用法就是实例化对象的操作,即可以通过一个给定的字符串(此字符串包含了完整的"包.类"的路径)来实例化一个类的对象。

1.通过无参构造实例化对象

​ 如果想通过Class类本身实例化其他类的对象,则可以使用newInstance()方法,但是必须要保证被实例化的类中存在一个无参构造方法

package kw;

class Person01{
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person01{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class Class_03 {
    public static void main(String[] args) {
        Class<?> c = null;

        try {
            c = Class.forName("kw.Person01");
        }catch (Exception e){
            e.printStackTrace();
        }

        Person01 per = null;
        try {
            per = (Person01) c.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        per.setName("康帅");
        per.setAge(18);
        System.out.println(per);
    }
}

输出:
Person01

​ 从程序运行结果来看,通过 Class.forName()方法实例化 Class对象之后,直接调用 newInstance()方法就可以根据传入的完整 "包.类"名称的方式进行对象的实例化操作,完全取代了之前使用关键字 new 的操作方式。

在使用以上操作时需要注意,被实例化对象的类中必须存在无参构造方法,如果不存在,则肯定是无法实例化的。

错误展示:

package kw;

class Person01{
    private String name;
    private int age;

    public Person01(String name, int age){
        this.setName(name);
        this.setAge(age);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person01{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class Class_03 {
    public static void main(String[] args) {
        Class<?> c = null;

        try {
            c = Class.forName("kw.Person01");
        }catch (Exception e){
            e.printStackTrace();
        }

        Person01 per = null;
        try {
            per = (Person01) c.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        per.setName("康帅");
        per.setAge(18);
        System.out.println(per);
    }
}

输出:
Exception in thread "main" java.lang.RuntimeException: java.lang.InstantiationException: kw.Person01
at kw.Class_03.main(Class_03.java:51)
Caused by: java.lang.InstantiationException: kw.Person01
at java.lang.Class.newInstance(Class.java:427)
at kw.Class_03.main(Class_03.java:49)
Caused by: java.lang.NoSuchMethodException: kw.Person01.()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.newInstance(Class.java:412)
... 1 more

从以上代码可以发现,因为类中并没有存在无参构造方法,所以是根本无法直接使用newInstance()方法实例化的。因此,建议在使用Class类实例化对象时一定要在类中编写无参构造方法

各种高级应用中都提倡类中存在无参构造方法
在实际Java程序开发中,反射是最为重要的操作原理,在现在的开发设计中大量的应用了反射处理机制,如Struts、Spring 框架等;在大部分的操作中基本上都是操作无参构造方法,所以希望读者在日后使用反射开发时类中一定要保留无参构造方法。

2.调用有参构造实例化对象

​ 也可以通过其他方式进行实例化操作,只是在操作时需要明确地调用类中的构造方法,并将参数传递进去之后才可以进行实例化操作。

操作步骤:

  1. 通过 Class类中的getConstructors()取得本类中的全部构造方法。
  2. 向构造方法中传递一个对象数组进去,里面包含了构造方法中所需的各个参数。
  3. 之后通过Constructor 类实例化对象。

在这里需要使用 Constructor类,表示的是构造方法。

Constructor类中常用方法

序号 方法 类型 描述
1 public int getModifiers() 普通 得到构造方法的修饰符
2 public String getName() 普通 得到构造方法的名称
3 public Class<?>[] getParameterTypes() 普通 得到构造方法中参数的类型
4 public String toString() 普通 返回此构造方法的信息
5 public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException 普通 向构造方法中传递参数实例化对象

调用类中的有参构造

特别注意:

在try-catch语句块中声明的变量,外部不可见

package kw;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;


class Person01{
    private String name;
    private int age;

    public Person01(String name, int age){
        this.setName(name);
        this.setAge(age);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person01{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class Class_03 {
    public static void main(String[] args) {
        Class<?> c = null;

        try {
            c = Class.forName("kw.Person01");
        }catch (Exception e){
            e.printStackTrace();
        }

        Person01 per = null;

        Constructor<?> con[] = null;
        
        /* 下面的语句可以替换成这个,还可以抛出异常
          try {
            con = c.getConstructors();
        } catch (SecurityException e) {
            throw new RuntimeException(e);
        }
        */
        
        /*注意不能这要替换,因为在try语句块中声明的变量,外部是不可见的
           try {
            Constructor<?> con[] = null;
            con = c.getConstructors();
        } catch (SecurityException e) {
            throw new RuntimeException(e);
        }
         
        */
            con = c.getConstructors();
            
        try {
            per = (Person01) con[0].newInstance("康帅", 18);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }

        System.out.println(per);
    }
}

输出:

Person01

​ 以上程序通过Class类取得了一个类中的全部构造方法,并以对象数组的形式返回,因为显式的调用了类中的构造方法,且在Persion类中只有一个构造方法,所以直接取出对象数组中的第一个元素即可(下标为0就表示调用第一个构造方法)。在声明对象数组时,必须考虑到构造方法中参数的类型顺序,所以第一个参数的类型为String,第二个参数的类型为Interger(在使用时可以自动拆箱)。

3.反射的应用--取得类的结构

​ 在实际开发中,以上程序就是反射应用最多的地方。当然,反射机制所提供的功能远不止如此,还可以通过反射得到一个类的完整结构,那么就要使用到 java.lang.reflect 包中的一下几个类。

  1. Constructor:表示类中的构造方法。
  2. Field:表示类中的属性。
  3. Method:表示类中的方法。

这3个类都是AccessibleObject类的子类:

通过以上几个类和Class类共同完成类的反射操作

Persion02.java

package kw;

interface China{
    public static final String NATIONAL = "China";
    public static final String AUTHOR = "康帅";
    public void sayChina();
    public String sayHello(String name, int age);
}

public class Person02 implements China{
    private String name;
    private int age;

    public Person02(){

    }
    public Person02(String name){
        this.name = name;
    }

    public Person02(String name, int age){
        this(name);
        this.setAge(age);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void sayChina(){
        System.out.println("作者:" + AUTHOR + "\t国籍:" + NATIONAL);
    }

    public String sayHello(String name, int age){
        return name + ", 你好!我今年" + age + "岁了。";
    }
}

1.取得所实现的全部接口

​ 要取得一个类所实现的全部接口,则必须使用Class类中的getInterfaces()方法。

getInterfaces()方法定义:

public Class<?>[] getInterfaces()

​ getInterfaces()方法返回一个Class类的对象数组,之后直接利用 Class类中的getName()方法取得类的名称。

取得Person02类实现的全部接口

package kw;

public class GetInterfaceDemo01 {
    public static void main(String[] args) {
        Class<?> c1 = null;

        try {
            c1 = Class.forName("kw.Person02");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        Class<?>[] intfaces = null;
        intfaces = c1.getInterfaces();

        for (int i = 0; i < intfaces.length; i++) {
            System.out.println("实现的接口:" + intfaces[i].getName());
        }

    }
}

输出:
实现的接口:kw.China

​ 因为接口是类的特殊形式,而且一个类可以实现多个接口,所以此时以Class数组的形式将全部的接口对象返回,并利用循环的方式将内容依次输出。

2.取得父类

​ 一个类可以实现多个接口,但是只能继承一个父类,所以如果要取得一个类的父类可以直接使用Class类中的getSuperclass()方法。

getSuperclass() 方法定义如下:

public native Class<? super T> getSuperclass()

​ getSuperClass() 方法返回的是Class实例,和之前得到的接口一样,可以通过getName()方法取得类的名称。

取得Person02 的父类:

package kw;

public class GetSuperClassDemo01 {
    public static void main(String[] args) {
        Class<?> c = null;

        try {
            c = Class.forName("kw.Person02");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        Class<?> c1 = c.getSuperclass();
        System.out.println("父类名称:" + c1.getName());
    }
}

输出:
父类名称:java.lang.Object

Person02 类在编写时没有明确的继承一个父类,所以默认继承Object类。

3.取得全部构造方法

​ 要取得一个类中的全部构造方法,则必须使用Class类中的 getConstructos()方法。

取得Person02类中的全部构造方法:

package kw;
import java.lang.reflect.Constructor;

public class GetConstructorsDemo01 {
    public static void main(String[] args) {
        Constructor<?>[] cons = null;
        Class<?> c = null;
        try {
            c = Class.forName("kw.Person02");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        try {
            cons = c.getConstructors();
        } catch (SecurityException e) {
            throw new RuntimeException(e);
        }
        for (int i = 0; i < cons.length; i++) {
            System.out.println("直接打印构造方法:" + cons[i]);
        }
    }
}

输出:
直接打印构造方法:public kw.Person02(java.lang.String,int)
直接打印构造方法:public kw.Person02(java.lang.String)
直接打印构造方法:public kw.Person02()

​ 以上确实取得了全部的构造方法,取得时是直接通过输出Constructor 对象得到的完整信息,是比较全的信息。当然,用户也可以自己手动拼凑出信息。可以通过Constructor类中的三个方法取得一个类的全部构造方法。

取得一个类的全部构造方法 【手动拼凑】:

package kw;
import java.lang.reflect.Constructor;

public class GetConstructorsDemo02 {
    public static void main(String[] args) {
        Class<?> c = null;

        try {
            c = Class.forName("kw.Person02");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        
        Constructor<?>[] constructors = c.getConstructors();

        for (Constructor con :
                constructors) {
            Class<?> p[] = con.getParameterTypes(); //列出构造方法中的参数类型
            System.out.print("构造方法:");
            System.out.print(con.getModifiers() + " "); //取出权限
            System.out.print(con.getName() + " "); //取出名称
            System.out.print("("); //输出(
            for (int i = 0; i < p.length; i++) {
                System.out.print(p[i].getName() + " arg" + i);
                if (i < p.length - 1){
                    System.out.print(",");
                }
            }
            System.out.print("){}\n");
        }
    }
}

输出:
构造方法:1 kw.Person02 (java.lang.String arg0,int arg1){}
构造方法:1 kw.Person02 (java.lang.String arg0){}
构造方法:1 kw.Person02 (){}

​ 从程序的运行结果可以发现,已经取得了构造方法的方法名称及参数类型,但是在取得权限时却发现返回的是一个数字而不是public。这是因为在整个Java中对于方法的修饰符是使用一定的数字表示出来的,而如果要把这个数字还原成用户可以看懂的关键字,则必须依靠Modifier类完成,此类定义在 java.lang.reflect 包中。直接使用 Modifier类中的以下方法即可还原修饰符:

public static String toString(int mod)

使用Modifier还原修饰符

package kw;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

public class GetConstructorsDemo02 {
    public static void main(String[] args) {
        Class<?> c = null;

        try {
            c = Class.forName("kw.Person02");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        
        Constructor<?>[] constructors = c.getConstructors();

        for (Constructor con :
                constructors) {
            Class<?> p[] = con.getParameterTypes(); //列出构造方法中的参数类型
            System.out.print("构造方法:");
            int mo = con.getModifiers();   //取出权限
            System.out.print(Modifier.toString(mo) + " "); //还原修饰符
            System.out.print(con.getName() + " "); //取出名称
            System.out.print("("); //输出(
            for (int i = 0; i < p.length; i++) {
                System.out.print(p[i].getName() + " arg" + i);
                if (i < p.length - 1){
                    System.out.print(",");
                }
            }
            System.out.print("){}\n");
        }
    }
}

输出:
构造方法:public kw.Person02 (java.lang.String arg0,int arg1){}
构造方法:public kw.Person02 (java.lang.String arg0){}
构造方法:public kw.Person02 (){}

​ 从程序的输出结果可以发现,使用Modifier将取出来的修饰符数字还原成了用户可以看懂的权限修饰符。

4.取的全部方法

​ 要取得一个类中的全部方法,可以使用 Class类中的 getMethods()方法,此方法返回一个 Method 类的对象数组。而如果要想进一步取的方法的具体信息,例如方法的参数、抛出的异常声明等,则就必须依靠 Method类。

Method类中的常用方法

序号 方法 类型 描述
1 public int getModifiers() 普通 取的本方法的访问修饰符
2 public String getName() 普通 取得方法的名称
3 public Class<?>[] getParameterTypes() 普通 得到方法的全部参数的类型
4 public Class<?> getReturnType() 普通 得到方法的返回值类型
5 public Class<?>[] getExceptionTypes() 普通 得到一个方法的全部抛出异常
6 public Object invoke(Object obj, Object... args)throws IllegalAccessException, IllegalArgumentException,InvocationTargetException 普通 通过反射调用类中的方法,此方法在后面进行介绍

取得一个类的全部方法定义:

package kw;
import java.lang.reflect.Modifier;
import java.lang.reflect.Method;

public class GetMethodDemo01 {
    public static void main(String[] args) {
        Class<?> c = null;
        try {
            c = Class.forName("kw.Person02");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        Method[] methods = c.getMethods();

        for (Method m:
             methods) {
            System.out.print("方法:");
            int mo = m.getModifiers();
            System.out.print(Modifier.toString(mo) + " ");  //方法权限
            System.out.print(m.getReturnType().getName() + " "); // 方法返回值类型
            System.out.print(m.getName() + " ");  //方法名称
            System.out.print("(");
            Class<?>[] parameters = m.getParameterTypes();
            for (int i = 0; i < parameters.length; i++) {
                System.out.print(parameters[i].getName() + " arg" + i);   // 方法每个参数的类型
                if (i < parameters.length - 1) {
                    System.out.print(",");
                }
            }
            System.out.print(")");

            Class<?>[] exceptons = m.getExceptionTypes();  // 方法抛出的异常
            if (exceptons.length > 0){
                System.out.print(" throws ");
                for (int i = 0; i < exceptons.length; i++) {
                    System.out.print(exceptons[i].getName());
                    if (i < exceptons.length - 1) {
                        System.out.print(", ");
                    }
                }
            }
            System.out.println("{}");
        }
    }
}

输出:
方法:public java.lang.String getName (){}
方法:public void setName (java.lang.String arg0){}
方法:public void sayChina (){}
方法:public int getAge (){}
方法:public java.lang.String sayHello (java.lang.String arg0,int arg1){}
方法:public void setAge (int arg0){}
方法:public final void wait () throws java.lang.InterruptedException{}
方法:public final void wait (long arg0,int arg1) throws java.lang.InterruptedException{}
方法:public final native void wait (long arg0) throws java.lang.InterruptedException{}
方法:public boolean equals (java.lang.Object arg0){}
方法:public java.lang.String toString (){}
方法:public native int hashCode (){}
方法:public final native java.lang.Class getClass (){}
方法:public final native void notify (){}
方法:public final native void notifyAll (){}

​ 从程序运行结果来看,程序不仅可以将 Person02类的方法输出,也把从 Object类中继承而来的方法同样进行了输出。

扩展:

开发工具就是利用反射的原理

​ 在使用IDE进行程序开发时,基本上都是带随笔提示功能的,即使用一个 "." 就可以找到一个类的全部方法,实际上这个功能就是利用此种方式完成的。

5.取得全部属性

​ 在反射操作中也同样可以取得一个类中的全部属性,但是在取得属性时有以下两种不同的操作。

  1. 得到实现的接口或父类中的公共属性: public Field[] getFields() throws SecurityException
  2. 得到本类中的全部属性: public Field[] getDeclaredFields() throws SecurityException

​ 以上方法返回的都是Field 的数组,每一个Field对象表示类中的一个属性,而要想取得属性的进一步信息,就需要使用 Field类中的方法。

Field类的常用方法

序号 方法 类型 描述
1 public Object get(Object obj)throws IllegalArgumentException, IllegalAccessException 普通 得到一个对象中属性的具体内容
2 public void set(Object obj, Object value)throws IllegalArgumentException, IllegalAccessException 普通 设置指定对象中属性的具体内容
3 public int getModifiers() 普通 得到属性的修饰符
4 public String getName() 普通 返回此属性的名称
5 public boolean isAccessible() 普通 判断此属性是否可被外部访问(已弃用)
6 public void setAccessible(boolean flag) throws SecurityException 普通 设置一个属性是否可被外部访问(以弃用)
7 public String toString() 普通 返回此Field类的信息

4.Java反射机制的深入应用

​ 反射除了可以取得一个类的完整结构外,还可以调用类中的指定方法或指定属性,并且可以通过反射完成对数组的操作。

1,通过反射调用类中的方法

​ 如果通过反射调用类中的方法可以通过Method类完成,操作如下:

  1. 通过Class类的 getMethod(String name, Class...parameterTypes)方法取得一个Method的对象,并设置此方法操作时所需要的参数类型。
  2. 之后可以使用invoke()进行调用,并向方法中传递要设置的参数。

调用Person02类中的sayChina()方法:

package kw;
import java.lang.reflect.Method;

public class InvokeDemo01 {
    public static void main(String[] args) {
        Class<?> c = null;
        try {
            c = Class.forName("kw.Person02");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        try {
            Method met = c.getMethod("sayChina");  //此方法没有参数
            met.invoke(c.newInstance());                // 调用方法,必须传递对象实例
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

输出:
作者:康帅 国籍:China

​ 以上程序中通过Class类的getMethod()方法根据类中的方法名称取得Method对象,并通过invoke()调用指定的方法。在使用invoke()方法时必须传入一个类的实例化对象,因为在sayChina()方法上没有任何的参数,所以此处没有设置参数类型和参数内容,本程序操作示意图如下:

调用需要传递参数的方法:Person02类中的sayHello(String name, int age)

package kw;
import java.lang.reflect.Method;

public class InvokeDemo01 {
    public static void main(String[] args) {
        Class<?> c = null;
        try {
            c = Class.forName("kw.Person02");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        try {
            Method met = c.getMethod("sayHello", String.class, int.class);  //此方法有参数
            String rv = null;
           // 调用方法,必须传递对象实例, 同时传递两个参数值
            rv = (String) met.invoke(c.newInstance(), "康帅", 18);
            System.out.println(rv);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

输出:
康帅, 你好!我今年18岁了。

​ 以上程序中,因为sayHello()方法本身要接收两个参数,所以在使用getMethod()方法调用时除了要指定调用的方法名称外,也需要指定参数的类型,由于sayHello()方法调用完之后存在返回值,而且返回值的类型是String,所以使用了一个字符串接收方法返回的内容。

2.调用setter和getter方法

​ 使用反射调用类中的 setter 和 getter 方法。

调用setter 及 getter 方法:

package kw;
import java.lang.reflect.Method;

public class InvokeSetGetDemo01 {
    public static void main(String[] args) {
//        实例化class 对象
        Class<?> c = null;
        Object obj = null;
        try {
            c = Class.forName("kw.Person02");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
//        实例化操作对象
        try{
            obj = c.newInstance();
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.print("姓名:\t");
        setter(obj, "name", "康帅", String.class);
        getter(obj, "name");
        System.out.print("年龄:\t");
        setter(obj, "age", 18, int.class);
        getter(obj, "age");
    }

    /**
     * @param obj 操作的对象
     * @param att 操作的属性
     * @param value 设置的值
     * @param type 参数的类型
     */

    public static void setter(Object obj, String att, Object value, Class<?> type){
        try{
            Method met = obj.getClass().getMethod("set" + initStr(att), type);
            met.invoke(obj, value);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void getter(Object obj, String att){
        try {
            Method met = obj.getClass().getMethod("get" + initStr(att));
            System.out.println(met.invoke(obj));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static String initStr(String old){
        String str = old.substring(0, 1).toUpperCase() + old.substring(1);
        return str;
    }


}

输出:

姓名: 康帅
年龄: 18

3.通过反射操作属性

​ 在反射操作中虽然可以使用 Method 调用类中的 setter 和 getter方法设置和取得属性,但是这样操作毕竟很麻烦,所以在反射机制中也可以直接通过 Field类操作类中的属性,通过 Field 类提供的 set() 和 get() 方法就可以完成设置和取得属性内容的操作。但是在操作前首先要注意的是,在类中所有的属性已经设置成了私有的访问权限,所以在使用set() 和 get() 方法时首先要使用 Filed 类中的 setAccessible(true) 方法将需要操作的属性设置成可以被外部访问。

package kw;
import java.lang.reflect.Field;

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

        Class<?> c = null;
        Object obj = null;
        c = Class.forName("kw.Person02");
        obj = c.newInstance();

        Field nameField = null;
        Field ageField = null;

        nameField = c.getDeclaredField("name");
        nameField.setAccessible(true); //将name属性设置为可以被外部访问
        nameField.set(obj, "康帅");

        ageField = c.getDeclaredField("age");
        ageField.setAccessible(true);
        ageField.set(obj, 18);

        System.out.println("姓名:" + nameField.get(obj));
        System.out.println("年龄:" + ageField.get(obj));
    }
}

输出:

姓名:康帅
年龄:18

注意:
在使用反射操作属性时最好通过setter和getter方法

​ 以上程序是属于扩大类属性的访问权限后直接操作属性,所以在Person02类中并不需要编写setter()和getter()方法,但是在开发中调用属性是要使用setter 和 getter 方法,这一点在之前讲解private关键字时已经为读者详细的解释过,所以,以后再开发时一定要注意:不要直接操作属性,而是通过setter和getter方法调用。

4.通过反射操作数组

​ 反射机制不仅可以用在类上,还可以应用在任意的引用数据类型的数据上,当然,这也包含数组,既可以使用反射操作数组。可以通过Class类的以下方法取得一个数组的Class对象

public native Class<?> getComponentType();

​ 在反射操作包 java.lang.reflect中使用Array类表示一个数组,可以通过此类获取数组的长度,取得数组内容的操作。

Array类的常用方法

序号 方法 类型 描述
1 public static native Object get(Object array, int index)throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 普通 根据下标取得数组内容
2 public static Object newInstance(Class<?> componentType, int length) throws NegativeArraySizeException 普通 根据已有的数组类型开辟新的数组对象
3 public static native void set(Object array, int index, Object value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException 普通 修改指定位置的内容

取得数组信息并修改数组内容

package kw;
import java.lang.reflect.Array;

public class ClassArrayDemo01 {
    public static void main(String[] args) throws Exception{
        int[] temp = {1, 2, 3};
        Class<?> c = temp.getClass().getComponentType(); //取得数组的Class对象
        System.out.println("类型:" + c.getName());  //取得数组类型名称
        System.out.println("长度:" + Array.getLength(temp));
        System.out.println("第一个内容:" + Array.get(temp, 0));
        Array.set(temp, 0, 6);  //修改第一个内容
        System.out.println("第一个内容:" + Array.get(temp, 0));
    }
}

输出:

类型:int
长度:3
第一个内容:1
第一个内容:6

​ 在应用中还可以通过Array类根据已有的数组类型来开辟新的数组对象,下面使用Array完成一个可以修改已有数组大小的功能。

修改数组大小

java.lang.Class.isArray() 确定该Class对象表示一个数组类。

package kw;
import java.lang.reflect.Array;

public class ChangeArrayDemo01 {
    public static void main(String[] args) throws Exception {
        int[] temp = {1, 2, 3};
        int[] newTemp = (int[]) arrayInc(temp, 5);    //扩大数组长度
        print(newTemp);             //打印数组信息
        System.out.println("\n---------------------------");
        String[] str = {"good", "nice", "cool"};
        String[] newStr = (String[]) arrayInc(str, 8);
        print(newStr);
    }

    public static Object arrayInc(Object obj, int len){  //修改数组大小
        Class<?> c = obj.getClass();  //通过数组得到Class对象
        Class<?> arr = c.getComponentType(); //得到数组的Class对象
        Object newO = Array.newInstance(arr, len); //重新开辟新的数组大小
        int co = Array.getLength(obj); // 取得数组长度
        System.arraycopy(obj, 0, newO, 0, co); //复制数组内容
        return newO;
    }

    public static void print(Object obj){  //输出数组
        Class<?> c = obj.getClass();
        if (!c.isArray()){                //判断是否是数组
            return;                     //不是则返回
        }
        Class<?> arr = c.getComponentType(); //取得数组的Class
        System.out.println(arr.getName() + "数组的长度是:" + Array.getLength(obj)); //输出数组信息
        for (int i = 0; i < Array.getLength(obj); i++) {  //循环输出
            System.out.print(Array.get(obj, i) + "、");   //通过Array输出
        }
    }
}

输出:

int数组的长度是:5

1、2、3、0、0、

java.lang.String数组的长度是:8
good、nice、cool、null、null、null、null、null、

5.动态代理