前言

    下面所有的示例学习内容中的所有的类和对象,均以Dog或者cat为例,进行学习。

Class

    在Java编译运行过程中,当程序new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象。

反射机制

    反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。一直以来反射技术都是Java中的闪亮点,这也是目前大部分框架(如Spring/Mybatis等)得以实现的支柱。在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。

深入理解Class对象机制

Java代码运行阶段

先通过下图了解下java代码在计算机中运行的三个阶段:
在这里插入图片描述

类加载

    实际上所有的类都是在对其第一次使用时动态加载到JVM中的,当程序创建第一个对类的静态成员引用时,就会加载这个被使用的类。下面通过一个简单例子来说明Class对象被加载的时间节点:

package first.test;

class Dog {
  static {   System.out.println("wang!"); }
}

class Cat{
  static {   System.out.println("miao~"); }
}

public class watch{
  public static void print(Object obj) {
    System.out.println(obj);
  }
  public static void main(String[] args) {  
    print("1");
    new Dog();
    print("2");
    new Cat();
    print("3");
  }
}

在上述代码中,每个类Dog、Cat都存在一个static语句:
运行结果:

1
wang!
2
miao~
3

    Java程序在它们开始运行之前并非都被加载到内存的,其各个部分是按需加载,所以在程序使用该类时,类加载器首先会检查这个类的Class对象是否已被加载,如果还没有加载,默认的类加载器就会先根据类名查找.class文件(编译后Class对象被保存在同名的.class文件中),在这个类的字节码文件被加载时,它们必须接受相关验证,以确保其没有被破坏并且不包含不良Java代码(这是java的安全机制检测),完全没有问题后就会被动态加载到内存中,此时相当于Class对象也就被载入内存了(毕竟.class字节码文件保存的就是Class对象),同时也就可以被用来创建这个类的所有实例对象。

Class.forName方法

Class clazz=Class.forName("first.test.Dog");

    forName方法是Class类的一个static成员方法,程序中所创建的所有的Class对象都源于这个Class类,因此Class类中定义的方法将适应所有Class对象。通过forName方法,我们可以获取到类对应的Class对象引用。因此如果我们想获取一个类的运行时类型信息并加以使用时,可以调用Class.forName()方法获取Class对象的引用,这样做的好处是无需通过持有该类的实例对象引用而去获取Class对象。

Class字面常量

    在Java中存在另一种方式来生成Class对象的引用,它就是Class字面常量,如下:

Class clazz = Dog.class;

    这种方式相对前面两种方法更加简单,更安全。因为它在编译器就会受到编译器的检查同时由于无需调用forName方法效率也会更高,因为通过字面量的方法获取Class对象的引用不会自动初始化该类。

理解Java的反射技术

    如前言的反射机制所说,反射即是在程序动态运行的时候,对于任意一个类,可以获得其所有的方法以及变量。在反射包中,我们常用的类主要有:
1.Constructor类:表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象;
2.Field类:Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private);
3.Method类:Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private)。
    下面将对这几个重要类进行分别说明(类中的方法其实很好区分,每个方法后面+s,表示获取到相应类的所有对象;每个方法前面+declared,表示获取到不仅仅是public对象,还包括private对象)。

Constructor类

获取构造方法们
	//返回指定参数类型、具有public访问权限的构造函数对象
	* Constructor<?>[] getConstructors()  
	* Constructor<T> getConstructor(类<?>... parameterTypes)  
	//返回指定参数类型、所有声明的(包括private)构造函数对象
	* Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)  
	* Constructor<?>[] getDeclaredConstructors() 

示例

Class<?> clazz = null;
//获取Class对象的引用
clazz = Class.forName("first.test.Dog");
//此时必须无参构造函数,否则将抛异常
Dog dog = (Dog) clazz.newInstance();
dog.setAge(3);
dog.setName("hali");

获取带String参数的public构造函数

Constructor cs =clazz.getConstructor(String.class);
//创建Dog     
Dog dog= (Dog) cs.newInstance("hali");
dog.setAge(3);

取得指定带int和String参数构造函数,该方法是私有构造

Constructor cs=clazz.getDeclaredConstructor(int.class,String.class);
//由于是private必须设置可访问,否则创建示例没有权限(底层)
cs.setAccessible(true);
//创建Dog对象
Dog dog= (Dog) cs.newInstance(3,"hali");

Method类

获取成员方法们:
	* Method[] getMethods()  
	* Method getMethod(String name, 类<?>... parameterTypes)  

	* Method[] getDeclaredMethods()  
	* Method getDeclaredMethod(String name, 类<?>... parameterTypes)

示例

Class clazz = Class.forName("first.test.Dog");
//根据参数获取public的Method,包含继承自父类的方法
Method method = clazz.getMethod("wang",int.class,String.class);
//获取所有public的方法:
Method[] methods =clazz.getMethods();
//获取当前类的方法包含private,该方法无法获取继承自父类的method
Method methodSelf = clazz.getDeclaredMethod("wangSelf");
//获取当前类的所有方法包含private,该方法无法获取继承自父类的method
Method[] methodsSelf=clazz.getDeclaredMethods();

Field类

获取成员变量们
	* Field[] getFields() :获取所有public修饰的成员变量
	* Field getField(String name)   获取指定名称的 public修饰的成员变量

	* Field[] getDeclaredFields()  获取所有的成员变量,不考虑修饰符
	* Field getDeclaredField(String name)  

示例,getDeclaredFields()方法获取的对象不包括父类,但是getFields()包括

Class<?> clazz = Class.forName("first.test.Dog");
//获取指定字段名称的Field类,注意字段修饰符必须为public而且存在该字段
Field field = clazz.getField("age");
//获取所有修饰符为public的字段,包含父类字段
Field fields[] = clazz.getFields();
//获取当前类所字段(包含private字段),不包含父类的字段
Field fields2[] = clazz.getDeclaredFields();

注意:Field其中的set(Object obj, Object value)方法是Field类本身的方法,用于设置字段的值,而get(Object obj)则是获取字段的值,相信这个不难理解。另外,final关键字修饰的Field字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的。

简单的框架实现

    一直说反射是框架的核心,接下来就简单写一个框架,来帮助我们创建任何类的对象(以Dog为例)。
编写一个Dog类

package first.test;

public class Dog {
    public void wang(){
        System.out.println("wang..wang.");
    }
}

编写配置文件 pro.properties

className=first.test.Dog
methodName=wang

编写框架类

/**
 * 框架类
 */
public class ReflectTest {
    public static void main(String[] args) throws Exception {
        //可以创建任意类的对象,可以执行任意方法

        //Properties对象
        Properties pro = new Properties();
        //获取class目录下的配置文件
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("pro.properties");
        pro.load(is);

        //获取配置文件中定义的数据
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");
        Class cls = Class.forName(className);
        //创建对象
        Object obj = cls.newInstance();
        Method method = cls.getMethod(methodName);
        //执行获取的方法
        method.invoke(obj);
    }
}

至此,一个简单的框架就实现了。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐