1、异常
1、概念:
程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。
异常本身是一个类,产生异常就是:创建异常这个对象并抛出这个异常对象。
2、Java中的异常体系:
(1)异常的根类是 java.lang.Throwable ,其下有两个子类:java.lang.Error 与 java.lang.Exception ,平常所说的异常指 java.lang.Exception 。
Throwable: 异常和错误的父类
|---: Error错误,程序无法处理的
|---: Exception异常,程序可以处理的
|---: 编译时异常,必须处理的
|---: 运行时异常,可以处理,可以不处理
(2)异常的分类:
a.编译时异常:RuntimeException及其子类都是运行时异常
特点:代码一写完就报错,不处理通不过编译
a.运行时异常:除了运行时异常以外的都是编译时异常
特点:不处理也能通过编译,运行才报错
3、JVM处理异常的方式———中断处理
1.打印异常信息
2.停止程序
4、异常的产生过程解析
(1)当程序执行到第20行,发现a[10]出现了s数组索引越界,就会创建一个ArrayIndexOutOfBoundsException异常对象
(2)异常出现后,先会在其所在的方法getNumber()中查看是否有处理异常的代码,没有的话进入下一步。
(3)将该异常抛给调用该方法的地方(main方法调用的,也就是抛给main方法),然后再在main()方法中查看该方法有没有处理异常的代码,没有,则进入下一步。
(4)将该异常交给JVM,JVM收到该异常后,做两件事:a.打印异常信息 b.停止程序
图解:
5、throw关键字【创建】异常
1、throw的作用: 创建/产生/制造异常 (搞事情)
2、使用格式:throw new 异常类名("异常的提示信息")
6、Java中处理异常的2种方式之【throws关键字处理异常】(不负责)
(1)throws概念:
处理异常的一种方式,声明异常,,将异常抛给别人处理。(不负责任的)
(2)使用格式:
修饰符 返回值类型 方法名(参数列表) throws 异常的类名1, 异常的类名2 {
...
}
(3)throw 和 throws的区别:
throw:
作用:** 创建/制造异常
格式: throw new 异常类名(); throw后面跟的是异常的对象
数量: throw后面只能跟一个异常对象
throws:
作用: 处理异常的一种方式,声明异常,让别人处理异常
格式: throws后面跟的是异常的类名
数量: throws后面可以跟多个异常的类名
7、Java中处理异常的2种方式之【try…catch处理异常 】(负责)
(1)概念:
处理异常的一种方式,不同于throws,而是处理该异常。(负责任的)
(2)使用格式:
try {
可能出现问题的代码;
} catch (异常类名 变量名) {
处理异常的代码;
}
(3)处理异常时代码执行情况
代码没有问题的情况:
1.先执行try中代码,发现try中代码没有异常
2.执行完try中所有代码
3.不会执行catch中的代码
4.走catch后面代码
代码有问题的情况:
1.先执行try中代码,发现try中代码有异常
2.不会执行try后续的代码
3.跳到catch中处理异常(执行cash中的代码)
4.走catch后的代码
(4)try…catch处理异常的好处
当出现异常后,我们处理了异常,程序会接着往下走,不会崩溃
(5)try…catch处理多异常
try {
可能出现异常的代码;
} catch (异常类名1 变量名) {
处理第一个异常的代码;
} catch (异常类名2 变量名) {
处理第二个异常的代码;
}
注意:异常可以抓父类,但是父类异常放后面。(否则会报错(编译报错))
// 异常可以抓父类,但是父类异常放后面
//Exception是ArrayIndexOutOfBoundsException的父类
try {
int c = a / b;
System.out.println(c);
int x = arr[10];
System.out.println(x);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("aaaaaaaaaaaa");
} catch (Exception e) {
System.out.println("我处理了除数为0的异常");
}
8、finally代码块
(1)概念:
代码块被finally修饰,则该代码块的代码一定会被执行
(2)使用格式:
finally {
一定会被执行的代码
}
常与try…catch一起使用
try {
可能出现异常的代码
} catch (异常类名 变量名) {
处理异常的代码
} finally {
一定会被执行的代码
}
(3)使用效果:一定执行
(即使该代码块在return语句后面,也一定会执行)
public static void main(String[] args) {
int a = 10;
int b = 0;
try {
int c = a / b;
System.out.println(c);
} catch (ArithmeticException e) {
System.out.println("处理除数为0的异常");
return; // 结束方法
} finally {
System.out.println("我是一定会被执行的代码");
System.out.println("我是一定会被执行的代码");
}
System.out.println("走我了吗?");
}
(4)使用场景
一定会被执行的代码就放到finally中。如IO流的关闭,释放数据库的连接,释放锁 (释放资源的代码)。
9、异常类Throwable中的常用方法
String getMessage() 返回此throwable的详细消息字符串。(返回异常的原因)
String toString() 返回此throwable的简短描述。(返回异常的类名和原因)
void printStackTrace() 将此throwable和其追溯打印到标准错误流。打印异常的堆栈信息
public static void main(String[] args) {
int a = 10;
int b = 0;
System.out.println("我是黑色");
// err表示错误信息,在控制台显示的文字是红色的
System.err.println("我是红色");
try {
int c = a / b;
System.out.println(c);
} catch (ArithmeticException e) {
System.out.println(e.getMessage()); // / by zero
System.out.println(e.toString()); // java.lang.ArithmeticException: / by zero
e.printStackTrace(); // java.lang.ArithmeticException: / by zero at com.itheima.demo08异常中常用方法.Demo08.main(Demo08.java:21)
}
}
10、关于类在抛出(throws)异常时的注意事项:
1.子类重写方法throws的异常要比父类方法throws的异常少(见父、子类方法的test01()和Test02()方法。
2.子类重写方法throws的异常可以是父类方法throws异常的子类
3.父类方法没有throws异常,子类方法只能try...catch处理
(PS:原则:异常越少越好)
见以下代码:
父类
class Fu {
public void test01() throws IOException, ParseException {}
public void test02() throws IOException {}
public void test03() {}
子类
class Zi extends Fu {
// 1.子类重写方法throws的异常要比父类方法throws的异常少
@Override
public void test01() throws IOException {}
// 2.子类重写方法throws的异常可以是父类方法throws异常的子类
@Override
public void test02() throws FileNotFoundException {}
// 3.父类方法没有throws异常,子类方法只能try...catch处理
@Override
public void test03() {
try {
FileInputStream fis = new FileInputStream("abc");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
10、自定义异常
(1)为什么要自定义异常
JDK提供的异常虽然比较多,但是不一定符合我们的需求。此时我们可以根据自己的业务来定义异常类,例如年龄负数问题,考试成绩负数问题。(这里的自定义异常都是指定义运行时异常。)
(2)自定义异常的步骤:
1.定义类继承Exception/RuntimeException
2.编写构造方法,调用父类的构造方法
见代码
// 1.定义类继承Exception
// 年龄小于18的异常
public class U18Exception extends Exception {
// 2.编写构造方法,调用父类的构造方法
public U18Exception() {
}
// 提供错误信息
public U18Exception(String message) {
super(message);
}
}
// 网站注册,年龄小于18,有问题
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入姓名");
String name = sc.next();
System.out.println("请输入年龄");
int age = sc.nextInt();
//register(name,age);//报错。方法抛出的异常,要处理
// 调用的地方,应该要处理这个异常
try {
register(name, age);
} catch (U18Exception e) {
System.out.println("处理了年龄小于18的异常, 您的年龄太小,请回去拿父母的身份证");
}
}
// 注册的方法
public static void register(String name, int age) throws U18Exception {
// 判断年龄
if (age < 18) {
// 非法年龄,不让注册,创建一个异常, 【自己搞出来的异常,跑出去,让别人来处理】
throw new U18Exception("您的年龄不合法");
} else {
System.out.println("恭喜您,请尽情浏览...");
}
}
11、当我们在工作中遇到异常时候的处理方式:
运行时异常: 使用if条件判断,过滤特殊情况
编译时异常:
1.alt + 回车 -> Add exception to method signature (throws处理异常,不负责任的)
2.alt + 回车 -> Surround with try/catch (try...catch处理异常,负责任的)
例如:
public static void main(String[] args) {
// 运行时异常
int a = 10;
int b = 0;
if (b != 0) {
int c = a / b;
System.out.println(c);
} else {
System.out.println("提示用户除数为0啦");
}
// 编译时异常
try {
FileInputStream fis = new FileInputStream("abv");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
2、进程与线程
1、并发与并行
并行: 两个或以上的事件在同一时刻发生(同时发生)
并发: 两个或以上的事件在一个时间段内发生(交替执行)
2、线程与进程
(1)进程:正在运行的程序
如我们电脑上会安装很多的应用程序比如Notepad++,微信等等,这些程序安装好后都是在电脑硬盘上面
(2)线程:线程是进程的执行单元(线程是用来执行程序的代码的)
a、Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例。(通过Thread类来创建线程)
b、主线程:执行main方法的那个线程就是主线程
3、多线程同时执行
(1)原理:CPU同一时刻只能处理一个线程.CPU在多个线程间快速切换,造成多个程序同时执行的"假象"
(2)优点:
1.让多件事情同时处理,提高效率。
2.提高资源的利用率(百度云案例解释)
(3)注意:
多线程一定能提高程序的运行速率吗
不一定。(1)因为线程越多,CPU耗费在切换线程的时间会变长,降低速度;
(2)因为线程越多,每个线程得到执行的机会将会减少,降低速度。
4、创建线程
通过Thread类来创建线程,步骤:
1.定义类继承Thread
2.重写run方法
3.创建子类对象
4.调用start方法启动线程
Ps:我们为什么要继承Thread类来创建进程,而不直接用Thread类来创建其对应的线程对象?
见代码解释
public static void main(String[] args) {
System.out.println("来啦!");
Thread t = new Thread();
t.start();
// t.start()会调用系统资源创建出一个新的线程,这个新的线程会执行t.run();
// Thread类中的run方法不是我们想要执行的代码
MyThread mt = new MyThread();
// mt.run();//不调用start(),直接调用run,就不会有新的线程创建并运行
mt.start();
// mt.start();会调用系统资源创建出一个新的线程,【新的线程会执行mt.run();】
// 找到MyThread类的run方法,这个run方法是我们重写的方法,就是我们要执行的代码
}
总结:谁.start()新线程就调用谁的run()方法。而t.start()的run方法不是我们想要执行的代码。而mt.start()才是。
5、Thread类中的方法
(1)获取线程名称的方式:
String getName() 返回此线程的名称
(2)修改线程名称的方式:
普通方法:
void setName(String name) 修改线程的名称
构造方法:
Thread(String name)
获取名字:
public class NamedThread extends Thread {
@Override
public void run() {
System.out.println("run: " + getName());//在线程内部直接调用getName方法
for (int i = 0; i < 10; i++) {
System.out.println("run: " + i);
}
}
}
public static void main(String[] args) {
NamedThread nt = new NamedThread();
System.out.println("main: " + nt.getName());//在main方法内部通过对象的getName方法获取线程的名字
nt.start();
}
修改名字
public class NamedThread extends Thread {
public NamedThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println("run: " + getName());
for (int i = 0; i < 10; i++) {
System.out.println("run: " + i);
}
}
}
public static void main(String[] args) {
NamedThread nt = new NamedThread("下载");
// 修改线程的名称
// nt.setName("听音乐");
System.out.println("main: " + nt.getName());
nt.start();
}
(3)Thread类中的静态方法currentThread()
static Thread currentThread() 返回对当前正在执行的线程对象的引用。(获取当前线程)
public static void main(String[] args) {
System.out.println("开始");
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
System.out.println(thread.toString()); // Thread[main,5,main] //(thread类修改了toString()方法)
// main:线程的名称
// 5: 线程的优先级
// main: 线程组的名称
}
(4)Thread类中的静态方法sleep(long millis)
static void sleep(long millis) 让执行这行代码的线程睡眠指定的时间
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("run: " + i);
try {
Thread.sleep(1000);//出现编译异常。但是因为父类Thread没有抛异常,所以子类MyThread就不能抛异常。此时只能处理异常(try..catch语句)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("开始");
System.out.println(1);
Thread.sleep(3000); // 3s//出现异常,抛出
System.out.println(2);
MyThread mt = new MyThread();
mt.start();
}
(5)线程的礼让方法
static void yield() 对调度程序的一个暗示,当前线程不太想执行了,让CPU尽量切换到其他线程
6、创建线程的另一种方式:实现Runnable接口创建线程
1、Runaable接口
Runnable
接口应由任何类实现,其实例将由线程执行。 该类必须定义一个无参数的方法,称为run
2、为什么要实现接口创建线程
(1)相比于继承Thread的方式创建线程,它只能单继承;而实现Runnable接口的方式创建线程,还能继承其他类,更加灵活
(2)实现Runnable接口方式创建线程,将任务(run方法的重写)和 线程的启动分开,降低了代码的耦合性
3、实现接口创建线程的步骤
1.定义类实现Runnable接口
2.重写run方法
3.创建实现类对象
4.创建Thread的对象,将Runnable实现类对象作为参数(thread的另一个含参构造方法)
Thread(Runnable target) 分配一个新的 Thread对象。
5.调用start启动线程
实现Runnable接口
// 1.定义类实现Runnable接口
public class MyRunnable implements Runnable {
// 2.重写run方法
@Override
public void run() {
// 写在新线程上要执行的代码(任务)
for (int i = 0; i < 20; i++) {
System.out.println("run: " + i);
}
}
}
public static void main(String[] args) {
// 3.创建实现类对象
MyRunnable mr = new MyRunnable();
// 4.创建Thread的对象,将Runnable实现类对象作为参数
Thread t = new Thread(mr);
// 5.调用start启动线程
t.start(); // t.start();调用系统资源创建出新的线程,新的线程执行参数的run方法
for (int i = 0; i < 20; i++) {
System.out.println("main: " + i);
}
}
4、有了Runnable,则可以使用匿名内部类方式创建线程
public static void main(String[] args) {
// 参数是接口: 1.传入接口的实现类对象 2.传入匿名内部类
// Thread(Runnable target)
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是匿名内部类方式新建的线程");
}
});
t1.start();
// 简写
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是简化的方式新建的线程");
}
}).start();//直接调用了start方法
}
行参数的run方法
for (int i = 0; i < 20; i++) {
System.out.println("main: " + i);
}
}
#### 4、有了Runnable,则可以使用匿名内部类方式创建线程
```java
public static void main(String[] args) {
// 参数是接口: 1.传入接口的实现类对象 2.传入匿名内部类
// Thread(Runnable target)
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是匿名内部类方式新建的线程");
}
});
t1.start();
// 简写
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是简化的方式新建的线程");
}
}).start();//直接调用了start方法
}