反射
问: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。
例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语言的运行时绑定(也称为动态绑定或晚期绑定)
问:动态语言
问: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等框架都是通过类的反射进行开发的。
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构造方法
例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();
}
}
}
具体如何使用反射,请看两篇文章