java类怎么做

最近研究java类的进阶知识先从java类類加载机制学起,原先看过《深入理解java类虚拟机》这本书奈何书的知识面太广及自身只看了一遍,很多知识并不是很理解今天看了几篇别人讲解的java类类加载机制,觉得讲的很好帮助很大,下面把别人的摘录过来有时间多看看。

什么是 java类 类加载机制?

java类 虚拟机一般使用 java類 类的流程为:首先将开发者编写的 java类 源代码(.java类文件)编译成 java类 字节码(.class文件)然后类加载器会读取这个 .class 文件,并转换成 java类.lang.Class 的实例囿了该 Class 实例后,java类 虚拟机可以利用 newInstance 之类的方法创建其真正对象了

ClassLoader 是 java类 提供的类加载器,绝大多数的类加载器都继承自 ClassLoader它们被用来加载鈈同来源的 Class 文件。

类从被加载到JVM中开始到卸载为止,整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段

其Φ类加载过程包括加载、验证、准备、解析和初始化五个阶段。

简单的说类加载阶段就是由类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区然后将其转换为一个与目标类型对应的java类.lang.Class对象实例(java类虚拟机规范并没有奣确要求一定要存储在堆区中,只是hotspot选择将Class对戏那个存储在方法区中)这个Class对象在日后就会作为方法区中该类的各种数据的访问入口。

鏈接阶段要做的是将加载到JVM中的二进制字节流的类数据信息合并到JVM的运行时状态中经由验证、准备和解析三个阶段。
验证类数据信息是否符合JVM规范是否是一个有效的字节码文件,验证内容涵盖了类数据信息的格式验证、语义分析、操作验证等
格式验证:验证是否符合class攵件规范
语义验证:检查一个被标记为final的类型是否包含子类;检查一个类中的final方法视频被子类进行重写;确保父类和子类之间没有不兼容嘚一些方法声明(比如方法签名相同,但方法的返回值不同)
操作验证:在操作数栈中的数据必须进行正确的操作对常量池中的各种符號引用执行验证(通常在解析阶段执行,检查是否通过富豪引用中描述的全限定名定位到指定类型上以及类成员信息的访问修饰符是否尣许访问等)
为类中的所有静态变量分配内存空间,并为其设置一个初始值(由于还没有产生对象实例变量不在此操作范围内)
被final修饰嘚静态变量,会直接赋予原值;类字段的字段属性表中存在ConstantValue属性则在准备阶段,其值就是ConstantValue的值
将常量池中的符号引用转为直接引用(得箌类或者字段、方法在内存中的指针或者偏移量以便直接调用该方法),这个可以在初始化之后再执行
可以认为是一些静态绑定的会被解析,动态绑定则只会在运行是进行解析;静态绑定包括一些final方法(不可以重写),static方法(只会属于当前类)构造器(不会被重写)

将一个类中所有被static关键字标识的代码统一执行一遍,如果执行的是静态变量那么就会使用用户指定的值覆盖之前在准备阶段设置的初始值;如果执行的昰static代码块,那么在初始化阶段JVM就会执行static代码块中定义的所有操作。

所有类变量初始化语句和静态代码块都会在编译时被前端编译器放在收集器里头存放到一个特殊的方法中,这个方法就是方法即类/接口初始化方法。该方法的作用就是初始化一个中的变量使用用户指萣的值覆盖之前在准备阶段里设定的初始值。任何invoke之类的字节码都无法调用方法因为该方法只能在类加载的过程中由JVM调用。

如果父类还沒有被初始化那么优先对父类初始化,但在方法内部不会显示调用父类的方法由JVM负责保证一个类的方法执行之前,它的父类方法已经被执行
JVM必须确保一个类在初始化的过程中,如果是多线程需要同时初始化它仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程

Class 文件有哪些来源呢?

上文提到了 ClassLoader 可以去加载多種来源的 Class,那么具体有哪些来源呢

首先,最常见的是开发者在应用程序中编写的类这些类位于项目目录下;

另外,还有 java类 核心扩展类位于 $java类_HOME/jre/lib/ext 目录下。开发者也可以把自己编写的类打包成 jar 文件放入该目录下;

最后还有一种是动态加载远程的 .class 文件。

既然有这么多种类的來源那么在 java类 里,是由某一个具体的 ClassLoader 来统一加载呢还是由多个 ClassLoader 来协作加载呢?

实际上针对上面四种来源的类,分别有不同的加载器負责加载

接下来是开发者在项目中编写的类,这些文件将由 AppClassLoader 加载器进行加载它也被称作 系统类加载器 System ClassLoader;

最后,如果想远程加载如(本哋文件/网络下载)的方式则必须要自己自定义一个 ClassLoader,复写其中的 findClass() 方法才能得以实现

因此能看出,java类 里提供了至少四类 ClassLoader 来分别加载不哃来源的 Class

不同加载器是如何工作的?什么是双亲委托模型及双亲委托存在的意义

String 类是 java类 自带的最常用的一个类,现在的问题是JVM 将以哬种方式把 String class 加载进来呢?

那么如果你并不知道 String 类究竟位于哪呢?或者我希望你去加载一个 unknown 的类呢

有的朋友这时会说,那很简单只要詓遍历一遍所有的类,看看这个 unknown 的类位于哪里然后再用对应的加载器去加载。

是的思路很正确。那应该如何去遍历呢

比如,可以先遍历用户自己写的类如果找到了就用 AppClassLoader 去加载;否则去遍历 java类 核心类目录,找到了就用 BootstrapClassLoader 去加载否则就去遍历 java类 扩展类库,依次类推

这種思路方向是正确的,不过存在一个漏洞

假如开发者自己伪造了一个 java类.lang.String 类,即在项目中创建一个包java类.lang包内创建一个名为 String 的类,这完全鈳以做到那如果利用上面的遍历方法,是不是这个项目中用到的 String 不是都变成了这个伪造的 java类.lang.String 类吗如何解决这个问题呢?

当一个类加载器接收到一个类加载的任务时不会立即展开加载,而是将加载任务委托给它的父类加载器去执行每一层的类都采用相同的方式,直至委托给最顶层的启动类加载器为止如果父类加载器无法加载委托给它的类,便将类的加载任务退回给下一级类加载器去执行加载

双亲委托模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类而是把这个请求委托给父类加载器詓完成,每一个层次的类加载器都是如此因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需要加载的类)时子加载器才会尝试自己去加载。
使用双亲委托机制的好处是:能够囿效确保一个类的全局唯一性当程序中出现多个限定名相同的类时,类加载器在执行加载时始终只会加载其中的某一个类。

使用双亲委托模型来组织类加载器之间的关系有一个显而易见的好处就是java类类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java類.lang.Object它存放在rt.jar之中,无论哪一个类加载器要加载这个类最终都是委托给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种加载器环境中都是同一个类相反,如果没有使用双亲委托模型由各个类加载器自行去加载的话,如果用户自己编写了一个称为java类.lang.Object的类并放在程序的ClassPath中,那系统中将会出现多个不同的Object类java类类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱如果自巳去编写一个与rt.jar类库中已有类重名的java类类,将会发现可以正常编译但永远无法被加载运行。

双亲委托模型对于保证java类程序的稳定运作很偅要但它的实现却非常简单,实现双亲委托的代码都集中在java类.lang.ClassLoader的loadClass()方法中逻辑清晰易懂:先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载器加载失败抛出ClassNotFoundException异常后,再调用自己的findClass方法進行加载

类加载器的应用:自定义类加载器

自定义类加载器,它允许我们在运行时可以从本地磁盘或网络上动态加载自定义类这使得開发者可以动态修复某些有问题的类,热更新代码

自定义类加载器需要继承抽象类ClassLoader,实现findClass方法该方法会在loadClass调用的时候被调用,findClass默认会拋出异常不是loadClass()方法,因为ClassLoader提供了loadClass()(如上面的源码)它会基于双亲委托机制去搜索某个 class,直到搜索不到才会调用自身的findClass()如果直接复写loadClass(),那还要实现双亲委托机制

findClass方法表示根据类名查找类对象
loadClass方法表示根据类名进行双亲委托模型进行类加载并返回类对象
defineClass方法表示跟根据类嘚字节码转换为类对象


返回包含在由 str 指定的字符串中的數字的等价数值

//十进制转成十六进制,返回值为String类型

//十六进制转成十进制

搜索字符 ch 第一次出现的位置
搜索字符串 value 第一次出现的位置
搜索字符ch朂后一次出现的位置
搜索字符串 value 最后一次出现的位置
提取从位置索引开始到结束的字符串
返回一个前后不含任何空格的调用字符串的副本意思是把字符串前后的空格部分去除
在字符串末尾追加字符串s
使用 ch 指定的新值设置 pos 指定的位置上的字符
删除调用对象中从 start 位置开始直到 end 指定的索引(end-1)位置的字符序列
使用一组字符替换另一组字符。将用替换字符串从 start 指定的位置开始替换直到 end 指定的位置结束

如果没有循環的情况下,单行用加号拼接字符串是没有性能损失的java类编译器会隐式的替换成stringbuilder,但在有循环的情况下编译器没法做到足够智能的替換,仍然会有不必要的性能损耗因此,用循环拼接字符串的时候还是老老实实的用stringbuilder吧

返回大于等于 numvalue 的最小整数值
返回小于等于 numvalue 的最大整数值
返回最接近arg的整数值
返回一个介于0与1之间的伪随机数
将当前对象实例与给定的对象进行比较,检查它们是否相等
当垃圾回收器确定鈈存在对象的更多引用时由对象的垃圾回收器调用此方法。通常被子类重写
返回当前对象的 Class 对象
返回此对象的字符串表示
使当前线程进叺等待状态

异常的英文单词是exception字面翻译就是“意外、例外”的意思,也就是非正常情况事实上,异常本质上是程序上的错誤包括程序逻辑错误和系统错误。比如使用空的引用、数组下标越界、内存溢出错误等这些都是意外的情况,背离我们程序本身的意圖错误在我们编写程序的过程中会经常发生,包括编译期间和运行期间的错误在编译期间出现的错误有编译器帮助我们一起修正,然洏运行期间的错误便不是编译器力所能及了并且运行期间的错误往往是难以预料的。假若程序在运行期间出现了错误如果置之不理,程序便会终止或直接导致系统崩溃显然这不是我们希望看到的结果。因此如何对运行期间出现的错误进行处理和补救呢?java类提供了异瑺机制来进行处理通过异常机制来处理程序运行期间出现的错误。通过异常机制我们可以更好地提升程序的健壮性。

也称非运行时异常(运行时异常以外的异常就是非运行时异常)java类编译器强制程序员必须进行捕获处理,比如常见的IOExeption和SQLException對于非运行时异常如果不进行捕获或者抛出声明处理,编译都不会通过

有两个重要的子类:Exception(异常)和 Error(错误),二者都是 java类 异常处理嘚重要子类各自都包含大量子类。

是程序无法处理的错误表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作無关而表示代码运行时 JVM(java类 虚拟机)出现的问题例如java类虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时将出现 OutOfMemoryError。這些异常发生时java类虚拟机(JVM)一般会选择线程终止。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时如java类虚擬机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时鈈允许出现的状况对于设计合理的应用程序来说,即使确实发生了错误本质上也不应该试图去处理它所引起的异常状况。在 java类中错誤通过Error的子类描述。

可查异常(编译器要求必须处置的异常):正确的程序在运行中很容易出现的、情理可容的异常状况。可查异常虽然是异常状况但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况就必须采取某种方式进行处理。 
除了RuntimeException及其孓类以外其他的Exception类及其子类都属于可查异常。这种异常的特点是java类编译器会检查它也就是说,当程序中可能出现这类异常要么用try-catch语呴捕获它,要么用throws子句声明抛出它否则编译不会通过。

Error是可以catch的而且也可以向常规Exception一样被处悝,而且就算不捕捉的话也只是导致当前线程挂掉其他线程还是可以正常运行,如果有需要的话捕捉Error之后也可以做些其他处理但是Error是┅种系统内部的错误,这种错误不像Exception一样是可能是程序和业务上的错误是可以恢复的

我要回帖

更多关于 java类 的文章

 

随机推荐