异常&多线程
第一章 异常
1、异常概述
异常: 不正常的情况
异常 :指的是程序在执行过程中,出现的不正常的情况,最终会导致JVM的非正常停止。
异常发生的原因有很多,通常包含以下几大类:
- 用户输入了非法数据。
- 要打开的文件不存在。
- 网络通信时连接中断,或者JVM内存溢出。
这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。
**异常处理:**为了防止代码在运行期可能因为发生异常而导致的程序终止,我们需要将可能会出现问题的代码进行异常捕获并处理,使程序还可以继续运行下去。
在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象的过程。
Java处理异常的方式是中断处理(停止java虚拟机JVM)。
比如说我们常见的:空指针异常,索引越界异常等,都会导致JVM终止
2、异常的体系和分类
在Java中所有的问题都可以使用一个类来表示,这个类叫做Throwable。
异常的体系(重点)
-- java.lang.Throwable类:是所有异常和错误的父类
Throwable 类是 Java 语言中所有错误或异常的超类。
1.异常:小问题,就想平时生病感冒,发烧了,是可以治愈的
在java中,异常我们是可以解决的,解决了异常之后,程序可以继续往后执行
-- java.lang.Exception extends Throwable类:编译期异常,在写代码的时候,程序报的异常
-- java.lang.RuntimeException extends Exception:运行期(时)异常,在运行代码的时候,程序报的异常.比较轻微的异常情况,而且通常情况,这种异常可以通过if语句来避免出现。 如:数组越界异常
2.错误:严重问题,就想得了癌症,艾滋,是无法治愈的
在java中,遇到了错误,必须的修改代码,不让错误出现
-- java.lang.Error extends Throwable类 :错误
package com.itheima.demo01Exception;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 异常和错误的区别
*/
public class Demo01ExceptionAndError {
public static void main(String[] args) {
//-- java.lang.Exception extends Throwable类:编译期异常,在写代码的时候,程序报的异常
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse("2021-06-10");
System.out.println(date);
} catch (ParseException e) {
e.printStackTrace();
}
try {
//-- java.lang.RuntimeException extends Exception:运行期(时)异常,在运行代码的时候,程序报的异常
int[] arr = {10,20,30};
System.out.println(arr[3]); //ArrayIndexOutOfBoundsException:3
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("后续代码100行!");
}
}
3、异常的产生过程解析(面试)
package com.itheima.demo01Exception;
/*
异常的产生过程解析(面试)
分析一下异常是如何产生的,发生异常之后JVM是如何处理异常的
*/
public class Demo02Exception {
public static void main(String[] args) {
int[] arr = {10,20,30};
int e = getElement(arr, 3);
System.out.println(e);
}
/*
定义一个方法,方法的参数传递一个数组和数组的索引
在方法中,获取数组指定索引处的元素返回
*/
public static int getElement(int[] arr,int index){
int ele = arr[index];
return ele;
}
}
4、throw关键字(重点)
作用:
让我们在[方法中]抛出指定的异常对象
没有使用throw关键字,程序执行的过程中出现了异常,JVM会创建异常相关的对象,并抛出异常对象
格式:
修饰符 返回值类型 方法名(参数列表){
throw new xxxException("异常的错误信息");
...
throw new yyyException("异常的错误信息");
}
注意:
1.throw关键字必须写在[方法中]
2.throw关键字后边创建的对象,必须是Exception或者RuntimeException或者他们的子类
不能是和异常无关的对象(Person,Student==>错误)
3.throw关键字抛出的是运行时(期)异常,我们无需处理异常,默认交给JVM处理(中断)
throw关键字抛出的是编译期异常,我们就必须的自己处理这个异常
package com.itheima.demo01Exception;
/*
异常的产生过程解析(面试)
分析一下异常是如何产生的,发生异常之后JVM是如何处理异常的
*/
public class Demo03Exception {
public static void main(String[] args) {
int[] arr = {10, 20, 30};
//int[] arr = null;
int e = getElement(arr, 3);
System.out.println(e);
}
/*
定义一个方法,方法的参数传递一个数组和数组的索引
在方法中,获取数组指定索引处的元素返回
在工作中,一般都会对方法传递来的参数,进行一些合法性的判断
参数合法:使用参数参与计算
参数不合法:使用抛出异常的方式,告之方法的调用者,传递的参数有问题
*/
public static int getElement(int[] arr, int index) {
/*
对参数arr进行一个合法性的判断,判断arr是否为null
数组的值是null,抛出空指针异常,告之方法的调用者“您传递的数组arr的值是null”
*/
if (arr == null) {
throw new NullPointerException("您传递的数组arr的值是null");
}
/*
对参数index进行一个合法性的判断,判断index是否超出了数组索引的使用范围
index超出了范围,抛出数组的索引越界异常,告之方法的调用者
“您传递的数组索引index超出了索引的使用范围”
*/
if (index < 0 || index > arr.length - 1) {
throw new IndexOutOfBoundsException("您传递的数组索引" + index + "超出了索引的使用范围");
}
int ele = arr[index];
return ele;
}
}
5、throws关键字(重点)
异常处理的第一种方式
作用:
throw关键字抛出的是编译期异常,我们就必须的处理这个异常
可以使用throws关键字把异常对象抛出给方法的调用者处理,最终可以抛出给JVM处理(中断)
格式:
修饰符 返回值类型 方法名(参数列表) throws xxxException,...yyyException{
throw new xxxException("异常的错误信息");
...
throw new yyyException("异常的错误信息");
}
注意:
1.在方法中抛出什么异常对象,就在方法上使用throws关键字声明抛出什么异常对象
在方法中抛出多个异常对象,就需要在方法上声明多个异常对象
在方法中抛出的多个异常对象,有子父类关系,在方法上声明父类异常对象即可
2.调用了一个声明抛出异常对象的方法,必须的对这个异常对象进行处理
可以使用throws关键字继续声明抛出异常对象给方法的调用者处理,最终会抛出给JVM处理
可以使用try...catch关键字自己处理异常
弊端:
最终会把异常对象抛出给JVM,JVM会中断我们当前正在执行的程序
那么异常之后代码代码就不会执行了
package com.itheima.demo01Exception;
/*
异常的产生过程解析(面试)
分析一下异常是如何产生的,发生异常之后JVM是如何处理异常的
*/
public class Demo04Exception {
public static void main(String[] args) throws Exception {
int[] arr = {10, 20, 30};
//int[] arr = null;
int e = getElement(arr, 3);
System.out.println(e);
}
/*
定义一个方法,方法的参数传递一个数组和数组的索引
在方法中,获取数组指定索引处的元素返回
在工作中,一般都会对方法传递来的参数,进行一些合法性的判断
参数合法:使用参数参与计算
参数不合法:使用抛出异常的方式,告之方法的调用者,传递的参数有问题
*/
public static int getElement(int[] arr, int index) throws Exception {
/*
对参数arr进行一个合法性的判断,判断arr是否为null
数组的值是null,抛出空指针异常,告之方法的调用者“您传递的数组arr的值是null”
*/
if (arr == null) {
throw new Exception("您传递的数组arr的值是null");
}
/*
对参数index进行一个合法性的判断,判断index是否超出了数组索引的使用范围
index超出了范围,抛出数组的索引越界异常,告之方法的调用者
“您传递的数组索引index超出了索引的使用范围”
*/
if (index < 0 || index > arr.length - 1) {
throw new Exception("您传递的数组索引" + index + "超出了索引的使用范围");
}
int ele = arr[index];
return ele;
}
}
6、throws抛出子父类异常的处理(重点)
在继承关系下,子类不能抛出比父类更多或更大的异常。
1.在方法中抛出什么异常对象,就在方法上使用throws关键字声明抛出什么异常对象
在方法中抛出多个异常对象,就需要在方法上声明多个异常对象 在方法中抛出的多个异常对象,有子父类关系,在方法上声明父类异常对象即可
package com.itheima.demo01Exception;
import java.io.FileNotFoundException;
import java.io.IOException;
/*
1.在方法中抛出什么异常对象,就在方法上使用throws关键字声明抛出什么异常对象
在方法中抛出多个异常对象,就需要在方法上声明多个异常对象
在方法中抛出的多个异常对象,有子父类关系,在方法上声明父类异常对象即可
public static void main(String[] args) throws FileNotFoundException ,IOException
public static void main(String[] args) throws IOException
public static void main(String[] args) throws Exception
*/
public class Demo05Throws {
public static void main(String[] args) throws Exception {
readFile(null);
}
public static void readFile(String path) throws Exception{
/*
判断path是否为null,是null抛出IOException(读写异常)
告之方法的调用者“您传递的文件的路径是null”
*/
if(path==null){
throw new IOException("您传递的文件的路径是null");
}
/*
判断path是否为d:\\a.txt文件,不是,抛出FileNotFoundException(文件找不到异常)
告之方法的调用者“您传递的文件路径不是d:\\a.txt”
*/
if(!"d:\\a.txt".equals(path)){
throw new FileNotFoundException("您传递的文件路径不是d:\\a.txt");
}
//文件路径没有问题,读取文件
System.out.println("读取到了d:\\a.txt文件,文件中的内容是abc");
}
}
7、try…catch关键字(重点)
Ctrl + alt + t 选择try catch
异常处理的第二种方式
作用
1.throw关键字抛出的是编译期异常,我们就必须得处理这个异常
2.调用了一个声明抛出编译期异常的方法,我们就必须得处理这个异常 就可以使用try catch关键字自己手动处理异常
格式:
try{
//可能产生异常的代码(也可以写没有异常的代码)
}catch(定义一个异常相关的变量){//用来接收try中产生的异常对象
//异常的处理逻辑:可以随意编写
}
...
catch(定义一个异常相关的变量){//用来接收try中产生的异常对象
//异常的处理逻辑:可以随意编写
}
好处:
使用try catch处理异常,可以继续执行try catch后边的代码
注意:
1.try中产生了异常对象,那么就会执行catch中异常的处理逻辑,执行完毕会继续执行try catch之后的代码
2.try中没有产生异常对象,那么就不会执行catch中异常的处理逻辑,执行完try中的代码,会继续执行try catch之后的代码
package com.itheima.demo01Exception;
import java.io.FileNotFoundException;
import java.io.IOException;
/*
异常处理的第二种方式
作用
1.throw关键字抛出的是编译期异常,我们就必须得处理这个异常
2.调用了一个声明抛出编译期异常的方法,我们就必须得处理这个异常 就可以使用try catch关键字自己手动处理异常
*/
public class Demo06TtyCatch {
public static void main(String[] args) {
try {
//可能产生异常的代码,把异常代码抛出给catch中定义的变量
// readFile( null);
// readFile("c:\\a.txt");
readFile("d:\\a.txt");
} catch (Exception e) {
e.printStackTrace();
System.out.println("哈哈");
}
System.out.println("执行后续100行代码");
}
public static void readFile(String path) throws FileNotFoundException,IOException {
/*
判断path是否为null,是null抛出IOException(读写异常)
告之方法的调用者“您传递的文件的路径是null”
*/
if(path==null){
throw new IOException("您传递的文件的路径是null");
}
/*
判断path是否为d:\\a.txt文件,不是,抛出FileNotFoundException(文件找不到异常)
告之方法的调用者“您传递的文件路径不是d:\\a.txt”
*/
if(!"d:\\a.txt".equals(path)){
throw new FileNotFoundException("您传递的文件路径不是d:\\a.txt");
}
//文件路径没有问题,读取文件
System.out.println("读取到了d:\\a.txt文件,文件中的内容是abc");
}
}
8、finally关键字(重点)
finally关键字里边定义的代码,无论程序是否有异常都会执行
Finally必须和try一起使用
package com.itheima.demo01Exception;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
finally关键字里边定义的代码,无论程序是否有异常都会执行
Finally必须和try一起使用
try{
a
}catch(异常类型 变量名){
b
}finally{
c
}
try{
}finally{
}
try{
}catch(异常类型 变量名){
}catch(异常类型 变量名){
}finally{
}
*/
public class Demo07Finally {
public static void main(String[] args) throws ParseException {
try {
System.out.println("[1]可能会产生异常的代码");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse("2021-06-10");
System.out.println(date);
} catch (ParseException e) {
System.out.println("[2]异常的处理逻辑");
e.printStackTrace();
} finally {
System.out.println("[3]程序无论是否产生异常,都会执行的代码");
}
System.out.println("后续100行代码");
}
}
9、异常处理的注意事项(了解)
package demo02Exception;
public class Demo01Exception {
public static void main(String[] args) /*2、throws ArrayIndexOutOfBoundsException*/{
/*try {
int[] arr = {10,20,30};
System.out.println(arr[3]); //1、ArrayIndexOutOfBoundsException: 3
} catch (Exception e) {
e.printStackTrace();
}*/
int[] arr = {10,20,30};
System.out.println(arr[1]);
System.out.println("后续代码");
}
}
一句话:子类方法不能抛出比父类更大更多地方式。
package demo02Exception;
import java.io.IOException;
public class Demo02Fu {
public void show01() throws IOException {
}
}
package demo02Exception;
import java.io.IOException;
public class Demo03Zi extends Demo02Fu {
@Override
public void show01() throws IOException {
}
}
10、自定义异常(使用)
3.1 自定义异常概念
概念:
java给我们提供异常类,不够我们使用,就需要自己定义一些异常相关的类
注意:
1.自定义异常的类名,一般都是以Exception结尾,说明这个类就是一个异常相关的类(见名知意)
2.自定义异常类
a.可以继承Exception:自定义异常就是一个编译期异常
使用:如果在方法中抛出了编译期异常,那么我们就必须处理这个异常,有两种处理方式
1).使用throws关键字在方法上声明抛出这个异常对象,让方法的调用者处理,最终抛出给JVM(中断)
2).使用try...catch自己捕获处理这个异常对象
b.也可以继承RuntimeException:自定义异常就是一个运行期异常
使用:如果在方法中抛出了运行期异常,我们无需处理,默认交给JVM处理(中断)
package com.itheima.demo03MyException;
//自定义一个异常类RegisterException,继承Exception,就是一个编译期异常
public class RegisterException extends Exception {
public RegisterException() {
}
public RegisterException(String message) {
super(message);
}
}
package com.itheima.demo03MyException;
import java.sql.SQLOutput;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;
public class Demo01Test {
public static void main(String[] args) throws RegisterException {
//1、定义一个集合,存储用户已经注册过的用户名
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "jack", "rose", "tom", "jerry");
//2、使用Scanner获取用户本次输入的用户名
Scanner sc = new Scanner(System.in);
System.out.println("请输入您要注册的用户名:");
String name = sc.nextLine();
checkName(list, name);
}
/*
3、定义一个方法,在方法中判断用户名是否已经被注册
*/
private static void checkName(ArrayList<String> list, String name) throws RegisterException {
for (String regName : list) {
if(name.equals(regName)){
throw new RegisterException("您输入的用户名" + name + "已经被注册了");
}
}
list.add(name);
System.out.println(list);
System.out.println("恭喜您,注册成功!");
}
}
第二章 多线程
我们在之前,学习的程序在没有跳转语句的前提下,都是由上至下依次执行,那现在想要设计一个程序,边打游戏边听歌,怎么设计?
要解决上述问题,咱们得使用多进程或者多线程来解决
1、并发并行
2、进程的概念
进程:进入到内存中的程序叫进程。
3、线程的概念
线程:是进程中的一个执行单元。负责当前进程中程序的执行,一个进程中至少有一个线程(单线程程序)。一个进程中是可以有多个线程的,这个应用程序也可以称这为多线程程序。
4、主线程
执行主方法的线程就叫主线程。
package com.itheima.demo05mainThread;
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println(name+"==>"+i);
}
}
}
package com.itheima.demo05mainThread;
public class Demo01MainThread {
public static void main(String[] args) {
Person p1 = new Person("tom");
p1.run();
Person p2 = new Person("jerry");
p2.run();
}
}
5、多线程程序创建的第一种方式_继承Thread类(重点)
package com.itheima.demo06Thread;
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run==>"+i);
}
}
}
package com.itheima.demo06Thread;
/*
多线程程序创建的第一种方式:继承Thread类(重点)
java.lang.Thread类:是一个描述线程的类
实现步骤:
1、创建一个类 继承Thread类
2、在Thread的子类中,重写Thread类的run方法,设置线程任务(开启新的线程要干什么 清理垃圾 查杀病毒)
3、创建Thread类的子类对象
4、调用start方法,开启一个新的线程,执行run方法
------------------------------------------------------------------
void start() 使该线程开始执行;java虚拟机调用这个线程的 run方法。
1、调用start方法之后,结果是两个线程并发地运行:
当前线程(main线程:执行main方法)和其他的线程(新的线程:执行run法)。
两个线程一起抢夺cpu的执行权,谁抢到了谁执行,所以就有了随机性的打印结果(抢占式调度)
2、多次启动一个线程是不合法的。特别是当线程已经结束执行后,不能再重新启动。
一个线程对象,只能调用一次start方法。
*/
public class Demo01Thread {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//调用start方法,开启一个新的线程,执行run方法
myThread.start();
//主线程在开启新的线程后,会继续执行主方法中的代码
for (int i = 0; i < 20; i++) {
System.out.println("main==>"+i);
}
}
}
6、获取线程的名称
package com.itheima.demo07GetThreadName;
/*
Thread类中的方法:获取线程名称
1、可以使用Thread类中的方法getName
String getName() 返回该线程的名称。
2、可以先获取到当前正在执行的线程Thread对象
static THread currentThread() 返回当前正在执行的线程对象的引用。
*/
public class MyThread extends Thread {
@Override
public void run() {
// String name = getName();
// System.out.println(name);
//Thread t = Thread.currentThread();
//String name = t.getName();
//System.out.println(name);
//链式编程
System.out.println(Thread.currentThread().getName());
}
}
package com.itheima.demo07GetThreadName;
public class Demo01GetThreadName {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
//获取主线程的名称
System.out.println(Thread.currentThread().getName());
}
}
7、线程休眠
package com.itheima.demo08sleep;
/*
Thread类中静态方法
static void sleep(long mills) 1秒=1000毫秒
在指定的毫秒内让当前正在执行的线程休眠(暂停执行)
休眠结束,程序继续执行
*/
public class Demo01Sleep {
public static void main(String[] args) {
System.out.println("让当前线程(main线程)睡眠5秒钟");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("5秒钟已经到了,程序继续执行!");
}
}
8、多线程程序创建的第二种方式:实现Runnable接口(重点)
package com.itheima.demo09Runnable;
public class RunnableImpl implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
}
}
package com.itheima.demo09Runnable;
/*
实现步骤:
1、创建Runnable接口的实现类,实现Runnable接口
2、在实现类中重写Runnable接口的run方法,设置线程任务
3、创建Runnable接口的实现类对象
4、创建Thread类对象,构造方法的参数传递Runnable接口的实现类对象
5、调用start方法开启一个新的线程,执行run方法
*/
public class Demo01Runnable {
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
Thread t = new Thread(run);
t.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}
9、创建多线程程序的2种方式区别
package com.itheima.demo10ThreadAndRunnable;
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(getName());
}
}
package com.itheima.demo10ThreadAndRunnable;
public class MyRunnable1 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"MyRunnable1");
}
}
package com.itheima.demo10ThreadAndRunnable;
public class MyRunnable2 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"MyRunnable2");
}
}
package com.itheima.demo10ThreadAndRunnable;
public class Demo01Test {
public static void main(String[] args) {
/* MyThread mt = new MyThread();
mt.start();*/
MyRunnable1 mr1 = new MyRunnable1();
MyRunnable2 mr2 = new MyRunnable2();
Thread t = new Thread(mr2);
t.start();
//main线程名
System.out.println(Thread.currentThread().getName());
}
}
10、匿名内部类的方式实现多线程程序(重点)
package com.itheima.demo11Thread;
/*
匿名内部类:简化代码
把子类继承父类、重写父类的方法、创建子类对象 合成一步完成
把实现接口、重写接口的方法、创建实现类对象 合成一步完成
语法: new 爹
new Thread(){
public void run(){
....
}
}.tart();
new Thread(new Runnable(){
public void run(){
....
}
}).start();
*/
public class Demo01Thread {
public static void main(String[] args) {
//父类:Thread类
new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程执行了");
}
}.start();
//接口:Runnable接口
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程执行了");
}
}).start();
}
}