1. Java中字节与字符的区别
- byte 即字节的意思,是java中的基本数据类型,用来申明字节型的变量,一个字节包含8个位,所以,byte类型的取值范围是-128到127。
- 字节是通过网络传输信息的单位。通常在读取图片、声音、可执行文件需要用字节数组来保存文件,在下载文件也是用byte数组来做临时的缓冲器接收文件内容。
- 机器只知道字节,而字符却是语义上的单位,它是有编码的,一个字符可能编码成1个2个甚至3个4个字节。
- 按照 unicode 标准所有字符都占2个字节。
2. 类加载
-
什么是类加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。 -
类的生命周期
加载 - 验证 - 准备 - 解析 - 初始化 - 使用 - 卸载
其中,类加载过程包括加载 - 验证 - 准备 - 解析 - 初始化五个过程
- 加载:查找并加载类的二进制文件
加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:
(1)通过一个类的全限定名来获取其定义的二进制字节流。
(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
(3)在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
加载阶段完成后,虚拟机外部的 二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。
- 验证:确保被加载的类的正确性
- 文件格式验证
验证字节流是否符合Class文件格式的规范,例如:是否以0xCAFEBABE开头等等。 - 元数据验证
对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。 - 字节码验证
字节码验证将对类的方法进行校验分析,保证被校验的方法在运行时不会做出危害虚拟机的事,一个类方法体的字节码没有通过字节码验证,那一定有问题,但若一个方法通过了验证,也不能说明它一定安全 - 符号引用验证
- 准备:为类的静态变量分配内存,并将其初始化为默认值
准备阶段是正式为类变量分配内存并设置变量的初始化值得阶段,这些变量所使用的内存都将在方法区中进行分配。(不是实例变量,且是初始值,若 public static int a=123;准备阶段后 a 的值为 0,而不是 123,要在初始化之后才变为 123,但若被 final 修饰,public static final int a=123;在准备阶段后就变为了 123) - 解析:把类中的符号引用转换为直接引用
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。 - 初始化
初始化是类加载过程的最后一步,为类的静态变量赋予正确的初始值。
初始化的时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:
(1)创建类的实例,也就是new的方式
(2)访问某个类或接口的静态变量,或者对该静态变量赋值
(3)调用类的静态方法
(4)反射
(5)初始化某个类的子类,则其父类也会被初始化
(6)当虚拟机启动时,用户需要制定一个要执行的主类(有 main 方法的那个类),虚拟机会先初始化这个类。
-
类加载器
(1)通过一个类的全限定名来获取定义此类的二进制字节流,实现这个动作的代码就是“类加载器”。
(2)比较两个类是否相同,只有这两个类是由同一个类加载器加载的前提下才有意义,否则即使这两个类来源于同一个 class 文件,被同一个虚拟机加载,只要加载他们的加载器不同,他们就是不同的类。
(3)从 JAVA 开发人员角度,类加载器分为:
启动类加载器:(Bootstrap ClassLoader)
负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。扩展类加载器:(Extension ClassLoader)
负责加载jdk\jre\lib\ext 下或者 java.ext.dirs 系统变量指定路径下 all 类库,开发者可以直接使用扩展类加载器。应用程序类加载器:(Application ClassLoader)
负责加载用户路径 classpath 上指定的类库,开发者可以直接使用这个类加载器,若应用程序中没有定义过自己的类加载器,一般情况下,这个就是程序中默认的类加载器。 -
类加载机制
- 全盘负责
当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入 - 父类委托
先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类 - 缓存机制
缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效
- 双亲委派模型
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。 - 双亲委派模型的好处:
比如加载位于rt.jar包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个Object对象,这样就避免了类的重复加载,因此 object 类在程序的各种加载环境中都是同一个类。
3. Error 和 Exception 的区别?
Java把所有非正常情况分为两种:异常(Exception)和错误(Error)。
- Error
一般是指虚拟机相关的问题,如系统奔溃、动态连接失败、虚拟机出现错误、内存空间不足等。这种错误无法恢复或不可能捕获,将导致应用程序中断。通常,应用程序无法处理这些错误,因此,应用程序不应该使用catch块来捕获Error对象。在定义该方法时,也无须在其throws子句中声明该方法可能抛出Error及其任何子类。 - Exception
Exception 程序本身可以处理的异常,包括RuntimeException等运行时异(Unchecked 异常)和 检查异常(Checked 异常)。- RuntimeException是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
- 非运行时异常(Checked 异常)(编译异常) 包括:RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常
4. Java 比较器 Comparable 接口和 Comparator 接口
-
相同:
- 都需要继承接口后才能排序。
- 都需要配合Collections.sort或者Arrays.sort的方法来排序,并都能支持自定义排序规则。
-
区别
- Comparable接口用在类的设计初期支持排序。
- Comparator接口用在类设计已经完成后,还想排序。
-
Comparable 接口
在java API文档中描写叙述此接口是强行将实现它的每个类的对象进行总体排序-----称为该类的自然排序,实现此接口的对象列表和数组能够用Collections.sort(),和Arrays.sort()进行自己主动排序。
也就是说,仅仅要实现了这个接口的对象(数组)就相当于有了排序的能力。所以叫做comparable—可排序的。所以能够说这是一种内部排序的方式,通过实现它唯一的方法compareTo(),关于此方法以下会再提及。 -
Comparator 接口
对于它,则是针对一些本身没有比較能力的对象(数组)为它们实现比較的功能。所以它叫做比較器,是一个外部的东西,通过它定义比較的方式,再传到Collections.sort()和Arrays.sort()中对目标排序,并且通过自身的方法compare()定义比較的内容和结果的升降序。 -
关于排序结果升序和降序的疑问
学习使用这两个接口的时候,看到每一个实现它们的程序都是这样做的:
对于compareTo():
public int compareTo(Object o){
return this.val - o.val;//假定比较的val属性
}
//或者可以这样写
public int compareTo(Object o){
if(this.val > o.val){
return 1;
}else if(this.val < o.val){
return -1;
}else{
return 0;
}
return this.val - o.val;//假定比较的val属性
}
这样就能对实现 Comparable 接口的对象进行升序排序了。
而对于compare()方法呢,则是这样:
升序:
public int compare(Object a, Object b){
return a.val - b.val;
}
public int compare(Object a, Object b){
if(a.val > b.val)
return 1;
else if(a.val < b.val)
return -1;
else
return 0;
}
降序:
public int compare(Object a, Object b){
return b.val - a.val;
}
public int compare(Object a, Object b){
if(a.val > b.val)
return -1;
else if(a.val < b.val)
return 1;
else
return 0;
}
Arrays中sort()函数的解说,它能够将数组中的所有或部分元素啊数字升序进行排序!!所以说当实现这两个接口后排序调用的sort()函数都是依照升序的方式来排的。故,上面的两个方法是为它推断两个元素的大小而存在的,sort()会依照升序排所有元素。比compareTo(),是对象内部的排序实现,它会依照对象的自然顺序来排列,当推断两个对象 的大小时,就会參考compareTo()的结果。大于0则觉得this.val大,反之同理。调用comparetor比较器时。sort()传入compare(a,b)这两个參数,而返回值它则默认大于0就是a的属性值大于b的,反之同理。而假设此事我们反着来,用o.val-this.val 或 b.val-a.val那么得到的值是与正确的两个值大小相反的,而sort()不知道依照自己觉得的以升序确定先后。小的在前。大的在后;那么实际情况就会变为大的在前,小的在后,这样结果就成了降序排列了~~~~