反射

对象

在java中万事万物都是对象。
但是只有静态对象和
类是java.lang.Class的对象
即任何一个类都是Class的对象

表达实例对象

表达实例对象有三种方法
比如Foo的实例对象如何表示
Foo foo1 = new Foo();//foo1就表示出来了.
Foo这个类 也是一个实例对象,Class类的实例对象,如何表示呢
任何一个类都是Class的实例对象,这个实例对象有三种表示方式

//第一种表示方式—>实际在告诉我们任何一个类都有一个隐含的静态成员变量class
Class c1 = Foo.class;

//第二中表达方式 已经知道该类的对象通过getClass方法
Class c2 = foo1.getClass();
官网 c1 ,c2 表示了Foo类的类类型(class type)
万事万物皆对象,类也是对象,是Class类的实例对象,这个对象我们称为该类的类类型

不管c1 or c2都代表了Foo类的类类型,一个类只可能是Class类的一个实例对象
System.out.println(c1 == c2);//true

第三种表达方式c3 = Class.forName("com.imooc.reflect.Foo");

1
2
3
4
5
6
7
8
Class c3 = null;
try {
c3 = Class.forName("com.imooc.reflect.Foo");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(c2==c3);//true

静态加载和动态加载

静态加载

一个程序需要先编译javac然后才能运行java
编译的过程可以认为是包括加载、验证、准备、解析和初始化
对于new关键字,在初始化阶段完成,也就是说如果一个类在new的时候没有找到,那么就会编译失败。
因此说new是静态加载机制。
比如

静态加载
静态加载

动态加载

因此可以使用类类型实现动态加载
Interface intterface = (Interface)class.forName()
这里要写成interface接口,因为根部不知道要加载哪个类

动态加载
动态加载

获取类的信息

获取方法信息

Method类,方法对象
一个成员方法就是一个Method对象
getMethods()方法获取的是所有的public的函数,包括父类继承而来的
getDeclaredMethods()获取的是所有该类自己声明的方法,不问访问权限
Method[] ms = c.getMethods();//c.getDeclaredMethods()
//得到方法的返回值类型的类类型
Class returnType = ms[i].getReturnType();
//得到方法的名称
ms[i].getName()
//获取参数类型—>得到的是参数列表的类型的类类型
Class[] paramTypes = ms[i].getParameterTypes();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 打印类的信息,包括类的成员函数、成员变量(只获取成员函数)
* @param obj 该对象所属类的信息
*/
public static void printClassMethodMessage(Object obj){
//要获取类的信息 首先要获取类的类类型
Class c = obj.getClass();//传递的是哪个子类的对象 c就是该子类的类类型
//获取类的名称
System.out.println("类的名称是:"+c.getName());
/*
* Method类,方法对象
* 一个成员方法就是一个Method对象
* getMethods()方法获取的是所有的public的函数,包括父类继承而来的
* getDeclaredMethods()获取的是所有该类自己声明的方法,不问访问权限
*/
Method[] ms = c.getMethods();//c.getDeclaredMethods()
for(int i = 0; i < ms.length;i++){
//得到方法的返回值类型的类类型
Class returnType = ms[i].getReturnType();
System.out.print(returnType.getName()+" ");
//得到方法的名称
System.out.print(ms[i].getName()+"(");
//获取参数类型--->得到的是参数列表的类型的类类型
Class[] paramTypes = ms[i].getParameterTypes();
for (Class class1 : paramTypes) {
System.out.print(class1.getName()+",");
}
System.out.println(")");
}
}

比如class是String,会打印出
会打印出int lastIndexOf(int,int,)、int length()、boolean matches(java.lang.String,)等

获取成员变量的信息

成员变量也是对象,java.lang.reflect.Field
Field类封装了关于成员变量的操作
getFields()方法获取的是所有的public的成员变量的信息
getDeclaredFields获取的是该类自己声明的成员变量的信息
Field[] fs = c.getDeclaredFields();
for (Field field : fs) {
//得到成员变量的类型的类类型
Class fieldType = field.getType();
//得到成员变量的名称
String fieldName = field.getName();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 获取成员变量的信息
* @param obj
*/
public static void printFieldMessage(Object obj) {
Class c = obj.getClass();
/*
* 成员变量也是对象
* java.lang.reflect.Field
* Field类封装了关于成员变量的操作
* getFields()方法获取的是所有的public的成员变量的信息
* getDeclaredFields获取的是该类自己声明的成员变量的信息
*/
//Field[] fs = c.getFields();
Field[] fs = c.getDeclaredFields();
for (Field field : fs) {
//得到成员变量的类型的类类型
Class fieldType = field.getType();
String typeName = fieldType.getName();
//得到成员变量的名称
String fieldName = field.getName();
System.out.println(typeName+" "+fieldName);
}
}

比如class是String,会打印出int hash、long serialVersionUID等

获取构造法方法信息

构造函数也是对象,java.lang. Constructor中封装了构造函数的信息
getConstructors获取所有的public的构造函数
getDeclaredConstructors得到所有的构造函数

Constructor[] cs = c.getDeclaredConstructors();
//获取构造函数的参数列表—>得到的是参数列表的类类型
Class[] paramTypes = constructor.getParameterTypes();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 打印对象的构造函数的信息
* @param obj
*/
public static void printConMessage(Object obj){
Class c = obj.getClass();
/*
* 构造函数也是对象
* java.lang. Constructor中封装了构造函数的信息
* getConstructors获取所有的public的构造函数
* getDeclaredConstructors得到所有的构造函数
*/
//Constructor[] cs = c.getConstructors();
Constructor[] cs = c.getDeclaredConstructors();
for (Constructor constructor : cs) {
System.out.print(constructor.getName()+"(");
//获取构造函数的参数列表--->得到的是参数列表的类类型
Class[] paramTypes = constructor.getParameterTypes();
for (Class class1 : paramTypes) {
System.out.print(class1.getName()+",");
}
System.out.println(")");
}
}

比如class是String,会打印出java.lang.String([B,int,int,)、java.lang.String([B,java.nio.charset.Charset,)等

反射的基本操作

主要过程:
1.要获取一个方法就是获取类的信息,获取类的信息首先要获取类的类类型
//要获取print(int ,int )方法
A a1 = new A();
Class c = a1.getClass();

2.获取方法 名称和参数列表来决定
getMethod获取的是public的方法
getDelcaredMethod自己声明的方法

//Method m = c.getMethod(“print”, new Class[]{int.class,int.class});
Method m = c.getMethod(“print”, int.class,int.class);

3.方法的反射操作
方法的反射操作是用m对象来进行方法调用 invoke和a1.print调用的效果完全相同
//方法如果没有返回值返回null,有返回值返回具体的返回值
//Object o = m.invoke(a1,new Object[]{10,20});
Object o = m.invoke(a1, 10,20);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class MethodDemo1 {
public static void main(String[] args) {
//要获取print(int ,int )方法
// 1.要获取一个方法就是获取类的信息,获取类的信息首先要获取类的类类型
A a1 = new A();
Class c = a1.getClass();
/*
* 2.获取方法 名称和参数列表来决定
* getMethod获取的是public的方法
* getDelcaredMethod自己声明的方法
*/
try {
//Method m = c.getMethod("print", new Class[]{int.class,int.class});
Method m = c.getMethod("print", int.class, int.class);
//方法的反射操作
//a1.print(10, 20);
//方法的反射操作是用m对象来进行方法调用 和a1.print调用的效果完全相同
//方法如果没有返回值返回null,有返回值返回具体的返回值
//Object o = m.invoke(a1,new Object[]{10,20});
Object o = m.invoke(a1, 10, 20);
System.out.println("==================");
//获取方法print(String,String)
Method m1 = c.getMethod("print", String.class, String.class);
//用方法进行反射操作
//a1.print("hello", "WORLD");
o = m1.invoke(a1, "hello", "WORLD");
System.out.println("===================");
// Method m2 = c.getMethod("print", new Class[]{});
Method m2 = c.getMethod("print");
// m2.invoke(a1, new Object[]{});
m2.invoke(a1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class A {
public void print() {
System.out.println("helloworld");
}
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());
}
}

集合与泛型

对于集合来说一般会在创建的时候就制定其类型。

1
2
ArrayList list = new ArrayList();
ArrayList<String> list1 = new ArrayList<String>();

同时我们还知道java语法糖,即编译之后集合的泛型是去泛型化的

1
2
3
Class c1 = list.getClass();
Class c2 = list1.getClass();
System.out.println(c1 == c2);//true

如果错误地添加会导致编译失败,

1
2
list1.add("hello");
//list1.add(20);错误的

但是通过泛型就可以绕过编译这一过程,完成添加不同类型的过程。

1
2
3
4
Method m = c2.getMethod("add", Object.class);
m.invoke(list1, 20);//绕过编译操作就绕过了泛型
System.out.println(list1.size());
System.out.println(list1);

完整程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void main(String[] args) {
ArrayList list = new ArrayList();
ArrayList<String> list1 = new ArrayList<String>();
list1.add("hello");
//list1.add(20);错误的
Class c1 = list.getClass();
Class c2 = list1.getClass();
System.out.println(c1 == c2);
//反射的操作都是编译之后的操作
/*
* c1==c2结果返回true说明编译之后集合的泛型是去泛型化的
* Java中集合的泛型,是防止错误输入的,只在编译阶段有效,
* 绕过编译就无效了
* 验证:我们可以通过方法的反射来操作,绕过编译
*/
try {
Method m = c2.getMethod("add", Object.class);
m.invoke(list1, 20);//绕过编译操作就绕过了泛型
System.out.println(list1.size());
System.out.println(list1);
/*for (String string : list1) {
System.out.println(string);
}*///现在不能这样遍历
} catch (Exception e) {
e.printStackTrace();
}
}

类加载

类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。
一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。
类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。
通过此实例的 newInstance()方法就可以创建出该类的一个对象。
实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。

基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。

java.lang.ClassLoader类介绍

java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例。
除此之外,ClassLoader还负责加载 Java 应用所需的资源,如图像文件和配置文件等。不过本文只讨论其加载类的功能。为了完成加载类的这个职责,ClassLoader提供了一系列的方法.

ClassLoader主要方法
ClassLoader主要方法

类加载器的树状组织结构

Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。
系统提供的类加载器主要有下面三个:
引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求
除了引导类加载器之外,所有的类加载器都有一个父类加载器。通过 getParent()方法可以得到。
对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;
对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类的类加载器。
因为类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的。
一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,形成树状结构。

类加载器的代理模式

类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。

Java 虚拟机是如何判定两个 Java 类是相同的?
Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。
只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。

代理模式是为了保证 Java 核心库的类型安全。
所有 Java 应用都至少需要引用 java.lang.Object类,也就是说在运行的时候,java.lang.Object这个类需要被加载到 Java 虚拟机中。
如果这个加载过程由 Java 应用自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object类,而且这些类之间是不兼容的。
通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。
不同的类加载器为相同名称的类创建了额外的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。
不同类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间。

真正完成类的加载工作是通过调用 defineClass来实现的;而启动类的加载过程是通过调用 loadClass来实现的。
前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。
在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。
也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器
两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。
如类 com.example.Outer引用了类 com.example.Inner,则由类 com.example.Outer的定义加载器负责启动类 com.example.Inner的加载过程。

方法 loadClass()抛出的是 java.lang.ClassNotFoundException异常;方法 defineClass()抛出的是 java.lang.NoClassDefFoundError异常。
类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。
下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。
也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即 loadClass方法不会被重复调用

loadClass()方法

java.lang.ClassLoader类的方法 loadClass()封装了前面提到的代理模式的实现。
同时loadClass()体现了模板模式,这个方法定义了类加载的过程,属于方法基类,其中findClass(String name)是由子类实现的,即不同的classloader有不同的实现方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

该方法会首先调用 findLoadedClass()方法来检查该类是否已经被加载过;
如果没有加载过的话,会调用父类加载器的 loadClass()方法来尝试加载该类;
如果父类加载器无法加载该类的话,就调用 findClass()方法来查找该类。
因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,最好不要覆写 loadClass()方法,而是覆写 findClass()方法

开发自己的类加载器

首先继承 java.lang.ClassLoader
然后,自己开发的类加载器只需要覆写 findClass(String name)方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class FileSystemClassLoader extends ClassLoader {
private String rootDir;
public FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
//得到类的全名的字节流
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
else {
//定义一个类
return defineClass(name, classData, 0, classData.length);
}
}
//将类的全名转为字节流输入
private byte[] getClassData(String className) {
String path = classNameToPath(className);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private String classNameToPath(String className) {
return rootDir + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
public static void main(String[] args) {
String classDataRootPath = "C:\\Documents and Settings\\Administrator\\workspace\\Classloader\\classData";
FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);
String className = "com.example.Sample";
try {
Class<?> class1 = fscl1.loadClass(className);
Object obj1 = class1.newInstance();
Class<?> class2 = fscl2.loadClass(className);
Object obj2 = class2.newInstance();
Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class);
setSampleMethod.invoke(obj1, obj2);
} catch (Exception e) {
e.printStackTrace();
}
}
}

类 FileSystemClassLoader的 findClass()方法首先根据类的全名在硬盘上查找类的字节代码文件(.class 文件),然后读取该文件内容,最后通过 defineClass()方法来把这些字节代码转换成 java.lang.Class类的实例。

https://www.ibm.com/developerworks/cn/java/j-lo-classloader/#minor1.1

在每个类加载器中的执行顺序:loadClass->findClass->defineClass
首先classloader主动调用loadClass,在loadClass中,如果父类不能加载就调用findClass,在findClass中找到类的全限定名,然后调用defineClass生成class。

#TODO
http://www.infoq.com/cn/articles/cf-java-byte-code
http://zyjustin9.iteye.com/blog/2092131
http://www.cnblogs.com/xrq730/p/4847337.html
contextClassLoader

Class.forName和classloader的区别

Java中class.forName()和classLoader都可用来对类进行加载。
class.forName()除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象

1
2
3
4
5
6
//Class.forName(String className) 这是1.8的源码
public static Class<?> forName(String className) throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
//注意第二个参数,是指Class被loading后是不是必须被初始化。 不初始化就是不执行static的代码即静态代码

LoadClass()方法加载类及初始化过程:
类加载(loadclass())(加载)——》newInstance()(链接+初始化)
newInstance():
(开始连接)静态代码块——》普通变量分配准备(a=0;b=0;c=null)——》(开始初始化)普通变量赋值(a=1;b=2;c=”haha”)——》构造方法——》初始化成功。

Class.forName(Stirng className)一个参数方法加载类及初始化过程:
类加载(Class.forName())(加载)——》静态代码块——》newInstance()(链接+初始化)

newInstance():
(开始连接)普通变量分配准备(a=0;b=0;c=null)——》(开始初始化)普通变量赋值(a=1;b=2;c=”haha”)——》构造方法——》初始化成功。