java反射机制认知

java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类的信息以及动态调用对象的方法的功能称为java语言的反射机制Reflection。

这就说明:Java程序可以加载一个编译期间完全未知的class,获悉其完整构造,并生成其对象实体、或对其fields设值、或唤起其methods。虽然java并不是动态语言。

如何达到上述目的,是本文探讨的内容。本文将介绍Reflection APIs,java.lang.Class,以及java.lang.reflect中的Method、Field、Constructor等类。

java.lang.Class是反射入口。java中一切皆对象,继承自object。每一个对象都有自己的类型,通过getClass()得到一个java.lang.Class对象,(基本类型通过字面值.class获得,也叫类标记,如:int.class)这个Class对象中包含了与类型有关的信息,包括其字段、方法、父类或接口等。

类的装载

在正式开始学习反射之前,我们来了解一些类的装载的知识。

类装载器把一个类装入JVM 中,要经过以下步骤:

1.装载:查找和导入Class 文件;

通过一个类的全限定名来获取定义此类的二进制字节流.然后将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构.最后在Java堆中生成一个代表这个类的java.lang.class对像,作为方法区的数据入口.

2.链接:执行校验、准备和解析步骤,其中解析步骤是可以选择的:

a)校验:检查载入Class 文件数据的正确性;

b)准备:给类的静态变量分配存储空间;

c)解析:将符号引用转成直接引用;

3.初始化:对类的静态变量、静态代码块执行初始化工作。

网上找到一个例子,可以看到一个类在什么时候被初始化:

package study.javacore.test;

import java.util.Random;
import klg.utils.MyTestUtil;

class Initable {
    static final int staticFinal = 47;
    static final int staticFinal2 = InitClassTest.rand.nextInt(100);
    static {
        System.out.println("Initialization Initable");
    }
}

class Initable2 {
    static int staticNoFinal = 147;
    static {
        System.out.println("Initialization Initable2");
    }
}

class Initable3 {
    static int staticNoFinal = 74;
    static {
        System.out.println("Initialization Initable3");
    }
}

public class InitClassTest {
    public static Random rand = new Random(47);

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class clazz = Initable.class; // 不会引起初始化
        MyTestUtil.print(clazz.getDeclaredFields());//读取字段信息不会初始化,获取方法信息也一样
        //当然如果取得字段的值就已经是初化了,调用方法也一样。因为这些操作都需要一个对象。
        //Initable initable=(Initable) clazz.newInstance(); //这个必然会导致Initable初始化
        System.out.println("after creating Initable reference");
       System.out.println(Initable.staticFinal); // 引用编译器常量不会引起初始化
        System.out.println(Initable.staticFinal2); // 引起初始化
        System.out.println(Initable2.staticNoFinal); // 引用非编译期常量会引起初始化
        Class initable3 = Class.forName("study.javacore.test.Initable3"); // 默认会引起初始化
        System.out.println("after creating Initable3 reference");
       System.out.println(Initable3.staticNoFinal);// 前面已经初始化此处不用再初始化        
    }
}

Class对象

Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。

Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。

对于我们编写的每个类,它们都有一个Class 对象。(更恰当地说,是保存在一个完全同名的.class 文件中)。在运行期,一旦我们想生成那个类的一个对象,用于执行程序的Java 虚拟机(JVM)首先就会检查那个类型的Class 对象是否已经载入。若尚未载入,JVM 就会查找同名的.class 文件,并将其载入。所以Java 程序启动时并不是完全载入的,这一点与许多传统语言都不同。一旦那个类型的Class 对象进入内存,就用它创建那一类型的所有对象。

取得Class对象的句柄

取得Class对象的句柄有三种方式:

  • 未知类名:Class.forName(className);
  • 已知对象名:new Person().getClass();
  • 已知类名:Person.class;

 

1.Class.forName(className)

实际上是调 Class.forName(className, true, currentLoader)。注意第二个参数,是指Class被loading后必须被初始化。

2.实例对象.getClass()。

说明:对类进行静态初始化、非静态初始化;返回引用object运行时真正(子类对象的句柄可能会赋给父类对象的句柄<多态>)所属的类的Class的对象。

Person person=new Person();
Object op=person;
System.out.println(op.getClass());//class study.javacore.test.Person

这里有一个特例,基本类型在向上类型转换以后,getClass()得到的是基本类型对应的包装类型。

int i=2;
Object oi=i;
System.out.println(oi.getClass());//class java.lang.Integer

3.类名.class,又叫类标记。

说明: JVM将使用类装载器, 将类装入内存(前提是:类还没有装入内存),不做类的初始化工作。

这样做不仅更加简单,而且更安全,因为它会在编译期间得到检查。由于它取消了对方法调用的需要,所以执行的效率也会更高。类标记不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。除此以外,针对每种基本数据类型的封装器类,它还存在一个名为TYPE 的标准字段:例如,int.class==Integer.TYPE

之所以说是句柄,是因为这三种方式得到Class对象是同一个:

System.out.println(Class.forName("java.lang.Object")==Object.class);//true
System.out.println(Object.class==new Object().getClass()); //true

认识反射的api

我们主要从4个方面认识反射的api

  • 获取类的基本信息java.lang.Class
  • 获取类的实例java.lang.Class和java.lang.reflect.Constructor<T>
  • 操作实例的属性java.lang.reflect.Field
  • 调用实例的方法java.lang.reflect.Method
  • 要记住一切都是由Class对象开始,java.lang.Class是反射入口。

获取类的基本信息

我们可以通过一个类的Class对象了解该类的基本信息。

Java class 内部模块

Java class 内部模块说明

相应之Reflection API,多半为Class methods。

返回值类型(return type)

package

class隶属哪个package

getPackage()

Package

field

class的属性

按访问权限分为:

所有、可访问

getDeclaredFields() ;

getDeclaredField(String name)  ;

 

 Field[]

 Field

method

class的方法

按访问权限分为:

所有、可访问

 

getMethods() ;

getMethod(String name, Class<?>...parameterTypes);  

 Method[]

 Method

modifier

class(或methods, fields)的属性

int getModifiers()

Modifier.toString(int)

Modifier.isInterface(int)

int

String

bool

class name or interface name

class/interface

名称getName()

String

type parameters

参数化类型的名称

getTypeParameters()

TypeVariable

<Class>[]

base class

base class(只可能一个)

getSuperClass()

Class或null

implemented interfaces

实现有哪些interfaces

getInterfaces()

Class[]

inner classes

内部classes

getDeclaredClasses()

Class[]

outer class

如果我们观察的class 本身是inner classes,那么相对它就会有个outer class。

getDeclaringClass()

Class

下面我们来实践一下:

Person.java

class Person {
    private int age;
    private String name;

    public Person() {
        super();
    }

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public void say(){
        System.out.println("Person say!");
    }
    
    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 "Person [age=" + age + ", name=" + name + "]";
    }
}

测试:注:MyTestUtil是我用反射写的用于打印对象的属性值工具类。

public static void main(String[] args) {
        Class demo1 = null;
        try {
            demo1 = Class.forName("study.javacore.test.Person");
        } catch (ClassNotFoundException e1) {
            e1.printStackTrace();
        }
       Class demo = Person.class;
       System.out.println(demo1 == demo);// true

       System.out.println(demo.getPackage());// 包名
        System.out.println(demo.getModifiers());// 访问权限修饰符,0就是没有
        MyTestUtil.print(demo.getConstructors());// 构成方法
        MyTestUtil.print(demo.getDeclaredFields());// 字段信息
        MyTestUtil.print(demo.getMethods());// 方法信息
    }

获取类的实例

获取累的实例有两种方法:

  • 通过Class对象的newInstance方法,创建此 Class 对象所表示的类的一个新实例。
try {
            // 调用的是无参的构成方法,如果没有无参数的构造方法报错。
            Person person  =(Person) demo.newInstance();
            person.setAge(10);
            person.setName("klguang");
            MyTestUtil.printWithSign("person", person);
        } catch (Exception e) {
            System.out.println("Class newInstance wrong!!!");
            e.printStackTrace();
        }
  • 通过Constructor对象,获取类的实例。
try {
            Class[] types = new Class[] { int.class, String.class };
            Constructor<Person> constructor = demo.getConstructor(types);
            Person person2 = constructor.newInstance(20, "klguang");
            MyTestUtil.printWithSign("person2", person2);
        } catch (Exception e) {
            System.out.println("::constructor wrong!!!");
            e.printStackTrace();
        }

操作实例的属性

可通过Class对象的getDeclaredFields() 或getDeclaredField(String name) 方法取得字段Field对象,然后调用field.setAccessible(true),允许访问字段,最后用field.set(Object obj,Object value)或field.get(Object obj)来设置和获取字段的值。

Person person3=new Person(20,"klguang");
        Field[] fileds = demo.getDeclaredFields();
        for (Field field : fileds) {
            field.setAccessible(true);// 设置些属性是可以访问的
            String name=field.getName();//取得field的名称        
            try {
                Object value = field.get(person3);// 得到此属性的值
                System.out.println("fieldName:"+name+"\tfieldValue:"+value);
                if(name.equals("age"))
                    field.set(person3, 40);
            } catch (Exception e) {
                e.printStackTrace();
            }             
        }
        MyTestUtil.printWithSign("person after access filed", person3);

调用实例的方法

可通过Class对象的getMethods() 或getMethod(String name, Class<?>...parameterTypes)方法取得Method对象,通过method.invoke(Object obj, Object... args)调用obj对象的方法。

Person person4=new Person(20,"klguang");
        Method methods[] = demo.getMethods();
        for (java.lang.reflect.Method method : methods) {
            if (method.getName().startsWith("get")) {
                try {
                    Object value=method.invoke(person4, null);
                    System.out.println("method invoke , return value is "+value);
                } catch (Exception e) {
                    System.err.println("method invoke wrong!!");
                }
            }
        }

反射实践,控制台打印工具类

我用反射写了一个打印对象的属性值工具类MyTestUtil,在控制台格式化输出对象属性。因此,可以不用在以后测试javabean时候,复写toString();当然复写了更好。

该工具类用到的反射方法如下:

/**
     * 
     * 
     * @param object
     * @param recursion
     *            是否要递归
     * @return
     */
    private static String beanToStr(Object object, boolean recursion) {
        if (object == null)
            return "null";
        Class clazz = object.getClass();
        StringBuilder sb = new StringBuilder();
        //返回源代码中给出的底层类的简称
        sb.append(clazz.getSimpleName()).append("[");
        Field[] fields = sortFieldByType(clazz.getDeclaredFields());
        int iMax = fields.length - 1;
        if (iMax == -1)
            return sb.append("]").toString();
        for (int i = 0;; i++) {
            Field field = fields[i];
            field.setAccessible(true);// 设置些属性是可以访问的
            String name = field.getName();// 取得field的名称
            if (name.equals("serialVersionUID"))
                continue;
            try {
                Object value = field.get(object);// 得到此属性的值
                if (isSimpleType(value) || !recursion)
                    sb.append(name + " = " + String.valueOf(value));
                else
                    sb.append("\r\n" + indent(clazz.getSimpleName().length() + 2," ")
                            + objToStr(value, false) + "\r\n");
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (i == iMax)
                return sb.append("]").toString();
            sb.append(",");
        }
    }