11-反射

反射

问:Classloader

Java中类的加载是由ClassLoader和它的子类来实现的,它负责在运行时查找和装入类文件中的类。

ClassLoader是一个重要的Java运行时系统组件,它主要包括:引导类加载器(BootStrap ClassLoader)、扩展类加载器(Extension ClassLoader)、应用类加载器(Application ClassLoader)和用户自定义类加载器(java.lang.ClassLoader的子类)。

  • Bootstrap ClassLoader:它负责加载放在\jre\lib/目录中的,或者-Xbootclasspath参数所指定路径中的Java核心库(如rt.jar),是用原生代码来实现的;
  • Extension ClassLoader:它负责加载\jre\lib\ext目录中,或系统变量java.ext.dirs所指定路径中的所有类库;
  • Application ClassLoader:它负责加载Java应用的CLASSPATH所指定的类库。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它是应用最广泛的类加载器。开发者可以直接使用这个类加载器,如果应用程序中没有自定义自己的类加载器,一般情况下这个就是程序中默认的类加载器;
  • Custom ClassLoader:应用程序根据自身需要自定义的ClassLoader,是java.lang.ClassLoader的子类,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。

例1
例2

例3:能不能自己写个类,也叫java.lang.String?

可以,但在应用的时候,需要用自己的类加载器去加载,否则,系统的类加载器永远只是去加载rt.jar包中的那个java.lang.String。
由于在tomcat的web应用程序中,都是由webapp自己的类加载器先自己加载WEB-INF/classess目录中的类,然后才委托上级的类加载器加载。
如果我们在tomcat的web应用程序中写一个java.lang.String,这时候Servlet程序加载的就是我们自己写的java.lang.String,
但是这么干就会出很多潜在的问题,原来所有用了java.lang.String类的都将出现问题。

问:JVM的类加载机制 (还不怎么懂,暂时保留)

推荐阅读

Java类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中准备、验证、解析3个部分统称为连接(Linking)。


图 1

加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)

例1

问:动态语言

问:Class对象 (Class Type,类类型)

Java虚拟机有一个运行时数据区,这个数据区又被分为方法区、堆区、栈区等,其中方法区的主要作用是存储被JVM加载的类信息、常量、静态变量等。当JVM加载某个类的时候,需要类加载器定位相应的class文件,然后将其读入到JVM中,紧接着JVM提取class中的类型信息,将这些信息存储到方法区中。

在Java的世界里,一切皆对象。从某种意义上说,在Java中有两种对象:实例对象和Class对象。实例对象就是我们平常定义的一个类的实例,例如定义一个类Person,然后使用new Person()定义Person类的一个实例对象;而Class对象是没办法用new关键字得到的,每当加载一个新的类的时候,JVM都会在Java堆中创建一个对应于该类的Class对象,该对象就代表该类,通过该Class对象我们就可以访问该类的基本信息。

获取Class对象一般有三种方式

(1) 通过类对象的getClass()方法获取

public class test {
   public static void main(String[] args) {
      Dog dog = new Dog();
      Class clazz = dog.getClass();
   }
}

(2) 通过类的.class静态属性获取

public class test {
   public static void main(String[] args) {
        Class clazz = Dog.class;
   }
}

通过这种方式时,只会加载Dog类,并不会触发Dog类构造器的初始化。

基本的数据类型如int、double、void等都有Class对象;

(3) 使用Class.forName(String className)动态加载类,className需要是类的全限定名(最常用)

public class ClassTest {
   public static void main(String[] args) {
     try {
         Class clazz = Class.forName("packageName.Dog");
     } catch (ClassNotFoundException e) {}
   }
}

问:反射机制

所谓的反射是Java语言在运行时拥有一项自观的能力,反射使您的程序代码能够得到装载到JVM中的类的内部信息,允许您执行程序时才得到所需类的内部信息,而不是在编写代码的时候就必须要知道所需类的内部信息。这样,在程序运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够访问它的任意一个属性和方法;这种动态获取信息以及动态调用对象的方法的功能称为Java语言的反射机制。

问:反射机制的基本使用

请看文章

Java反射机制的实现要借助于4个类:Class,Constructor,Field,Method;其中Class代表的是类的Class对象,Constructor是代表类构造方法的对象,Field是代表类属性的对象,Method是代表类方法的对象,通过这四个对象我们可以粗略的看到一个类的各个组成部分。其中最核心的就是Class对象,它是实现反射的基础。

1. 反射机制的作用

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法;
  • 在运行时调用任意一个对象的方法;
  • 生成动态代理

Spring、Struts、Hibernate等框架都是通过类的反射进行开发的。

例1

2. 应用反射的基本步骤:

(1) 获得你想操作的类的Class对象;

(2) 调用Class对象中的方法获取你想要的信息集合,如调用getDeclaredFields()方法得到类的所有属性;

(3) 处理第2步中得到的信息,然后进行你想做的实际操作。

3. Class对象的常用方法 - 获取类的信息

(1) 获得类的名称

  • String getName()
  • String getSimpleName()

(2) 获得类的构造方法

  • Constructor getConstructor(Class[] parameterTypes)
  • Constructor[] getConstructors()
  • Constructor getDeclaredConstructor(Class[] parameterTypes)
  • Constructor[] getDeclaredConstructors()

(3) 获得类的字段信息

  • Field getField(String name)
  • Field[] getFields()
  • Field getDeclaredField(String name)
  • Field[] getDeclaredFields()

(4) 获得类的方法信息

  • Method getMethod(String name, Class[] parameterTypes)
  • Method[] getMethods()
  • Method getDeclaredMethod(String name, Class[] parameterTypes)
  • Method[] getDeclaredMethods()

注意:

(1) getFields()与getDeclaredFields()区别

  • getFields() 只能返回类中所有的公有属性,包括从父类继承的公有属性;
  • getDeclaredFields() 返回类中声明的所有属性,包括public/private/protect/default属性,不包括从父类继承的属性

(2) getMethods()与getDeclaredMethods()区别

  • getMethods() 只能返回类中所有的公有方法,包括从父类继承的公有方法;
  • getDeclaredFields() 返回类中声明的所有方法,包括public/private/protect/default方法,不包括从父类继承的方法

(3) getConstructors()与getDeclaredConstructors()区别

  • getConstructors() 只能返回类中所有的公有构造方法
  • getDeclaredConstructors() 返回类中声明的所有构造方法,包括public/private/protect/default构造方法

例1

例2:获取类的信息

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

/**
 * 打印类的信息,包括类的成员函数、成员变量、构造函数
 * java.lang.Method中封装了类的成员函数信息
 * java.lang.reflect.Field中封装了类的成员变量的信息 
 * java.lang.Constructor中封装了类的构造函数的信息
 * 
 * @param obj 要获取类的信息,首先要获取类的类类型。传递的是哪个子类的对象obj,clazz就是该子类的类类型
 */
public class ReflectUtil {
    /**
     * 获取类的成员函数信息
     * @param obj
     */
    public static void printClassMethodMessage(Object obj) {
        Class clazz = obj.getClass();
        System.out.println("类的名称是: " + clazz.getName());
        Method[] methods = clazz.getMethods(); // clazz.getDeclaredMethods()
        for (int i = 0; i < methods.length; i++) {
            Class returnType = methods[i].getReturnType(); // 得到方法的返回值类型的类类型
            System.out.print(returnType.getName() + " ");
            System.out.print(methods[i].getName() + "(");
            Class[] paramTypes = methods[i].getParameterTypes(); // 获取参数类型--->得到的是参数列表的类型的类类型
            for (Class c : paramTypes) {
                System.out.print(c.getName() + ",");
            }
            System.out.println(")");
        }
    }

    public static void main(String[] args) {
        ReflectUtil.printClassMethodMessage(new Integer(1));
    }

    /**
     * 获取成员变量的信息
     * @param obj
     */
    public static void printFieldMessage(Object obj) {
        Class clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields(); // clazz.getFields()
        for (Field field : fields) {
            Class fieldType = field.getType(); // 得到成员变量的类型的类类型
            String typeName = fieldType.getName();
            String fieldName = field.getName();
            System.out.println(typeName + " " + fieldName);
        }
    }

    /**
     * 打印对象的构造函数的信息
     * @param obj
     */
    public static void printConstructorMessage(Object obj) {
        Class clazz = obj.getClass();
        Constructor[] constructors = clazz.getDeclaredConstructors(); // clazz.getConstructors()
        for (Constructor constructor : constructors) {
            System.out.print(constructor.getName() + "(");
            Class[] paramTypes = constructor.getParameterTypes(); // 获取构造函数的参数列表--->得到的是参数列表的类类型
            for (Class class1 : paramTypes) {
                System.out.print(class1.getName() + ",");
            }
            System.out.println(")");
        }
    }
}

4. 方法反射的应用

(1) 步骤

  • 根据方法的名称和方法的参数列表获取某个方法
  • 调用method.invoke(对象,参数列表)执行方法

(2) 举例

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 方法的反射操作:使用Method对象进行方法调用
 */
public class MethodReflect {
    public static void main(String[] args) {
        A a = new A();
        Class clazz = a.getClass();
        try {
            /**
             * 调用print()方法: a.print();
             */
            Method m1 = clazz.getMethod("print");// 或者Method m1 = clazz.getMethod("print", new Class[]{});
            // 如果方法没有返回值则返回null;否则返回具体的返回值
            Object result1 = m1.invoke(a); // 或者m1.invoke(a, new Object[]{});
            System.out.println("==================");

            /**
             * 调用print(int, int)方法:a.print(10, 20);
             */
            Method m2 = clazz.getMethod("print", int.class, int.class); // 或者Method m2 = clazz.getMethod("print", new Class[]{int.class, int.class});
            Object result2 = m2.invoke(a, 10, 20); // 或者Object o = m2.invoke(a, new Object[]{10, 20});
            System.out.println("==================");

            /**
             * 调用print(String, String)方法:a.print("hello", "WORLD");
             */
            Method m3 = clazz.getMethod("print", new Class[]{String.class, String.class}); // 或者Method m3 = clazz.getMethod("print", String.class, String.class);
            Object result3 = m3.invoke(a, "hello", "WORLD");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class A {
    public void print() {
        System.out.println("hello world");
    }
    public void print(int a, int b) {
        System.out.println(a + b);
    }
    public void print(String a, String b) {
        System.out.println(a.toUpperCase() + ", " + b.toLowerCase());
    }
}

5. 泛型

Java中集合的泛型,只在编译阶段有效,运行时被擦除,可以用来防止错误输入

例1:

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class Generic {
    public static void main(String[] args) {
        List list1 = new ArrayList();
        List<String> list2 = new ArrayList<String>();
        list2.add("hello");
        // list2.add(20); 编译错误

        Class c1 = list1.getClass();
        Class c2 = list2.getClass();
        System.out.println(c1 == c2); // 结果为true,说明编译之后集合的泛型被擦除

        /*
         * Java中集合的泛型,只在编译阶段有效,运行时被擦除,可以防止错误输入
         * 验证:由于反射的操作都是编译之后的操作,我们可以通过方法的反射操作来绕过编译
         */
        try {
            Method m = c2.getMethod("add", Object.class);
            m.invoke(list2, 20); //绕过编译就绕过了泛型

            System.out.println(list2.size());
            //不能使用如下方式遍历集合
            /*for (String string : list1) {
                System.out.println(string);
            }*/
            System.out.println(list2);
        } catch (Exception e) {
          e.printStackTrace();
        }
    }
}

具体如何使用反射,请看两篇文章

分享到 评论