美文网首页
JavaSE 基础学习之六 —— Java 的反射操作

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

作者: 琦小虾 | 来源:发表于2018-06-27 23:08 被阅读0次

    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<?>... parameterTypes):
        • 参数:
          • String name: 方法名;
          • Class<?>...parameterTypes: 形参的Class类型对象
      • public Method getDeclaredMethod(String name,Class<?>... parameterTypes):

    方法的调用是通过 Method 类的 invoke 方法实现的。它的定义为:

    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<?> getType(): 返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型。
    • void setAccessible(boolean flag): 将此对象的 accessible 标志设置为指示的布尔值。

    下面用例程说明:

    • 获取一个成员变量对象
    • 如何进行成员变量的反射操作
      • field.get(target);
      • field.set(target, newValue);


    现在有类 User.java 如下:

    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<?> getConstructor(Class<?>... parameterTypes): 返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法
    • Constuctor<?>[] getDeclaredConstructors(): 返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法
    • T newInstance(): 创建此 Class 对象所表示的类的一个新实例。

    下面依旧用上面的中的 User.java 举例说明:

    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 (?, ?, ?)
    

    相关文章

      网友评论

          本文标题:JavaSE 基础学习之六 —— Java 的反射操作

          本文链接:https://www.haomeiwen.com/subject/julwyftx.html