首页 Java对象创建学习资料

Java对象创建学习资料

举报
开通vip

Java对象创建学习资料 1 Java 技术与 Java 虚拟机 Java 有四方面组成: Java 编程语言、Java 类文件格式、Java 虚拟机和 Java 应用程序接口(Java API) Java 语言的运行过程: 开发人员在 java 平台编写 Java 代码(.java 文件),然后将之编译成字节码(.class 文件)。.class 文件被装入内存进入虚拟机,被解释器解释执行,或者是被即时代码发生器有选择的转换成 机器码执行。Java 平台由 Java 虚拟机和 Java 应用程序接口搭建,Java 语言则是进入这个...

Java对象创建学习资料
1 Java 技术与 Java 虚拟机 Java 有四方面组成: Java 编程语言、Java 类文件格式、Java 虚拟机和 Java 应用程序接口(Java API) Java 语言的运行过程: 开发人员在 java 平台编写 Java 代码(.java 文件),然后将之编译成字节码(.class 文件)。.class 文件被装入内存进入虚拟机,被解释器解释执行,或者是被即时代码发生器有选择的转换成 机器码执行。Java 平台由 Java 虚拟机和 Java 应用程序接口搭建,Java 语言则是进入这个平 台的通道,用 Java 语言编写并编译的程序可以运行在这个平台上。 在 Java 平台的结构: JVM 处在核心的位置,它是一个虚构出来的计算机,是通过在实际的 计算机上仿真模拟各种计算机功能来实现的。Java 虚拟机有自己完善的硬件架构,如处理器、 堆栈、寄存器等,还具有相应的指令系统。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 Java 虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改 地运行。JVM 的下方是移植接口,移植接口由两部分组成:适配器和 Java 操作系统, 其中 依赖于平台的部分称为适配器;JVM 通过移植接口在具体的平台和操作系统上实现;在 JVM 的上方是 Java 的基本类库和扩展类库以及它们的 API, 利用 Java API 编写的应用程 序(application) 和小程序(Java applet) 可以在任何 Java 平台上运行而无需考虑底层平台, 就 是因为有 Java 虚拟机(JVM)实现了程序与操作系统的分离,从而实现了 Java 的平台无关性。 2 Java 虚拟机的体系结构 JVM 可以由不同的厂商来实现。由于厂商的不同必然导致 JVM 在实现上的一些不同,然 而 JVM 还是可以实现跨平台的特性,这就要归功于设计 JVM 时的体系结构了。 我们知道,一个 JVM 实例的行为不光是它自己的事,还涉及到它的子系统、存储区域、 数据类型和指令这些部分,它们描述了 JVM 的一个抽象的内部体系结构,其目的不光规定 实现 JVM 时它内部的体系结构,更重要的是提供了一种方式,用于严格定义实现时的外部 行为。每个 JVM 都有两种机制,一个是装载具有合适名称的类(类或是接口),叫做类装载 子系统;另外的一个负责执行包含在已装载的类或接口中的指令,叫做运行引擎。每个 JVM 又包括 方法 快递客服问题件处理详细方法山木方法pdf计算方法pdf华与华方法下载八字理论方法下载 区、堆、Java 栈、程序计数器和本地方法栈这五个部分,这几个部分和类装载 机制与运行引擎机制一起组成的体系结构图为: JVM 体系结构 图 3JVM 的体系结构 JVM 的每个实例都有一个它自己的方法域和一个堆,运行于 JVM 内的所有的线程都共 享这些区域;当虚拟机装载类文件的时候,它解析其中的二进制数据所包含的类信息,并把 它们放到方法域中;当程序运行的时候,JVM 把程序初始化的所有对象置于堆上;而每个 线程创建的时候,都会拥有自己的程序计数器和 Java 栈,其中程序计数器中的值指向下一 条即将被执行的指令,线程的 Java 栈则存储为该线程调用 Java 方法的状态;本地方法调 用的状态被存储在本地方法栈,该方法栈依赖于具体的实现。 执行引擎处于 JVM 的核心位置,在 Java 虚拟机规范中,它的行为是由指令集所决定的。尽管对于每条指 令,规范很详细地说明了当 JVM 执行字节码遇 到指令时,它的实现应该做什么,但对于怎么做却言之甚 少。Java 虚拟机支持大约 248 个字节码。每个字节码执行一种基本的 CPU 运算,例如,把一个整数 加到寄 存器,子程序转移等。Java 指令集相当于 Java 程序的汇编语言。 虚拟机的内层循环的执行过程如下: do{ 取一个操作符字节; 根据操作符的值执行一个动作; }while(程序未结束) 由于指令系统的简单性,使得虚拟机执行的过程十分简单,从而有利于提高执行的效率。指令中操作数的数量 和大小是由操作符决定的。如果操作数比一个字节大,那么它存储的顺序是高位字节优先。例如,一个 16 位 的参数存放时占用两个字节,其值为: 第一个字节*256+第二个字节字节码。 指令流一般只是字节对齐的。指令 tableswitch 和 lookup 是例外,在这两条指令内部要求强制的 4 字节边界 对齐。 对于本地方法接口,实现 JVM 并不要求一定要有它的支持,甚至可以完全没有。Sun 公司实现 Java 本地 接口(JNI)是出于可移植性的考虑,当然 我们也可以设计出其它的本地接口来代替 Sun 公司的 JNI。但是这 些设计与实现是比较复杂的事情,需要确保垃圾回收器不会将那些正在被本地方法调用的对象 释放掉。 Java 的堆是一个运行时数据区,类的实例(对象)从中分配空间,它的管理是由垃圾回收来负责的:不给程序员 显式释放对象的能力。Java 不规定具体使用的垃圾回收算法,可以根据系统的需求使用各种各样的算法。 Java 方法区与传统语言中的编译后代码或是 Unix 进程中的正文段类似。它保存方法代码(编译后的 java 代 码)和符号表。在当前的 Java 实现 中,方法代码不包括在垃圾回收堆中,但 计划 项目进度计划表范例计划下载计划下载计划下载课程教学计划下载 在将来的版本中实现。每个 类文件包含了一个 Java 类或一个 Java 界面的编译后的代码。可以说类文件是 Java 语言的执行代码文件。 为了保证类文件的平台无关性,Java 虚拟机规范中对类文件的格式也作了详细的说明。其具体细节请参考 Sun 公司的 Java 虚拟机规范。 Java 虚拟机的寄存器用于保存机器的运行状态,与微处理器中的某些专用寄存器类似。Java 虚拟机的寄存 器有四种: 1. pc: Java 程序计数器; 2. optop: 指向操作数栈顶端的指针; 3. frame: 指向当前执行方法的执行环境的指针;。 4. vars: 指向当前执行方法的局部变量区第一个变量的指针。 在上述体系结构图中,我们所说的是第一种,即程序计数器,每个线程一旦被创建就拥有了自己的程序计 数器。当线程执行 Java 方法的时候,它包含该线程正在被执行的指令的地址。但是若线程执行的是一个本 地的方法,那么程序计数器的值就不会被定义。 Java 虚拟机的栈有三个区域:局部变量区、运行环境区、操作数区 局部变量区 每个 Java 方法使用一个固定大小的局部变量集。它们按照与 vars 寄存器的字偏移量来寻址。局部变量都 是 32 位的。长整数和双精度浮点数占据了两 个局部变量的空间,却按照第一个局部变量的索引来寻址。(例 如,一个具有索引 n 的局部变量,如果是一个双精度浮点数,那么它实际占据了索引 n 和 n+1 所代 表的存储 空间)虚拟机规范并不要求在局部变量中的 64 位的值是 64 位对齐的。虚拟机提供了把局部变量中的值装载 到操作数栈的指令,也提供了把操作数栈中的 值写入局部变量的指令。 运行环境区 在运行环境中包含的信息用于动态链接,正常的方法返回以及异常捕捉。 动态链接 运行环境包括对指向当前类和当前方法的解释器符号表的指针,用于支持方法代码的动态链接。方法的 class 文件代码在引用要调用的方法和要访问的变 量时使用符号。动态链接把符号形式的方法调用翻译成实际方 法调用,装载必要的类以解释还没有定义的符号,并把变量访问翻译成与这些变量运行时的存储结构相 应的 偏移地址。动态链接方法和变量使得方法中使用的其它类的变化不会影响到本程序的代码。 正常的方法返回 如果当前方法正常地结束了,在执行了一条具有正确类型的返回指令时,调用的方法会得到一个返回值。执行 环境在正常返回的情况下用于恢复调用者的寄存器,并把调用者的程序计数器增加一个恰当的数值,以跳过 已执行过的方法调用指令,然后在调用者的执行环境中继续执行下去。 异常捕捉 异常情况在 Java 中被称作 Error(错误)或 Exception(异常),是 Throwable 类的子类,在程序中的原因是:①动 态链接错,如无法找到所需的 class 文件。②运行时错,如对一个空指针的引用。程序使用了 throw 语句。 当异常发生时,Java 虚拟机采取如下措施:  检查与当前方法相联系的 catch 子句表。每个 catch 子句包含其有效指令范围,能够处理的异常类 型,以及处理异常的代码块地址。  与异常相匹配的 catch 子句应该符合下面的条件:造成异常的指令在其指令范围之内,发生的异常 类型是其能处理的异常类型的子类型。如果找到了匹 配的 catch 子句,那么系统转移到指定的异常处 理块处执行;如果没有找到异常处理块,重复寻找匹配的 catch子句的过程,直到当前方法的所有嵌套的 catch 子句都被检查过。  由于虚拟机从第一个匹配的 catch 子句处继续执行,所以 catch 子句表中的顺序是很重要的。因为 Java 代码是结构化的,因此总可以把某个方 法的所有的异常处理器都按序排列到一个表中,对任意可 能的程序计数器的值,都可以用线性的顺序找到合适的异常处理块,以处理在该程序计数器值下发生的 异常 情况。  如果找不到匹配的 catch 子句,那么当前方法得到一个"未截获异常"的结果并返回到当前方法的调 用者,好像异常刚刚在其调用者中发生一样。如果 在调用者中仍然没有找到相应的异常处理块,那么这 种错误将被传播下去。如果错误被传播到最顶层,那么系统将调用一个缺省的异常处理块。 操作数栈区 机器指令只从操作数栈中取操作数,对它们进行操作,并把结果返回到栈中。选择栈结构的原因是:在只有少 量寄存器或非通用寄存器的机器(如 Intel486)上,也能够高效地模拟虚拟机的行为。操作数栈是 32 位的。 它用于给方法传递参数,并从方法接收结果,也用于支持操作的参数,并保存操作 的结果。例如,iadd 指令将 两个整数相加。相加的两个整数应该是操作数栈顶的两个字。这两个字是由先前的指令压进堆栈的。这两 个整数将从堆栈弹出、相 加,并把结果压回到操作数栈中。 每个原始数据类型都有专门的指令对它们进行必须的操作。每个操作数在栈中需要一个存储位置,除了 long 和 double 型,它们需要两个位置。操作 数只能被适用于其类型的操作符所操作。例如,压入两个 int 类型的 数,如果把它们当作是一个 long 类型的数则是非法的。在 Sun 的虚拟机实现中,这个限 制由字节码验证器 强制实行。但是,有少数操作(操作符 dupe 和 swap),用于对运行时数据区进行操作时是不考虑类型的。 本地方法栈,当一个线程调用本地方法时,它就不再受到虚拟机关于结构和安全限制方面的约束,它既可 以访问虚拟机的运行期数据区,也可以使用本地处理 器以及任何类型的栈。例如,本地栈是一个 C 语言的 栈,那么当 C 程序调用 C 函数时,函数的参数以某种顺序被压入栈,结果则返回给调用函数。在实现 Java 虚 拟机时,本地方法接口使用的是 C 语言的模型栈,那么它的本地方法栈的调度与使用则完全与 C 语言 的栈相同。 3 Java 虚拟机的运行过程 上面对虚拟机的各个部分进行了比较详细的说明,下面通过一个具体的例子来 分析 定性数据统计分析pdf销售业绩分析模板建筑结构震害分析销售进度分析表京东商城竞争战略分析 它的运行过程。 虚拟机通过调用某个指定类的方法 main 启动,传递给 main 一个字符串数组参数,使指定的类被装载,同 时链接该类所使用的其它的类型,并且初始化它们。例如对于程序: class HelloApp { public static void main(String[] args) { System.out.println("Hello World!"); for (int i = 0; i < args.length; i++ ) { System.out.println(args[i]); } } } 编译后在命令行模式下键入: java HelloApp run virtual machine 将通过调用 HelloApp 的方法 main 来启动 java 虚拟机,传递给 main 一个包含三个字符串"run"、"virtual"、 "machine"的数组。现在我们略述虚拟机在执行 HelloApp 时可能采取的步骤。 开始试图执行类 HelloApp 的 main 方法,发现该类并没有被装载,也就是说虚拟机当前不包含该类的二进 制代表,于是虚拟机使用 ClassLoader 试图寻找这样的二进制代表。如果这个进程失败,则抛出一个异常。 类被装载后同时在 main 方法被调用之前,必须对类 HelloApp 与其它类型进行链接然后初始化。链接包含 三个阶段:检验,准备和解析。检验检查被装载的主类的符号和语义,准备则创建类或接口的静态域以 及 把这些域初始化为 标准 excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载 的默认值,解析负责检查主类对其它类或接口的符号引用,在这一步它是可选的。 类的初始化是对类中声明的静态初始化函数和静态域的初 始化构造方法的执行。一个类在初始化之前它的 父类必须被初始化。整个过程如下: 图 4:虚拟机的运行过程 4 结束语 本文通过对 JVM 的体系结构的深入研究以及一个 Java 程序执行时虚拟机的运行过程的详细分析,意在剖 析清楚 Java 虚拟机的机理。 Java 虚拟机支持的数据类型 Java 虚拟机支持 Java 语言的基本数据类型如下: byte://1 字节有符号整数的补码 short://2 字节有符号整数的补码 int://4 字节有符号整数的补码 long://8 字节有符号整数的补码 float://4 字节 IEEE754 单精度浮点数 double://8 字节 IEEE754 双精度浮点数 char://2 字节无符号 Unicode 字符 虚拟机支持的其它数据类型包括: object//对一个 Javaobject(对象)的 4 字节引用 returnAddress//4 字节,用于 jsr/ret/jsr-w/ret-w 指令 (在 Sun 公司的实现中,对 object 的引用是一个句柄,其中包含一对指针:一个指针指向该 object 的方法表,另一个指向该 object 的数据。) jvm 内容引用:http://baike.baidu.com/view/160708.htm Java 对象的创建方式 public class Constion extends ConstructorSuper implements Serializable{ private int age=22; public static String head="static"; static{ System.out.println(head); System.out.println("加载静态代码块"); } public Constion (){ System.out.println(name); System.out.println("调用本类构造"); } public Constion (int age){ System.out.println("调用本类构造"); this.age=age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public class ConstructorSuper { public String name; static{ System.out.println("加载父类静态代码块"); } public ConstructorSuper(){ System.out.println("调用父类构造"); name="张三"; } public String getName() { return name; } public void setName(String name) { this.name = name; } } //对象创建方式一 Constionconstructor=new Constion (); //对象创建方式二 Class classs=Class.forName("com.revise.create. Constion"); Object object=classs.newInstance(); 对象的创建过程: 1. 初始化父类,为静态代码块静态属性分配空间 从顶级父类开始 2. 初始化父类,为属性赋值配置数据空间,从父类开始 object 的引用是一个句柄,其中包含一对指针:一个指针指向该 object 的方法表, 另一个指向该 object的数 new 的执行内容:1.分配数据空间 2.初始化数据 3调用构造方法 关于 string 对象的创建:String 对象有个存储池,每一个字符串在存储池里面都有唯一的地 址,在创建 string 对象时,如果对象池内部没有创建的内容则会在对象池内部创建一个,否 则则是引用对象池的该字符 Class 类创建对象 Class.forName(String str)加载对象 返回的是一个Class对象如果该类还没有加载则会 调用该类,static 语句将会被执行。如果没有找到该类将会抛出 ClassNoFountException Class 方法介绍 Class[] getInterfaces(),确定此对象所表示的类或接口实现的接口。 当使用.class 创建对 Class 对象的引用时,不会自动初始化该 Class 对像,会执行如下 三个步骤: 1.加载:由类加载器执行,查找字节码,并从字节码中创建对象 2.连接:在链接阶段将验证类中的字节码,为静态域分配空间,如果必须的话,将解 析这个类创建的对其他类的引用 3.初始化,如果该类有超类将会对其初始化,执行静态初始器和静态初始化块 Java5 后 Calss 支持泛型 Class cl=int.class; 注意点 Class cl=int.class;是错误的, Integer Class 对象不是 Number Class 对象的子类 但是在声明 Class cl=int.class 可以通过; 泛型中不支持子类 Class 对象去实例化父类 Class 对象 类型转换 Cast(Object obj) 该方法是参数对象强转成Class引用类型 Class< Constion> csl= Constion.class; ConstructorSuper constr=(ConstructorSuper) classs.newInstance(); Constion con=csl.cast(constr); 如果发生错误将会抛出 ClassCastException 异常 可以采用类型检查决定是否转换 If(xxxx instanceof XXXXX){ instanceof 返回 boolean 判断对象是否是 XXXXX 的类型 转换 } 反射操作对象实战篇: 进行属性设置 注意点 方法的调用和和属性的获取和方法设置的权限有关系,pubilc private 等 1.基础知识: Class cla=XXXX.class; (1).获取属性,设置属性 cla.getFields()获取属性列表 cla.getField(String fieldName) 获取属性值 Filed getName(String name)获取姓名 Filed getType();获取类型 Filed set(Object obj ,Object value) //setBoolen setFloat…. 获取该对象的属性值 XXXX xxx=new XXXX(); Field field=XXXX.class.getField(“name”); Object obj=field.get(xxx);//返回 XXXX 对象的该 name 属性值 field.get(XXXX);//获取 XXXX 的静态属性 name field.set(xxxx,”张三”);通过反射设置值 (2) 获取方法,调用方法 cla.getMethods();//获取方法数组 cla.getMethod(String methodName,Class[] cla) 第一个参数意义:方法名字,第二个是参数 Class 数组 Method invoke(Object obj,Object[] value);//调用方法,获取方法类型 method.invoke(constructor, "name"); //调用方法 method.invoke(constructor, new Object[]{...});//多参数 method.invoke(null, new Object[]{...}); 执行静态方法 构造方法 getConstructor(Class cls) 获取当个构造方法 getConstructors() 获取构造方法数组 Constructors newInstance(Object obj) 调用构造方法 contort.newInstance(2);//创建对象 如何突破权限进行属性的设置和获取 Class 方法 getDeclaredField(String name) getDeclaredMethod(String name ,Class[] classs) Method Field设置 setAccessible(true) 备注:反射是抓取基本类型无法获取父类型所定义的方法 实例代码: Map map=new HashMap(); map.put("name","张三"); map.put("age", 22); Class cls=Class.forName("com.revise.create.Constion"); Set keys=map.keySet(); Collection values=map.values(); Constion constion=new Constion(); for(String key:keys){ Field filed=cls.getDeclaredField(key); filed.setAccessible(true); filed.set(constion, toType(map.get(key),filed.getType())); } Field filed=cls.getDeclaredField("name"); filed.setAccessible(true); System.out.println(filed.get(constion)); } //定义类型转换 private static Object toType(Object value,Type type){ String className=type.toString();; if(className.equals("class java.lang.String")){ return value.toString(); }else if(className.equals("int")){ return Integer.parseInt(value.toString()); } return null; } 输出结果:张三 Java ClassLoader 1, 什么是 ClassLoader 类加载器是负责加载类的对象。ClassLoader 类是一个抽象类。如果给定类的二进制名称, 那么类加载器会试图查找或生成构成类定义的数据。一般策略是将名称转换为某个文件名, 然后从文件系统读取该名称的“类文件”。ClassLoader 类使用委托模型来搜索类和资源。每 个 ClassLoader 实例都有一个相关的父类加载器。需要查找类或资源时,ClassLoader 实 例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。虚拟机的 内置类加载器(称为 "bootstrap class loader")本身没有父类加载器,但是可以将它用作 ClassLoader 实例的父类加载器。 2 . ClassLoader 的方法 A)loadClass 使用指定的二进制名称来加载类。此方法的默认实现将按以下顺序搜索类: 1. 调用 findLoadedClass(String) 来检查是否已经加载类。 2. 在父类加载器上调用 loadClass 方法。如果父类加载器为 null,则使用虚拟机的内置 类加载器。 3. 调用 findClass(String) 方法查找类。 name - 类的二进制名称 resolve - 如果该参数为 true,则分析这个类 B)defineClass protected final Class defineClass(String name, byte[] b, int off, int len) throws ClassFormatError 将一个 byte 数组转换为 Class 类的实例。必须分析 Class,然后才能使用它。 此方法将默认 的 ProtectionDomain 分配给新定义的类。调用 Policy.getPolicy().getPermissions(new CodeSource(null, null)) 时,ProtectionDomain 被有效授予所返回的相同权限集。默认域在第 一次调用 defineClass 时创建,并在后续调用时被重用。 C)findSystemClass findSystemClass 方法从本地文件系统中寻找类文件,如果存在,就使用 defineClass 将原始字节 转换成 Class 对象,以将该文件转换成类。 D)resolveClass 可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的 loadClass 时可以调用 resolveClass,这取决于 loadClass 的 resolve 参数的值。 E)findLoadedClass findLoadedClass 充当一个缓存:当请求 loadClass 装入类时,它调用该方法来查看 ClassLoader 是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦 3,Java2 中 ClassLoader 的变动 1)loadClass 的缺省实现 在 Java2 中 loadClass 的实现嵌入了大多数查找类的一般方法,并使您通过覆盖 findClass 方法来 定制它,在适当的时候 findClass 会调用 loadClass。 这种方式的好处是可能不一定要覆盖 loadClass,只要覆盖 findClass 就行了,这减少了工作量。 2)新方法:findClass 使用指定的二进制名称查找类。 使用指定的二进制名称查找类。此方法应该被类加载器的实现重写,该实现按照委托模型来 加载类。在通过父类加载器检查所请求的类后,此方法将被 loadClass 方法调用。默认实 现抛出一个 ClassNotFoundException。 参数: name - 类的二进制名称 返回: 得到的 Class 对象 3)新方法:getSystemClassLoader 如果覆盖 findClass 或 loadClass,getSystemClassLoader 让我们以实际 ClassLoader 对象来访问 系统 ClassLoader,而不是固定的从 findSystemClass 调用它。 4)新方法:getParent 为了将类请求委托给父 ClassLoader,这个新方法允许 ClassLoader 获取它的父 ClassLoader。 4,定制 ClassLoader 我们将写一个自己的 ClassLoader 实现示例,它将实现如下步骤,这也是 ClassLoader 的工作 原理: # 调用 findLoadedClass 来查看是否存在已装入的类。 # 如果没有,那么采用那种特殊的神奇方式来获取原始字节。 # 如果已有原始字节,调用 defineClass 将它们转换成 Class 对象。 # 如果没有原始字节,然后调用 findSystemClass 查看是否从本地文件系统获取类。 # 如果 resolve 参数是 true,那么调用 resolveClass 解析 Class 对象。 # 如果还没有类,返回 ClassNotFoundException。 # 否则,将类返回给调用程序。 类加载技术提升篇 类加载器基本概念 顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使 用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代 码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class 类的一个实例。每个这样 的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情 况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。 java.lang.ClassLoader 类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然 后从这些字节代码中定义出一个 Java 类,即 java.lang.Class 类的一个实例。除此之外,ClassLoader 还负 责加载 Java 应用所需的资源,如图像文件和配置文件等。不过本文只讨论其加载类的功能。为了完成加 载类的这个职责,ClassLoader 提供了一系列的方法,比较重要的方法如 表 1 所示。关于这些方法的细节 会在下面进行介绍。 内部类的表示,如 com.example.Sample$1 和 com.example.Sample$Inner 等表示方式 类加载器的树状组织结构 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 类的方式实现自己 的类加载器,以满足一些特殊的需求。 除了引导类加载器之外,所有的类加载器都有一个父类加载器。通过 表 1 中给出的 getParent() 方法可以 得到。对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父 类加载器是引导类加载器;对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类 的类加载器。因为类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的。一般来说,开 发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,形成树状结构。 树的根节点就是引导类加载器。 类加载器的代理模式 类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器 先去尝试加载这个类,依次类推。在介绍代理模式背后的动机之前,首先需要说明一下 Java 虚拟机是如 何判定两个 Java 类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否 一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载 之后所得到的类,也是不同的。比如一个 Java 类 com.example.Sample,编译之后生成了字节代码文件 代理模式是为了保证 Java 核心库的类型安全。所有 Java 应用都至少需要引用 java.lang.Object 类,也就 是说在运行的时候,java.lang.Object 这个类需要被加载到 Java 虚拟机中。如果这个加载过程由 Java 应用 自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object 类,而且这些类之间是不兼容的。 通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的 都是同一个版本的 Java 核心库的类,是互相兼容的。 下面具体介绍类加载器加载类的详细过程。 加载类的过程 在前面介绍类加载器的代理模式的时候,提到过类加载器会首先代理给其它类加载器来尝试加载某 个类。这就意味着真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。 真正完成类的加载工作是通过调用 defineClass 来实现的;而启动类的加载过程是通过调用 loadClass 来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。 在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器启动类 的加载过程并不重要,重要的是最终定义这个类的加载器。两种类加载器的关联之处在于:一个类的定义 加载器是它引用的其它类的初始加载器。如类 com.example.Outer 引用了类 com.example.Inner,则由类 com.example.Outer 的定义加载器负责启动类 com.example.Inner 的加载过程。 线程上下文类加载器 线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread 中 的方法 getContextClassLoader() 和 setContextClassLoader(ClassLoader cl) 用来获取和设置线程的 上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl) 方法进行设置的话,线程将继 承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运 行的代码可以通过此类加载器来加载类和资源 前面提到的类加载器的代理模式并不能解决 Java 应用开发中会遇到的类加载器的全部问 快递公司问题件快递公司问题件货款处理关于圆的周长面积重点题型关于解方程组的题及答案关于南海问题 。Java 提供了 很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,如 JAXP 的 SPI 接口 定义包含在 javax.xml.parsers 包中。这些 SPI 的实现代码很可能是作为 Java 应用所依赖的 jar 包被 包含进来,可以通过类路径(CLASSPATH)来找到,如实现了 JAXP SPI 的 Apache Xerces 所包含的 jar 包。 SPI 接口中的代码经常需要加载具体的实现类。如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory 类中的 newInstance() 方法用来生成一个新的 DocumentBuilderFactory 的实例。这里的实例的真正的类是继承自 javax.xml.parsers.DocumentBuilderFactory,由 SPI 的实现所提供的。如在 Apache Xerces 中,实现 的类是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。而问题在于,SPI 的接口是 Java 核心 库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类 加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为 它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式无法解决这个问题。 线程上下文类加载器正好解决了这个问题。如果不做任何的设置,Java 应用的线程的上下文类加载器 默认就是系统上下文类加载器。在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。线程上下文类加载器在很多 SPI 的实现中都会用到。 下面介绍另外一种加载类的方法:Class.forName。 Class.forName Class.forName 是一个静态方法,同样可以用来加载类。该方法有两种形式:Class.forName(String name, boolean initialize, ClassLoader loader) 和 Class.forName(String className)。第一种形式 的参数 name 表示的是类的全名;initialize 表示是否初始化类;loader 表示加载时使用的类加载器。 第二种形式则相当于设置了参数 initialize 的值为 true,loader 的值为当前类的类加载器。 Class.forName 的一个很常见的用法是在加载数据库驱动的时候。如 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance() 用来加载 Apache Derby 数 据库的驱动。 在介绍完类加载器相关的基本概念之后,下面介绍如何开发自己的类加载器。 开发自己的类加载器 虽然在绝大多数情况下,系统默认提供的类加载器实现已经可以满足需求。但是在某些情况下,您还 是需要为应用开发出自己的类加载器。比如您的应用通过网络来传输 Java 类的字节代码,为了保证安全 性,这些字节代码经过了加密处理。这个时候您就需要自己的类加载器来从某个网络地址上读取加密后的 字节代码,接着进行解密和验证,最后定义出要在 Java 虚拟机中运行的类来。下面将通过两个具体的实 例来说明类加载器的开发。 文件系统类加载器 第一个类加载器用来加载存储在文件系统上的 Java 字节代码。完整的实现如 代码清单 6 所示。 清单 6. 文件系统类加载器 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"; } } (可选内容) 网络类加载器 下面将通过一个网络类加载器来说明如何通过类加载器来实现组件的动态更新。即基本的场景是:Java 字节代码(.class)文件存放在服务器上,客户端通过网络的方式获取字节代码并执行。当有版本更新的 时候,只需要替换掉服务器上保存的文件即可。通过类加载器可以比较简单的实现这种需求。 类 NetworkClassLoader 负责通过网络下载 Java 类字节代码并定义出 Java 类。它的实现与 FileSystemClassLoader 类似。在通过 NetworkClassLoader 加载了某个版本的类之后,一般有两种做法 来使用它。第一种做法是使用 Java 反射 API。另外一种做法是使用接口。需要注意的是,并不能直接在 客户端代码中引用从服务器上下载的类,因为客户端代码的类加载器找不到这些类。使用 Java 反射 API 可以直接调用 Java 类的方法。而使用接口的做法则是把接口的类放在客户端中,从服务器上加载实现此 接口的不同版本的类。在客户端通过相同的接口来使用这些实现类。网络类加载器的具体代码见 下载。 在介绍完如何开发自己的类加载器之后,下面说明类加载器和 Web 容器的关系。 类加载器与 Web 容器 对于运行在 Java EE? 容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。 不同的 Web 容器的实现方式也会有所不同。以 Apache Tomcat 来说,每个 Web 应用都有一个对应的类加 载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父 类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查 找范围之内的。这也是为了保证 Java 核心库的类型安全。 绝大多数情况下,Web 应用的开发人员不需要考虑与类加载器相关的细节。下面给出几条简单的原则: 每个 Web 应用自己的 Java 类文件和使用的库的 jar 包,分别放在 WEB-INF/classes 和 WEB-INF/lib 目录下面。 多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由所有 Web 应用共享的目录下 面。 当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。 在介绍完类加载器与 Web 容器的关系之后,下面介绍它与 OSGi 的关系。 类加载器与 OSGi OSGi? 是 Java 上的动态模块系统。它为开发人员提供了面向服务和基于组件的运行环境,并提供标 准的方式用来管理软件的生命周期。OSGi 已经被实现和部署在很多产品上,在开源社区也得到了广泛的支 持。Eclipse 就是基于 OSGi 技术来构建的。 OSGi 中的每个模块(bundle)都包含 Java 包和类。模块可以声明它所依赖的需要导入(import)的 其它模块的 Java 包和类(通过 Import-Package),也可以声明导出(export)自己的包和类,供其它模 块使用(通过 Export-Package)。也就是说需要能够隐藏和共享一个模块中的某些 Java 包和类。这是通 过 OSGi 特有的类加载器机制来实现的。OSGi 中的每个模块都有对应的一个类加载器。它负责加载模块自 己包含的 Java 包和类。当它需要加载 Java 核心库的类时(以 java 开头的包和类),它会代理给父类 加载器(通常是启动类加载器)来完成。当它需要加载所导入的 Java 类时,它会代理给导出此 Java 类 的模块来完成加载。模块也可以显式的声明某些 Java 包和类,必须由父类加载器来加载。只需要设置系 统属性 org.osgi.framework.bootdelegation 的值即可。 假设有两个模块 bundleA 和 bundleB,它们都有自己对应的类加载器 classLoaderA 和 classLoaderB。在 bundleA 中包含类 com.bundleA.Sample,并且该类被声明为导出的,也就是说可以被 其它模块所使用的。bundleB 声明了导入 bundleA 提供的类 com.bundleA.Sample,并包含一个类 com.bundleB.NewSample 继承自 com.bundleA.Sample。在 bundleB 启动的时候,其类加载器 classLoaderB 需要加载类 com.bundleB.NewSample,进而需要加载类 com.bundleA.Sample。由于 bundleB 声明了类 com.bundleA.Sample 是导入的,classLoaderB 把加载类 com.bundleA.Sample 的工作代理给导 出该类的 bundleA 的类加载器 classLoaderA。classLoaderA 在其模块内部查找类 com.bundleA.Sample 并定义它,所得到的类 com.bundleA.Sample 实例就可以被所有声明导入了此类的模块使用。对于以 java 开头的类,都是由父类加载器来加载的。如果声明了系
本文档为【Java对象创建学习资料】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_475978
暂无简介~
格式:pdf
大小:333KB
软件:PDF阅读器
页数:18
分类:互联网
上传时间:2013-09-21
浏览量:320