涉及代码见github
第一章 异常
1.1 异常概念
· 异常:指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。
Java等面向对象的语言中,异常本身是一个类。产生异常就算创造一个异常对象,Java处理异常就是中断处理。
异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行。
1.2 异常体系
所有错误或者异常的超类是 java.lang.Throwable
,其下有两个子类:java.lang.Error
与java.lang.Exception
,平常所说的异常指 java.lang.Exception
。
· Exception:编译期异常,进行编译(写代码)Kava程序出现的问题
- RuntimeException:运行期异常
注:把异常处理掉,程序就可以继续执行。
· Error:错误。
注:错误必须修改源代码,程序才能继续执行。
例一 :编译期异常
SimpleDateFormat的parse()方法本身有编译期异常。处理方法:
把异常抛出,交给虚拟机来处理。方式:中断处理,中止程序把异常打印在控制台。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public static void main(String[]args)throwsParseException{
//Exception:编译期异常
//格式化日期
// 1. throwsParseException处理异常
SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-dd");
//把字符串格式的日期解析为Date格式的日期
/*
正常情况
Datedate=sdf.parse("1999-8-16");
System.out.println(date);//MonAug1600:00:00CST1999
*/
/*
不正常情况
*/
Datedate=sdf.parse("19998-16");
System.out.println(date);//ParseException解析异常
}缺点:正常执行,没有问题。如果模式或者字符串有问题有问题,会报解析异常。
选择try-catch处理异常。
优点:处理后,程序可以正常执行。如果异常,控制台会输出ParseException,不过也会打印后续代码1
2
3
4
5
6
7
8
9//2.try-catch处理异常
Datedate=null;
try{
date=sdf.parse("19998-16");
}catch(ParseExceptione){
e.printStackTrace();
}
System.out.println(date);
System.out.println("后续代码");
例二:运行期异常
1 | int[]arr={1,2,3}; |
输出:
java.lang.ArrayIndexOutOfBoundsException: 3
后续代码
例三:Error
必须修改代码。
1 | //Error |
1.4 异常产生过程解析
第二章 异常的处理
2.1 抛出异常throw
可以使用throw关键字在指定方法中抛出指定的异常。
使用格式:throw new xxxException(“异常产生的原因”);
注意:
throw必须写在方法内部
throw后面的new的对象是Exception或者是Exception的子类对象
1
2
3if(arr == null) {
throw new NullPointerException("传递的数组的值为null");
}
*空指针异常是一个运行期异常,我们不用处理,默认交给JVM处理。
- throw抛出指定的异常对象,我们就必须处理这个异常对象
-如果throw后创建的是RuntimeException或者是RuntimeException的子类对象,我们可以不处理,默认交给JVM处理(打印异常对象,中断程序)
-如果throw后创建的是编译异常,我们就必须处理这个异常,要么throws,要么try-catch
- 以后我们首先必须对方法传递过来的参数进行合法性校验。如果参数不合法,就必须使用抛出异常的方法,告知方法的调用者,传递的参数有问题。
1
2
3If(index < 0 || index > arr.length - 1) {
throw new ArrayOutOfBoundException("传递的数组超出了数组的范围");
}
*ArrayOutOfBoundException同样是一个运行期异常
2.2 Objects非空判断
Objects类中的静态方法:(简化代码)public static <T> T requireNonNull(T obj)
:查看指定引用对象是不是null。
源码:
1 | public static <T> T requireNonNull(T obj) { |
· 在方法中直接使用Objects.requireNonNull(obj)
来代替类似于该方法源码那样自己写throw来抛出参数为null的异常。
· 也可以Objects.requireNonNull(obj, "传递的对象的值是null")
,自行添加message。
2.3 声明异常throws
throws关键字:异常处理的第一种方式,交给别人处理
作用:当方法内部抛出异常对象的时候,我们就必须处理这个异常的对象。可以使用throws来处理,会把异常对象声明抛出给方法的调用者处理,最终交给JVM处理(中断处理)
使用格式:方法声明时使用
1 | 修饰符 返回值类型 方法名(参数) throws AAAException, BBBException…{ |
注意:
- throws必须写在方法声明处
- throws后面声明的异常必须是Exception或者是Exception的子类
- 方法内部如果抛出了多个异常对象 ,那么throws后必须也声明多个异常。
【如果抛出的多个异常有子父类关系,只需要声明父类异常即可】 - 调用一个声明抛出异常的方法,我们就必须处理声明的异常。
- 要么继续使用throws声明抛出,交给方法的调用者处理,最终交给JVM–>中断处理。
- 要么try-catch,自己处理异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public static void main(String[] args) throws FileNotFoundException,IOException { //交给JVM处理
readFile("c:\\a.tx");
System.out.println("后续代码");
}
/*
定义一个方法,对传递的文件路径进行合法性判断
*/
public static void readFile(String fileName) throws FileNotFoundException,IOException{
if(!fileName.equals("c:\\a.txt")){
throw new FileNotFoundException("传递的文件路径不是c:\\a.txt");
}
/*
如果传递的路径,不是.txt结尾
那么我们就抛出IO异常对象,告知方法的调用者,文件的后缀名不对
*/
if(!fileName.endsWith(".txt")){
throw new IOException("文件的后缀名不对");
}
System.out.println("路径没有问题,读取文件");
}
}
*FileNotFoundException是编译异常,抛出了编译异常,就必须处理这个异常
*可以使用throws继续声明抛出FileNotFoundException这个异常对象,让方法调用者处理
FileNotFoundException extends IOException extends Excepiton
,
所以public static void readFile(String fileName) throws FileNotFoundException,IOException
还可以用public static void main(String[] args) throws IOException
或者public static void main(String[] args) throws Exception
代替。
缺陷:如果有后续代码,无法执行。
2.4 捕获异常try-catch
try-catch:异常处理第二种方式,自己处理异常
格式:
try{
**可能产生**异常的代码
}catch(定义一个异常的**变量**,用来**接收**try中抛出的异常对象){
异常的处理逻辑,接收异常对象之后,怎么处理异常对象
【一般在工作中,会把异常的信息记录到一个日志中】
}
...
catch(异常类名 变量名){
}
注意:
1. try中可能会抛出多个异常对象,那么就可以使用多个catch来处理这些异常对象
2. 如果try中产生了异常,那么就会执行catch中的异常处理逻辑, 执行完毕catch中的处理逻辑, 继续执行try…catch之后的代码;
如果try中没有产生异常,那么就不会执行catch中异常的处理逻辑,执行完try中的代码,继续执行try…catch之后的代码
调用方法输出异常信息
方法名 | 说明 |
---|---|
void printStackTrace() | 输出异常的堆栈信息 |
String getMesssage() | 返回异常信息描述字符串,是printStackTrace()输出信息的一部分 |
常见的异常类型
1 | publicstaticvoidmain(String[]args){ |
2.5 Throwable类中3个异常处理的方法
- `String getMessage()`返回此 throwable 的简短描述。
- `String toString()` 返回此 throwable 的详细消息字符串。
- `void printStackTrace()` JVM打印异常对象,默认此方法, 打印的异常信息是最全面的
在上一个例子的catch中的异常处理逻辑使用:
1 | /* |
2.4 finally 代码块
格式:
try{
} catch() {
}
…
catch() {
} finally {
无论是否出现异常都会执行
}
注意:
1. finally不能单独使用,必须和try一起使用
2. finally一般用于资源释放(资源回收),无论程序是否出现异常,最后都要资源释放(IO)
2.5 异常注意事项
多个异常使用捕获又该如何处理?
- 多个异常分别处理。(多个try-catch)
- 多个异常一次捕获,多次处理。(一个try,多个catch)
注意事项:
- catch里边定义的异常变量,如果有子父类关系,那么子类的异常变量必须写在上边,否则就会报错
“多态”:try中的子类异常也可以被父类异常变量catch到进行处理,所以下面的子类异常变量无法被使用,就会报错。
- 多个异常一次捕获一次处理。(catch的异常变量直接写为
Exception e
)
*运行时异常被抛出可以不处理,即不捕获也不声明抛出。
默认给虚拟机处理,终止程序,什么时候不抛出运行时异常了,再来继续执行程序
*尽量不要在finally中写return,否则返回的总是finally中的return。
*子父类的异常:
子类重写父类方法时,
- 抛出和父类相同的异常
- 或者抛出父类异常的子类
- 或者不抛出异常
- 如果父类方法没有抛出异常,子类也不可抛出异常。
此时子类产生该异常,只能捕获处理,不能声明抛出
第三章 自定义异常
3.1 概述
自定义异常类:
java提供的异常类, 不够我们使用, 需要自己定义一些异常类
格式:
public class XXXExcepiton extends Exception | RuntimeException{
添加一个空参数的构造方法
添加一个带异常信息的构造方法
}
** 注意**:
- 自定义异常类一般都是以Exception结尾,说明该类是一个异常类
- 自定义异常类,必须的继承Exception或者RuntimeException
-继承Exception:那么自定义的异常类就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try…catch
-继承RuntimeException:那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)
3.2 自定义异常练习
第四章 多线程
4.1 并发与并行
并发:交替执行(单CPU系统)
并行:同时执行(多CPU系统)
4.2 进程与线程
· 进程:进入到内存中的应用程序。
进程也是程序的一次执行过程,是系统运行程序的基本单位
· 线程:进程的一个执行单元。
· 线程调度:
- 分时调度:所有线程轮流进行,均分时间
- 抢占式调度:优先让优先级高的线程使用 CPU,如果优先级相同,就随机。
注:
java使用的是抢占式调度;
win10设置优先级:任务管理器–>详细信息–>右键某应用程序–>选择设置优先级
对于CPU的一个核而言,某个时刻,只能执行一个线程。多线程程序并不能提高程序运行速度,但能提高程序运行效率。
· 主线程:执行主方法(main())的线程
· 单线程程序:java程序中只有一个线程
- 执行从main方法开始,从上到下依次执行。
所以中间一旦出现异常,中止程序,后面代码不能执行。
4.3 创建线程类(一)
1)创建多线程程序的第一种方式: 创建Thread类的子类java.lang.Thread
类: 是描述线程的类,我们想要实现多线程程序,就必须继承Thread类
· 实现步骤:
- 创建一个Thread类的子类
- 在Thread类的子类中重写Thread类中的run方法, 设置线程任务(开启线程要做什么)
- 创建Thread类的子类对象
- 调用Thread类中的方法start方法,开启新的线程,执行run方法
void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
1)结果是两个线程并发地运行;当前线程(main线程)和另一个线程(创建的新线程, 执行其 run 方法)。
2)多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
3)java程序属于抢占式调度,那个线程的优先级高,那个线程优先执行;同一个优先级,随机选择一个执行1
2
3
4
5
6
7
8
9
10public static void main(String[] args) {
// 3.创建Thread类的子类对象
MyTread mt = new MyTread();
// 4.调用Thread类中的方法start方法,开启新的线程,执行run方法
mt.start();
for (int i = 0; i < 20; i++) {
System.out.println("main"+i);
}
}案例“随机打印结果”见GIthub:Demo07Thread1
2
3
4
5
6
7
8
9
10// 1.创建一个Thread类的子类
public class MyTread extends Thread{
// 2.在Thread类的子类中重写Thread类中的run方法,设置线程任务
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run"+i);
}
}
}
4.4 多线程原理
多线程程序_随机打印结果原理:
多线程程序_随机打印内存图解:
4.5 Thread类
4.5.1 常用方法
· 获取线程的名称两种方法:
1. 使用Thread类中的方法getName()
String getName()
返回该线程的名称。
2. 可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
static Thread currentThread()
返回对当前正在执行的线程对象的引用。
1 | // 在run方法中获取的是新线程的名称 |
· 设置线程名称两种方法(了解):
1. 使用Thread类中的方法setName(名字)
void setName(String name) 改变线程名称,使之与参数 name 相同。
2. 创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字
Thread(String name)
分配新的 Thread 对象。
e.g 在main方法中,开启多线程
1 | //方法一 |
- `public static void sleep(long millis)`: 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。<font color = #F9000>1000ms = 1s</font>
毫秒数结束之后,线程继续执行
方法有异常,可以用try-catch处理异常
4.6 Runnable
4.6.1 创建线程类(二)
2)创建多线程程序的第二种方式: 实现 java.lang.Runnable
接口
- Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
- java.lang.Thread类的构造方法
`Thread(Runnable target)` 分配新的 Thread 对象。
`Thread(Runnable target, String name)` 分配新的 Thread 对象。
实现步骤:
1. 创建一个Runnable接口的实现类
2. 在实现类中重写Runnable接口的run方法,设置线程任务
3. 创建一个Runnable接口的实现类对象
4. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5. 调用Thread类中的start方法,开启新的线程执行run方法
1 | public static void main(String[] args) { |
1 | public class RunnableImpl implements Runnable{ |
上述案例见GitHub:Demo08Runnable
4.6.2 Tread和Runnable区别
实现Runnable接口创建多线程程序的好处:
1. 避免了单继承的局限性
一个类只能继承一个类,类继承了Thread类就不能继承其他的类
实现了Runnable接口,还可以继承其他的类,实现其他的接口
2. 增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
· 实现类中,重写了run方法:用来设置线程任务
· 创建Thread类对象,调用start方法:用来开启新线程
4.7.3 匿名内部类方式实现线程创建
匿名内部类作用: 简化代码
- 把子类继承父类,重写父类的方法,创建子类对象合为一步完成
- 把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物: 子类/实现类对象, 而这个类没有名字
格式:
1 | new 父类/接口() { |
e.g
1 | // 线程的父类是Thread |