JavaSE 基础学习之六 —— Java 的反射操作

六. JavaSE 基础学习之六 —— Java 的反射操作

1.java.lang.Class 类

参考地址: 《Java源码解析(2) —— Class(1)》 《Class类详解》


万事万物都是对象。我们平常接触到的类,本身也是一种对象,它的类型是 Class,也可以说 Class 是类的类型,即类类型 (Class Type);任何一个类,都是 java.lang.Class 的一个实例对象。

Class 是 Java 的基本类之一,也是反射机制的基础,它的意义是类的抽象,即对“类”进行描述。比如获得类的属性的方法 getField,有获得该类的所有方法、所有公有方法的方法 getMethods, getDeclaredMethods。同时,Class 也是 Java 类型中最重要的一种,表示原始类型(引用类型)及基本类型。

(1) 如何表示这个实例对象?

  • 第一种:类名.Class
  • 第二种:对象.getClass()
  • 第三种:Class.forName(“类的全称”);

编译时刻加载类,称为静态加载类,比如通过 new 关键字加载的类。 在运行时刻加载类,称为动态加载类Class.forName() 方法就是 Java 语言中唯一一种动态加载的方法。动态加载类在编译不会报错,在运行时才会加载,使用接口标准能更方便动态加载类的实现。所以功能性的类尽量使用动态加载,而不用静态加载。

有了类的类类型,就可以获取类的所有信息;具体如下例所示:

:写一个方法,接受一个对象,然后打印该对象所属类的所有信息;

package homework4_27;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class homework1 {
    public static void main(String[] args) {
        Object o = new Object();
        printClassMsg("hello");
    }

    // 写一个方法,接受一个对象,然后打印该对象所属类的所有信息; (Reflect.Demo2.java)
    // 打印所有方法的返回值类型、方法名,参数类型列表      
    // 得到所有的声明的成员变量,打印成员变量的类型,名称
    public static void printClassMsg(Object obj){
        // 获取类类型
        Class c = obj.getClass();
        System.out.println("类类型名称"+c.getName());

        // 获取方法列表,包括方法名,返回值类型,参数列表
        System.out.println("======================");
        // getMethods(): 获取所有公有的方法;
        Method[] ms = c.getMethods();
        System.out.println(c.getName() + " 的类方法:");
        for(Method m : ms) {
            System.out.println("------------");
            System.out.println("  类方法名:" + c.getName() + "." + m.getName());
            System.out.println("  返回值类型:" + m.getReturnType().getName());
            System.out.println("  参数类型:");
            Class[] c_params = m.getParameterTypes();
            for(Class param : c_params) {
                System.out.println("    " + param.getName());
            }
        }

        // 获取类中已经声明的成员变量
        System.out.println("======================");
        // 获取所有声明的成员变量
        // c.getFields();

        // 获取该类的所有成员变量
        Field[] f = c.getDeclaredFields();
        System.out.println(c.getName() + " 的成员变量:");
        for(Field fie : f){
            System.out.println("------------");
            System.out.println("  变量类型:" + fie.getType().getName());
            System.out.println("  变量名称:" + fie.getName());
        }
    }
}

(2) 框架的原理

Java 框架就是一些类和接口的集合,通过这些类和接口协调来完成一系列的程序实现。框架又叫做开发中的半成品,它通过了编译,打成了 jar 包,但不能提供整个 WEB 应用程序的所有东西。但是有了框架,我们就可以集中精力进行业务逻辑的开发,同时框架会创建我们写的类的实例对象,并调用我们写的方法;这样我们就不用去关心它的技术实现以及一些辅助的业务逻辑。 说白了 Java 框架就是封装好方便程序员操作的类,在运行时动态加载我们的类(通过 Class.forName(类名) 方法),并创建对象调用方法。这样可以使项目的开发更简单,维护起来也更容易。

2. Java 的反射机制

参考网址: 《Java基础之—反射(非常重要)》 《Java源码解析(2) —— Class(1)》


前面提到框架是开发中的半成品,它可以在运行过程中加载实例,并填充业务。在框架中,反射是框架设计的灵魂。

Java 反射机制是在运行状态中进行的,它把 Java 类中的各种成分映射成一个个的 Java 对象。使用反射的前提条件,是首先必须得到字节码的 Class,它用于表示 .class 文件。通过动态获取信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。具体对于类和对象:

  • 任意一个,都能够知道这个类的所有属性和方法;
  • 任意一个对象,都能够调用它的任意一个方法和属性;

要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是 Class 类中的方法。所以先要获取到每一个字节码文件 *.class 对应的 Class 类型的对象。在 《Java基础之—反射(非常重要)》 一文中绘制了反射加载的过程:

用好反射,关键在于能够调用任意类或对象的方法和属性。我们可以通过 java.lang.reflect.Method 调用任意的方法,通过 java.lang.reflect.Field 调用任意的属性。

(1) java.lang.reflect.Method 方法的反射

  • 获取一个方法对象
    • 方法名称
    • 参数列表(的类型的类类型)
  • 如何用方法的反射操作方法?
    • method.invoke(target, 参数列表)

方法本身也可以用对象的形式表现出来,它被封装在 java.lang.reflect.Method 中。调用一个方法对象,关键在于两部分:

  • 方法签名
  • 参数列表的类型的类类型

举例说明:假设已经定义了方法 methodFunction(int a, double b, boolean c),对于这个方法,methodFunction 是它的方法签名,(int.class, double.class, boolean.class) 是它的参数列表的类型的类类型。

简要介绍几种不同的获取成员方法的方式:

  • 批量获取成员方法:
    • public Method[] getMethods(): 获取所有”公有方法”;该方法包含了父类的方法,也包含 Object 类;
    • public Method[] getDeclaredMethods(): 获取所有的成员方法,包括私有的,但不包括继承的;
  • 获取单个成员方法:
    • public Method getMethod(String name,Class
public Object invoke(Object obj,Object... args) {...}

参数说明:

  • Object obj: 要调用方法的对象;
  • Object… args: 调用方式时所传递的实参;

关于方法的反射,例程如下:

:写三个参数列表不同的 f 方法,获得其方法的反射:

package reflect;

import java.lang.reflect.Method;

class A{
    public void f(){
        System.out.println("helloworld");
    }
    public int f(int a ,int b){
        return a+b;
    }
    public String f(String a,String b,int c){
        return a+","+b+","+c;
    }
}

public class Demo3 {
    public static void main(String[] args) {
        A a1 = new A();
        Class c = a1.getClass();
        try {
            // 获取名为 f 的方法,参数列表的类型分别为 (int, int)
            Method method1 = c.getMethod("f",int.class,int.class);
            System.out.println(a1.f(10, 10));
            // 传入参数 (10, 10),输出 f(int, int) 的计算结果
            int n = (Integer)method1.invoke(a1, 10,10);
            System.out.println(n);

            // 获取无参数的 f 方法
            System.out.println("================");
            Method method2 = c.getMethod("f");
            a1.f();
            method2.invoke(a1);

            // 获取参数列表为 (String, String, int) 类型的 f 方法
            System.out.println("================");
            Method method3 = c.getMethod("f", String.class,String.class,int.class);
            System.out.println(a1.f("hello", "world",100));
            String ss = (String)method3.invoke(a1, "hello","world",100);
            System.out.println(ss);     
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

该例程中首先定义了一个类 A,其中定义了几个名称为 f 的方法,都有不同的参数列表和返回值。在 getMethod 方法中传入了不同的参数列表,就可以准确的获取我们想要的具体的 f 方法。

(2) java.lang.reflect.Field 成员变量的反射

Class 类中重要的方法:

  • Field[] getDeclaredFields(): 返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。
  • Field getDeclaredField(String name): 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。

Field 类中重要的方法:

  • Class\
package reflect;

public class User {
    private String name;
    private int age;
    public User() {}
    public User(String name, int age) {
        super();
        this.name = name;
        this.age = 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 String toString() {
        return "User [name=" + name + ", age=" + age + "]";
    }
}

写一个方法,接受一个对象:

  • 如果该对象有字符串属性,把其值改成大写;
  • 如果该对象有 int 属性,把值都加 100;
package reflect;

import java.lang.reflect.Field;

public class Demo6 {
    public static void main(String[] args) {
        User user = new User("zhangsan", 20);
        try {
            changeValue(user);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(user);
    }
    public static void changeValue(Object obj) throws Exception{
        Class c = obj.getClass();
        // 获取成员变量数组
        Field[] fs = c.getDeclaredFields();
        for (Field field : fs) {
            // 当前类型为 String
            if(field.getType() == String.class){
                // java 中,在类的外面获取此类的私有成员变量的值时,需要先用 setAccessible(true) 对变量进行权限更改设定;
                field.setAccessible(true);
                // 获取 String field 的值,并将其转为大写;
                String oldValue = (String)field.get(obj);
                field.set(obj, oldValue.toUpperCase()); 
            }
            // 当前类型为 int
            if(field.getType()==int.class){
                field.setAccessible(true);
                // 获取 int 类型 field 的值,并将其加 100
                int oldValue = field.getInt(obj);
                field.set(obj, oldValue+100);
            }
        }
    }
}

(3) java.lang.reflect.Constructor 构造函数的反射

  • constructor.getConstructor:获取某个构造函数
  • constructor.newInstance(参数):构建函数传入参数的反射操作

Class 类关于构造函数的重要方法:

  • Constructor
package reflect;

import java.lang.reflect.Constructor;

public class Demo7 {
    public static void main(String[] args) {
        Class c = User.class;
        try {
            // 获取 User 类的无参构造函数
            Constructor<User> cons = c.getConstructor();
            // 构造无参的 User 实例对象
            User u = cons.newInstance();
            System.out.println(u);

            // 获取 User(String, int) 的 User 构造函数
            Constructor<User> cons2 = c.getConstructor(String.class,int.class);
            // 构造 User(String, int) 的实例对象
            User u2 = cons2.newInstance("lisi",20);
            System.out.println(u2);
        }  catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5. 数组的反射

前面说过 Java 中万事万物都是对象,其中也包含数组。数组也是实例对象,而且数组的类类型只和类型和维数相关。数组在反射机制中的类是 java.lang.reflect.Array。

依旧有上例中的 User.java,下面用例程说明:

public class Demo8 {
    public static void main(String[] args) {
        int[] a = {1,2,3,4,5};
        int[] b = {5,6,7,8};
        int[][] c = {
                {1,2,3},
                {4,5,6,7}
        };
        String[][] str = {
                {"aaa","bbb"},
                {"ccc","ddd","eee"}
        };

        Class c1 = a.getClass();
        Class c2 = b.getClass();
        Class c3 = c.getClass();
        System.out.println(c1==c2); // true
        System.out.println(c1==int[].class); // true
        System.out.println(c1.getName()+","+c2.getName()); // [I,[I
        System.out.println(str.getClass().getName()); // [[Ljava.lang.String;
        System.out.println(str.getClass()==String[][].class); // true
    }
}

输出结果为:

true
true
[I,[I
[[Ljava.lang.String;
true

当然通过反射的方法来创建数组的用法是很少见的,其实也是多余的。为什么不直接通过 new 来创建数组呢?反射创建数组不仅速度没有 new 快,而且写的程序也不易读,还不如 new 来的直接。事实上通过反射创建数组确实很少见。


练习: 写一个函数,签名与参数列表为:public String getSql(Object obj);

  • 如果传递的是 User 对象
    • User 有 (name, age, sex) 属性
    • 写一个数据库操作的命令行:返回 insert into User(name, age, sex) values (?, ?, ?)
public class Demo11 {
    public static void main(String[] args) {
        String res = getSql(new User());
        System.out.println(res);
    }
    public static String getSql(Object obj) {
        StringBuffer s = new StringBuffer();
        s.append("insert into ");
        Class c = obj.getClass();
        // 不包含包名的类名称
        String className = c.getSimpleName();
        s.append(className).append("(");

        //==============
        // 获取类的成员变量,并用逗号与括号分隔存储
        // 如:(name, age, sex)
        //==============
        Field[] fs = c.getDeclaredFields();
        // 类的所有成员变量用逗号','隔开
        for(int i = 0; i < fs.length; i++) {
            s = i == 0 ? s.append(fs[i].getName()) : s.append(", ").append(fs[i].getName());
        }
        // 接上相同个数的 '?' 列表
        s.append(") values ").append(getString(fs.length));
        return s.toString();
    }
    public static String getString(int length) {
        StringBuilder s = new StringBuilder();
        s.append("(");
        for(int i = 0; i < length; i++)
            s = i == 0 ? s.append("?") : s.append(", ?");
            return s.append(")").toString();
    }
}

输出为:

insert into User(name, age, sex) values (?, ?, ?)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券