1.内部类
1.1静态内部类
class OuterClass{
private static int a = 10;
static class InnerClass{
private int a = OuterClass.a;
} // 静态内部类
}
- 静态内部类指声明为static的内部类,它不依赖于外部类实例而被实例化,而通常的内部类需要在外部类实例化后才能实例化。
- 静态内部类不能与外部类具有相同的名字,不能访问外部类的普通成员变量和方法,只能访问外部类的静态成员变量和静态方法(包括私有类型)
- 静态内部类的实例化
InnerClass inner = new InnerClass
- 静态内部类获取同名外部类静态变量
private int a = OuterClass.a;
1.2成员内部类
class OuterClass{
private int a = 10;
private static int b = 20;
class InnerClass{
private int a = OuterClass.this.a;
private int b = OuterClass.b;
} // 成员内部类
}
- 成员内部类可以自由的引用外部类的属性和方法,无论是静态的还是非静态的
- 成员内部类与一个实例绑定在了一起,不可以定义静态的属性和方法
- 只有在外部类实例化以后这个内部类才能被实例化
- 成员内部类的实例化
OuterClass.InnerClass inner = new OuterClass(). new InnerClass();
成员内部类获取外部同名变量
- 非静态变量:
private int a = OuterClass.this.a;
- 静态变量:
private int b = OuterClass.b;
- 非静态变量:
1.3局部内部类
class OuterClass{
public void method{
class InnerClass{} // 局部内部类
}
}
- 局部内部类是指定义在一个代码块内的类,它的作用范围为其所在代码块
- 局部内部类像局部变量一样,不能别public,private,protected,static修饰,只能访问方法中定义为final类型的局部变量
- 将静态内部类去掉static移入外部类的静态方法中,就成了局部静态内部类,局部静态内部类与静态内部类的基本特性相同
- 将成员内部类移入到外部类的实例方法或实例初始化代码中,就成了局部内部类,局部内部类与内部类基本特性相同
1.4匿名内部类
class OuterClass{
public static void main(String[] args){
new Thread(new Runnable{ // 匿名内部类
@Override
public void run{
// 代码
}
}).start();
}
}
- 匿名内部类相当于一个子类,是对接口的实现或是对抽象类/普通类的继承
- 匿名内部类不能有构造函数
- 不能有静态成员,方法,类
- 不能是public,protected,private,static
- 只能创建内部类的一个实例
2.获取父类的类名
2.1获取本类的类名
- 通过
getClass.getName
获取本类名(只能用于本类的非静态方法)public class Test{
public void test{
System.out.println(this.getClass().getName);
}
public static void main(String[] args){
new Test().test();// Test(全类名)
}
}
- 通过
Thread.currentThread().getStackTrace()[1].getClassName();
获取本类类名,使用与静态方法public class Test{
public static void staticMethod() {
String className = Thread.currentThread().getStackTrace()[1].getClassName();
System.out.println(className);
}
public static void main(String[] args){
staticMethod();// Test(全类名)
}
}
2.2获取父类类名
通过反射拿到当前类的父类的Class,再获取它的类名
public class Test extends A {
public void getFatherClassName(){
System.out.println(this.getClass().getSuperclass().getName());
}
public static void main(String[] args) {
Test t = new Test();
t.getFatherClassName();
}
}
3.Java中的关键字
3.1变量名的命名规则
- 由字母数字下划线和$符构成
- 第一个字符不能是数字
- 不能用Java中的关键字,如:char,int等
3.2break、continue和return
- break用于跳出break所在的循环不执行break之后的任何代码,如果有外层循环则直接进行外层的下一次循环
public static void main(String[] args) {
for (int i=0; i<5; i++) {
for (int j=0; j<5; j++) {
if (j == 2) {
break;
}
System.out.print(j);
}
System.out.println();
}
}
/* 输出:
* 01
01
01
01
01
*/
- continue用于终止本次循环,调到本层循环的下一次循环
public static void main(String[] args) {
for (int i=0; i<5; i++) {
for (int j=0; j<5; j++) {
if (j == 2) {
continue;
}
System.out.print(j);
}
System.out.println();
}
}
/* 输出:
* 0134
0134
0134
0134
0134
*/
return用于返回方法所定义的返回值,当方法为void时可以省略,也可以写成
return;
,当方法中有try…catch…finally块时即使try…catch中有return语句,finally中的return也会执行(应该避免在finally中写return语句) ```java public class Demo01 {public static int testReturn() {
try {
return 0;
}catch (Exception e){
e.printStackTrace();
return -1;
}finally {
return 1;
}
} public static void main(String[] args) {
System.out.println(testReturn());
} // 输出1
}
4.
跳出多层循环可以在外层循环外面定义一个标识符如:`out:`,然后使用`break out;`,即可跳出外层循环
```java
public static void main(String[] args) {
out: // 标识符
for (int i=0; i<5; i++) {
for (int j=0; j<5; j++) {
if (j == 2) {
break out; // 满足条件时跳出外层循环
}
System.out.print(j);
}
System.out.println();
}
}
// 输出:01
3.3final、finally、finalize
1.final
final修饰的类不能被继承(一般为基本类型,如String,StringBuilder,StringBuffer等)
final修饰的方法不能被重写(可以重载,并且重载方法可以不用final修饰)
final修饰的变量不能被改变
- final修饰基本类型时,基本类型的值不可变
- final修饰引用类型时,引用指向的对象不可变,但是对象的内容可以改变
final变量初始化
非static修饰final变量:
定义时赋值初始化
定义时不赋值,但是需要在所有构造函数中赋值初始化
定义时不赋值,在初始化块中进行初始化
public class Demo02 {
private final int a;
{
a = 10;
}
}
static修饰的final变量
定义时赋值初始化
定义时不赋值,在静态初始化块中进行初始化
public class Demo02 {
private static final int b;
static {
b = 20;
}
}
2.finally
- finally是异常处理的一部分,只能用在try…catch…finally中,其中finally可以省略
- finally语句块表示该语句最终一定执行,经常被用在需要释放资源的情况下
- finally语句块中的return语句会覆盖try…catch…语句块中的return语句,所有应该避免在finally中书写return语句
3.finalize
- finalize时Object类的一个方法,在垃圾回收器执行回收时会调用被回收对象的finalize方法
- 可以重写此方法来实现对其他资源的回收,例如关闭文件
- 注意,一旦垃圾会收器准备好释放对象占用的空间时,将首先调用其finalize方法,并且在下一次垃圾回收时才会真正回收对象占用的内存
3.4assert
断言,一般用于单元测试
3.5static关键字的作用
- 为特定的数据类型或对象分配单一的存储空间,与创建对象的个数无关
- 实现某个方法或属性与类而不是对象关联在一起,也就是在不创建对象的情况下就可以通过类来直接调用方法或使用类的属性
- Java语言中static主要有4中使用情况:成员变量,成员方法,代码块和内部类
1.static成员变量
- static变量属于类变量,与创建的对象无关(当然可以通过对象进行修改)
- static变量在内存中只有一个复制,所有实例都指向同一个内存空间,只要静态变量所在的类被加载,那么静态变量就被分配空间
- static变量可以通过”类.静态变量”或者”对象.静态变量”
2.static成员方法
static方法是类的方法,不需要创建对象就可以使用,而非static方法是对象的方法,只有创建对象才能使用
static方法中不能使用this和super关键字,不能调用非static方法,访问非static变量,只能访问所属类的静态成员变量和静态成员方法
static的重要功能:实现单例模式
public class Singleton{
private Singleton instance = null;
private Singleton(){
}
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
3.static代码块
- static代码块是独立于成员变量和成员函数的代码块,他不在任何方法体内,JVM在加载类时会执行static代码块,如果有多个代码块,jvm会依次执行
- static代码块可以用来初始化静态变量
- static代码块只会在类加载时执行一次
4.static内部类
- static内部类是指被声明为static的内部类,他可以不依赖外部类实例对象而被实例化,而通常内部类需要在外部类实例化之后才能实例化。
- static内部类不能与外部类有相同的名字,不能访问外部类的普通成员变量和函数,只能访问外部类的静态成员变量和方法(包括私有)
- 只有内部类才能声明为static
5.各种变量
- 实例变量:变量归对象所有,(只有实例化之后才可以)。每实例化一个对象,会创建一个副本并初始化,如果么有显示初始化,则会初始化一个默认值,各个对象的实例变量互不影响
- 局部变量:定义在方法中的变量,使用前必须初始化
- 类变量:用static修饰的属性,变量归类所有,只要类被加载,这个变量就可以被使用,所有实例对象共享类变量
- final变量:表示这个变量为常量,不能被修改
3.6使用switch的注意事项
- 使用switch(expr)时,expr只能是一个枚举常量(内部也是有整型或字符型实现)或一个整数表达式,其中整数表达式可以是基本类型int也可以是其包装类Integer
- 其他可以默认转为int的数据类型也可以作为expr,例如short、byte和char类型,以及它们的包装类
- case后面可以接常量数组例如1、2,也可以是一个常量计算式,例如1+3,还可以是final型的常量(也可以计算),必须在定义出初始化,否则编译报错
- jdk1.7之后switch开始支持String类型,本质上来讲,对String的支持还是通过int进行值匹配
3.7volatile的作用
- 在Java编程中,编译器会对程序进行优化,经常被访问的变量会被放入缓存
- 但是在多线程中变量的值可能因为其他变量而改变了,但是缓存中的值却没有改变,从而造成不便
- volatile是一个类型修饰符,它被设计用来修饰不同线程访问和修改的变量。
- 被volatile修饰的变量不会放入缓存,而是直接从内存中取
3.8instanceof的作用
- 用来判断一个引用类型的变量所指向的对象是否为一个类/接口的实例
3.9strictfp的作用
- 用来保证浮点数运算的精确性
4.基本类型与运算
4.1Java中的基本数据类型
基本类型 | 包装类 |
---|---|
int | Integer |
short | Short |
byte | Byte |
float | Float |
double | Double |
long | Long |
char | Character |
boolean | Boolean |
此外还有一种基本类型void,对应的封装类为java.lang.void,但是无法对其进行操作
- 原始数据类型在传递参数时都是按值传递,而应用类型则是按引用传递
- 引用数据类型会初始化为null,而基本数据类型的默认值与他的类型有关
- 在Java中默认的小数是double,所以在初始化float时可以使用
float a = 01.f
或者float a = (float)0.1
- 同理Java中默认的整型数字是int,所以要写long型的数字可以使用
long l = 123439546574687L
其中L也可以小写 - Java中的
null
值不是一个合法的Object实例,编译器没有为其分配内存,它仅仅表示该引用没有指向任何对象
4.2不可变类
不可变类,指一旦创建这个类的实例之后就不允许修改他的值,也就是说,一个对象一旦被创建出来,在他的生命周期中,他的成员变量就不能再改变。
Java中所有的包装类都是不可变类,还有String也是不可变类
不可变类的设计需要遵循4条基本原则
类中所有成员变量被private修饰
类中没有 写或者修改成员变量的方法,例如setXxx,只提供构造函数,一次生成永不改变
确保类中所有方法会被子类覆盖,可以通过把类定义为final或者把类中的方法都定义为final
如果类中的一个成员变量不是不可变量,那么在成员初始化时或者使用get方法获取该成员变量时,需要使用clone方法来保证类的不可变性
public class ImmutableClass2 {
private int a ;
private Person per;
public ImmutableClass2(int a, Person per) {
this.a = a;
this.per = per.clone();
}
public int getA() {
return a;
}
public Person getPer() {
return per.clone();
}
}
4.3值传递和引用传递
基本类型的传递是按值传递,原来的值复制一份,传递给形参,对形参的操作不会影响原来的值
引用传递是将实参是指向的的对象的地址传递给形参,此时形参也指向该对象,对形参的操作会影响实参
但是对于不可变类对象的引用需要注意:当将不可变类对象的引用传递给形参时,对形参的操作会切断形参与原对象的指向关系,形参将指向新的不可变类对象 ```java package junhaox.cn.unchange;
public class Test { public static void main(String[] args) { // 不可变类对象的引用 String str = “hello”; // 普通对象的引用 StringBuilder sb = new StringBuilder(“hello”); // 不可变类对象的引用传递 test05(str); // 普通对象的引用传递 test06(sb);
System.out.println(str);// hello
System.out.println(sb);// hello world
}
public static void test05(String str) {
str += " world";
}
public static void test06(StringBuilder sb) {
sb.append(" world");
}
}
<br />![](http://tc.junhaox.cn/img/20200515144445.jpg#alt=)
####4.4不同数据类型之间的转换规则
**默认低精度向高精度自动转换**
-
byte<short<char<int<int<long<float<double
-
基本类型分3类,字符型char,布尔型boolean,数值型int、short、long、float、double
-
char在向高级类型转换时(int, long, float等),会自动转为其对于的ASCII码
-
byte不能自动转换为char,其他正常,因为byte有符号,char无符号
-
byte,short,char在参与运算时会转为int,但是使用"+="运算符时不会产生类型转换
-
当基本数据类型遇到boolean类型时是不能自动转换的
-
参与运算时的整数才默认是int型的,赋值时不算(目前理解)
```java
byte by1 = 10; // 正确
byte by2 = by1 + 10; // 编译错误
- byte类型的范围[-128, 127]的理解 ``` byte 一个字节 8位 例如 0000 0001 十进制表示1 在计算机中最高位表示符号位,0表示正数,1表示负数 由此可以简单推算byte类型正负数的范围 正数的范围是[1, 127], [0000 0001, 0111 1111] 负数的范围是[-127, -1], [1111 1111, 1000 0001] 但是存在两个0 +0:0000 0000 -0:1000 0000 必须舍弃一个(-0),且规定1000 0000表示-128 到此就可以结束了,简单理解了byte的范围
补充: 1.计算机中的负数是用负数的原码的补码存储的 2.计算机中减法是用加法(补码做加法)来做的(符合位参与计算) 3.负数的补码 = 负数原码的反码 + 1 4.正数的原码,补码,反码都是其本身,也可以说正数没有反码,补码 如何验证-0:1000 0000(补码)表示-128? 只需要验证[-128, 127]是一个循环 即:-128 + 1 = -127 , 127 + 1 = -128 -128 + 1 = -127: 1000 0000(补) + 0000 0001(补) = 1000 0001(补) 反码 = 1000 0001(补) - 1 = 1000 0000 原码 = 1111 1111 = -127(10) 127 + 1 = -128: 0111 1111(补) + 0000 0001(补) = 1000 0000(补) 1000 0000(补) = -128(10) 难以解决的问题 -128 的 原码是什么?
**强制类型转换**
- 除了byte(低精度)转char(高精度)需要强转,其他都是高精度向低精度转需要强转
- byte,char,short在参与运算时会先转为int在进行计算,例如两个short值相加的到的是int
- 此外"+="运算符在Java编译时会进行特殊处理,`short s1 = 1; s += 1`可以编译通过,当然"++"运算符也可以
<a name="699a961e"></a>
#### 4.5Math类中的取整函数
- `Math.round();`四舍五入
- `Math.floor()`向下取整
- `Math.ceil()`向上取整
<a name="24c4d3b9"></a>
#### 4.6i和i
-
i++先运算后自增
-
++i先自增在运算
```java
public static void main(String[] args){
int i = 1;
System.out.println(i++ + i++);
System.out.println("i=" + i);
System.out.println(i++ + ++i);
System.out.println("i=" + i);
System.out.println(i++ + i++ + i++);
System.out.println("i=" + i);
/*
结果:
1 + 2 = 3
i=3
3 + 5 = 8
i = 5
5 + 6 +7 = 18
i=8
*/
}
4.7移位
byte、short、char移位时默认转为int在进行移位
<<左移,舍去高位,低位补0,左移几位相当于“原值*(2的位移次幂)”,例如1<<2的十进制为4
有符号右移,负数高位补1,正数高位补0
int型的数字
public static void main(String[] args) {
int i = -4;
System.out.println("i的二进制"+Integer.toBinaryString(i));
//11111111111111111111111111111100
System.out.println("i右移1位"+Integer.toBinaryString(i>>1));
//11111111111111111111111111111110
int j = 4;
System.out.println("j的二进制"+Integer.toBinaryString(j));
//100
System.out.println("j右移1位"+Integer.toBinaryString(j>>>1));
//10
}
short型的数字,short、char、byte在进行移位时会转换为int,所以和int没什么区别
public static void main(String[] args) {
short i = -4;
System.out.println("i的二进制"+Integer.toBinaryString(i));
//11111111111111111111111111111100
System.out.println("i右移1位"+Integer.toBinaryString(i>>1));
//11111111111111111111111111111110
short j = 4;
System.out.println("j的二进制"+Integer.toBinaryString(j));
//100
System.out.println("j右移1位"+Integer.toBinaryString(j>>>1));
//10
}
无符号右移,无论正负高位都补0
int型
public static void main(String[] args) {
int i = -4;
System.out.println("i的二进制"+Integer.toBinaryString(i));
//11111111111111111111111111111100
System.out.println("i右移1位"+Integer.toBinaryString(i>>>1));
//01111111111111111111111111111110
int j = 4;
System.out.println("j的二进制"+Integer.toBinaryString(j));
//100
System.out.println("j右移1位"+Integer.toBinaryString(j>>>1));
//10
}
short型,直接打印short右移的值时和int型没有区别
public static void main(String[] args) {
short i = -4;
System.out.println("i的二进制"+Integer.toBinaryString(i));
//11111111111111111111111111111100
System.out.println("i右移1位"+Integer.toBinaryString(i>>>1));
//01111111111111111111111111111110
short j = 4;
System.out.println("j的二进制"+Integer.toBinaryString(j));
//100
System.out.println("j右移1位"+Integer.toBinaryString(j>>>1));
//10
}
short型,当使用short接收自己右移之后的值,在打印二进制时会发生变化
public static void main(String[] args) {
short i = -1;
System.out.println("i的二进制"+Integer.toBinaryString(i));
//11111111111111111111111111111111(32位)
i >>>= 10;
System.out.println("i右移10位"+Integer.toBinaryString(i));
//预期:1111111111111111111111(22位)
//实际:11111111111111111111111111111111(32位)
}
解释:i无符号右移得到:
00000000001111111111111111111111
将i无符号右移的到的值赋给short型的i(2个字节),这时候会取后16位1111111111111111即-1,然后打印-1二进制(先转为int型)得到11111111111111111111111111111111(32位)
4.8char类型存储汉字
- Java中的默认编码为Unicode,每个字符占2个字节,char类型的每个字符也是2个字节,所以char可以存储一个汉字
- String虽然是由char所构成,但是它采用了一种更加灵活的方式来存储,即英文占一个字节,中文占两个字节(和工程的默认编码有关)
5.字符串与数组
5.1字符串的创建存储机制
创建字符串的两种方式:
String s1 = new String("abc");String s2 = new String("abc")
,存在两个引用对象s1,s2,两个相同的字符串对象”abc”,他们的内存地址是不同的,只要new就会产生新的对象String s1 = "abc";String s2 = "abc";
,创建String的引用s1,s2指向字符串常量池中的”abc”
- Java中的字符串统一放在字符串常量池堆中,不论是直接将引用指向字符串常量池中的对象,还是通过new操作间接引用常量池中的字符串
String s = "abc"; //将"abc"放入常量池中
String s = "a" + "bc"; //把"a","bc"转为字符串常量"abc"存到常量区
String s = new String("abc"); // 在运行时将"abc"放到堆里面
- 通过字面量给字符串赋值在编译时启动前就将字符串放入字符串常量池中,而通过new的字符串是在运行时才放入堆中
String s1 = "abc";//在常量区里面存放一个"abc"字符串对象
String s2 = "abc";//s2引用常量区的对象,因此不会创建对象
String s3 = new String("abc");//在堆中创建新的对象
String s4 = new String("abc");//在堆中又创建一个新的对象
5.2字符串的拆分
1.使用String类中split()方法
给定字符串”welcome#to#our#country###”根据’#’进行拆分
String.split("#")
相当于给定限制参数为0,尽可能多的拆分,所得数组中不包含空字符串[welcome, to, our, country]
String.split("#", n)
,双参分割法,给定限制分割参数为n,即拆分的次数为n-1,数组长度不会超过n-1,当n为3时拆分结果为[welcome, to, our#country###]
String.split("#", 0)
,和单参分割相同,尽可能的多分割,但是会舍去数组中的空字符串[welcome, to, our, country]
String.split("#", -1)
,限制参数为负数时,尽可能的多拆分,保留拆分中的空字符串[welcome, to, our, country, , , ]
2.使用StringTokenizer进行拆分
StringTokenizer的构造函数
StringTokenizer(String str, String delim, boolean returnDelims)
其中str是要拆分的字符串,delim是分隔符,returnDelims表示分隔符是否作为标记返回,如果为true,每个分隔符都作为长度为1的字符串进行返回;如果为false则跳过分隔符StringTokenizer(String str, String delim)
此时returnDelims的默认值为falseStringTokenizer(String str)
此时分隔符默认的分隔符集 “ \t\n\r\f”,即:空白字符、制表符、换行符、回车符和换页符。分隔符字符本身不作为标记。当delim为null时,构造方法本身不抛出异常,但是调用其他方法时可能会报NullPointException
当str为null是抛出NullPointException
public static void testStringTokenizer() {
String str = "welcome##to##our##country###";
StringTokenizer tokenizer = new StringTokenizer(str, "##", false);
while (tokenizer.hasMoreTokens()) {
System.out.println("--"+tokenizer.nextToken());
}
}
/*
--welcome
--to
--our
--country
*/
public static void testStringTokenizer() {
String str = "welcome##to##our##country###";
StringTokenizer tokenizer = new StringTokenizer(str, "##", true);
while (tokenizer.hasMoreTokens()) {
System.out.println("--"+tokenizer.nextToken());
}
}
/*
--welcome
--#
--#
--to
--#
--#
--our
--#
--#
--country
--#
--#
--#
*/
推荐使用split
5.2Java中的数组
对于维数组的声明[]必须为空
数组是具有相同类型的数据集合,他们具有固定的长度,在内存中占据连续的空间
Java中的数组是对象,可以通过instanceof来判断
int[] a = {1, 2};
System.out.println(a instanceof int[]);// true
Java中一维数组初始化的方式(以int[]为例)
int[] a;
然后初始化int a[];
然后初始化对于以上两种方式,在数组定义时不会为其分配内存空间,而是在初始化时在分配空间;对于基本类型会在初始化时会初始化对于的默认值(例如int会被初始化为0),对象类型会被初始化为null。
// 先定义在初始化的三种方式
a = new int[3];// 分配空间并初始化为0
a = {1, 3, 5};// 分配空间并初始化为对于值
a = new int[]{1, 3, 5};// 分配空间并初始化为对于值
- 边定远变初始化
int[] a = new int[3];// 分配空间并初始化为0
int[] a = {1, 3, 5};// 分配空间并初始化为对于值
int[] a = new int[]{1, 3, 5};// 分配空间并初始化为对于值
// 对于以上几种方式都可以写成int a[]...的形式
Java中二维数组的声明方式(以int[][]为例)
int[][] a;
int a[][];
int[] a[];
初始化
int[][] a = {{1, 3..}, {3, 4..},{5, 5}...}
int[][] a = new int[2][3];// 声明并初始化0
// 定义一个每一行中列数不相同的数组
int[][] a = new int[2][];
a[0] = new int[3];
a[1] = new int[4];
- 遍历二维数组
//普通for循环
for(int i=0; i<a.length; i++){
for(int j=0; j<a[i].length; j++){
System.out.puintln(a[i][j]);
}
}
//增强for循环
for(int[] arr:a){
for(int i:arr){
System.out.println(i);
}
}
6.异常处理
1.finally代码块的执行时机
finally代码块一定执行,除了以下两种情况:
- 进入try之前程序抛出异常
- 在try或catch代码块中手动执行
System.exit(-1);
finally代码块会在return语句之前被执行,所以finally代码块中的return语句会覆盖其他代码块中的return语句
执行return语句之前会将return中的变量拷贝一份到寄存器或内存中
- 基本类型拷贝后再在finally(finally中没有return语句)中修改不会影响try或catch中return的值
引用类型拷贝后再在finally(finally中没有return语句)中修改对象的内容会影响try或catch中return的值,因为拷贝的是指向对象的一个引用,而不是对象本身,两个引用指向同一个对象 ```java public class Demo02 { public static int testFinally1() { int a = 1; try {
return a;
} finally {
a = 2;
} } public static StringBuilder testFinally2() { StringBuilder sb = new StringBuilder(“hello”); try {
return sb;
} finally {
sb.append(" world");
} }
public static void main(String[] args) { System.out.println(testFinally1()); // 1 System.out.println(testFinally2()); // hello world }
} ```
6.2Throwable是所有异常处理的基类,所以Throwable以及其所有子类都可以用throw抛出
6.3运行时异常和普通异常以及Error
- Error类型的异常属于非常严重的错误是JVM层次的错误是应该被解决的错误,编译器不会检查Error是否被处理,因此不推荐去捕获Error类型的异常
- Exception表示可恢复的异常,编辑器可以捕捉到的。它的类型包含两种:检查异常(checked exception)和运行时异常(runtime excepiton)
检查异常:
- 所有继承自Exception且不是RuntimeException的异常都是检查异常
- 检查异常发生在编译阶段,Java编译器强制程序去捕获此异常例如IO异常和SQL异常,这些异常都发生在编译阶段,不捕获则编译时抛出Error
java.lang.Error: Unresolved compilation problem
运行时异常:
- 编辑器不要求对其进行捕获处理。如果不对其进行处理,当出现这种异常时,会有JVM处理。例如NullPointerException,ClassCastException等
- 当出现异常时,系统会将这种异常一直往上层抛出,知道遇到处理块,如果没有处理块,则抛到最上层;如果是多线程则又
Thread.run()
抛出(正常情况下run方法中不允许抛出异常,所以一定会处理);如果是单线程就由main方法抛出,抛出之后程序就终止
- 对于异常的捕获应该先捕获子类异常,在捕获父类异常