前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaSE 基础学习之六 —— Java 的反射操作

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

作者头像
剑影啸清寒
发布2019-05-26 19:47:13
4640
发布2019-05-26 19:47:13
举报

六. 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 (?, ?, ?)
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年06月27日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 六. JavaSE 基础学习之六 —— Java 的反射操作
    • 1.java.lang.Class 类
      • (1) 如何表示这个实例对象?
      • (2) 框架的原理
    • 2. Java 的反射机制
      • (1) java.lang.reflect.Method 方法的反射
      • (2) java.lang.reflect.Field 成员变量的反射
      • (3) java.lang.reflect.Constructor 构造函数的反射
    • 5. 数组的反射
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档