Java进阶之异常&初识线程

一、异常

1.1 异常的概述

异常指的是不正常,指的是程序出现了某些问题。

在Java中Throwable类是所有异常和错误的父类。

  • Exception:异常,是程序中轻微的,可以挽回的问题
  • Error: 错误,是程序中严重的,不可挽回的问题

1.2 异常的继承体系以及分类

在这里插入图片描述

1.3 异常产生的过程

public class Demo02Exception {
    public static void main(String[] args) {
        //创建数组
        int[] arr = new int[2];//[0,0]
        //获取指定索引位置的元素
        int value = getValue(arr, 10);
        System.out.println(value);
    }

    /*
        定义方法,获取数组指定索引位置的元素。
            参数:数组,索引。
            返回值:获取到的元素
     */
    public static int getValue(int[] arr, int index) {
        //获取arr数组中index位置的元素
        int value = arr[index];
        return value;
    }
}

在这里插入图片描述
异常一旦抛出,代码就不会向下执行了。

1.4 throw关键字的使用

作用:用来手动向外抛出异常。

格式:
    throw new 异常类名();

注意:
使用throw向外抛出的一定要是一个异常对象

在创建异常对象时可以在构造方法中传递一个字符串,该字符串表示异常信息。

  public class Demo03Throw {
    public static void main(String[] args) {
        //创建数组
        int[] arr = new int[2];//[0,0]
        //获取指定索引位置的元素
        int value = getValue(arr, 10);
        System.out.println(value);
    }

    /*
        定义方法,获取数组指定索引位置的元素。
            参数:数组,索引。
            返回值:获取到的元素
     */
    public static int getValue(int[] arr, int index) {
        //对index进行判断,如果索引不合法,手动向外抛出索引越界异常。
        if (index < 0 || index >= arr.length) {
            //拼接异常信息
            String msg = "数组索引的范围是0到" + (arr.length - 1) + ",您传递的索引是" + index;
            //手动向外抛出异常
            throw new ArrayIndexOutOfBoundsException(msg);
        }
        //获取arr数组中index位置的元素
        int value = arr[index];
        return value;
    }
} 

1.5 throws关键字的使用

throw:用来手动抛出异常。
throws:用来进行异常声明,声明某个方法有可能会抛出异常

格式:
    修饰符 返回值类型 方法名(参数列表) throws 异常类名 {
        方法体;
        return 返回值;
    }

注意:

  • 1.如果在方法内抛出了编译时异常,那么必须要使用throws进行声明
  • 2.在调用使用throws声明异常的方法时,调用者方法如果不解决这个异常(try…catch),必须也叫使用throws进行声明
  • 3.如果在方法中抛出的是运行时异常,可以不使用throws进行使用。
  • 4.如果方法中有可能抛出多个异常,那么我们需要使用throws进行多个异常的声明.
  • 5.如果在方法中有可能抛出多个异常,那么我们也可以直接使用throws声明这些异常的父类异常.
public class Demo05Throws {
    public static void main(String[] args) throws Exception{
        //method();
        method2();
    }

    public static void method4() throws Exception{
        //定义变量
        int num = 10;
        //判断num是奇数还是偶数,并根据判断抛出不同的异常.
        if (num % 2 == 0) {
            throw new IOException();
        } else {
            throw new SQLException();
        }
    }


    public static void method3() throws IOException, SQLException{
        //定义变量
        int num = 10;
        //判断num是奇数还是偶数,并根据判断抛出不同的异常.
        if (num % 2 == 0) {
            throw new IOException();
        } else {
            throw new SQLException();
        }
    }

    //抛出运行时异常
    public static void method2() {
        throw new RuntimeException();
    }

    //抛出编译时异常
    public static void method() throws Exception{
        //向外抛出编译时异常
        throw new Exception();
    }
}  

1.6 try…catch解决异常

如果想要解决掉异常,而不是将异常向外抛,可以使用try…catch语句(捕获处理)

格式:
    try {
        可能出现异常的代码
    } catch(异常类名 变量名) {
        出现异常后执行的代码
    }

如果try中引发的异常和catch小括号中写的异常类名相同,那么catch可以捕获到该异常。

执行流程:

  • 第一种情况:try中的代码没有任何异常,那么代码会跳过catch继续向下执行
  • 第二种情况:try中的代码有异常并且catch捕获到了这个异常,那么代码会从try直接跳到catch中
  • 第三种情况:try中的代码有异常但是catch没有捕获到这个异常,这个异常会依旧向外抛

两种处理异常的方式

  • 向外抛(甩锅):throw throws
  • 解决异常: try…catch
public class Demo06TryCatch {
    public static void main(String[] args) {
        try {
            System.out.println("1. try...start");
            Object obj = null;
            obj.toString();//NullPointerException
            System.out.println("2. try...end");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("3. catch");
        }
        System.out.println("4. main...end");
    }
}

1.7 finally代码块

在try…catch语句后面可以追加finally代码块,finally代码块的特点是finally代码块的内容无论如何都会执行(除非System.exit)

格式:
    try {
        【A】可能会出现异常的代码
    } catch(要捕获的异常类名 变量名) {
        【B】出现异常后执行的代码
    } finally {
        【C】一定会执行的代码
    }

执行流程:

  • 第一种情况:如果try中的代码没有异常,执行流程为【A】【C】
  • 第二种情况:如果try中的代码有异常,并且catch捕获到了这个异常,执行流程为 【A】【B】【C】
  • 第三种情况:如果try中的代码有异常,但是catch没有捕获到这个异常,执行流程为 【A】【C】向外抛出异常
public class Demo07Finally {
    public static void main(String[] args) {
        try {
            System.out.println("1. try...start");
            Object obj = null;
            obj.toString();//NullPointerException
            System.out.println("2. try...end");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("3. catch");
        } finally {
            System.out.println("4. finally");
        }
        System.out.println("5. main...end");
    }
}

finally代码块的使用场景:

一定要执行的代码放在finally代码块中,比如后期释放资源的操作(IO流中的关闭流,JDBC关闭连接)都是在finally代码块中执行的。

1.8 编译时异常和运行时异常的区别

  • 编译时异常:在编译时期必须要进行处理(try…catch或throws)
  • 运行时异常:在编译时期,可以处理,也可以不处理
public class Demo08Exception {
    public static void main(String[] args) {
        method2();
        //method();
    }

    //抛出运行时异常
    public static void method2() {
        throw new RuntimeException();
    }

    //抛出编译时异常
    public static void method() throws Exception{
        throw new Exception();
    }
}

1.9 继承关系中方法重写时的异常处理

  • 在方法重写时,如果父类方法没有抛出异常,那么子类重写的方法也不能抛,如果子类方法有异常,只能try…catch
  • 在方法重写时,如果父类方法抛出了异常,那么子类方法可以抛,也可以不抛。如果子类方法向外抛,要么抛出和父类方法异常一样的异常,要么抛出父类方法异常的子类异常。

注意:上面所说的规则只适用于编译时异常。

1.10 多异常处理的注意事项

try后面可以追加多个catch,多个catch可以进行多异常的捕获处理

格式:
    try {
        可能出现异常的代码;
    } catch(要捕获的异常类名 变量名) {
        出现异常后执行的代码;
    } catch(要捕获的异常类名 变量名) {
        出现异常后执行的代码;
    } catch(要捕获的异常类名 变量名) {
        出现异常后执行的代码;
    }

执行流程:

  • 第一种情况:如果try中的代码没有异常,所有的catch都不执行
  • 第二种情况:如果try中的代码有异常, 哪个catch先捕获到异常,那么就执行哪个catch后面的语句
  • 第三种情况:如果try中代码有异常,但是所有的catch都没有捕获到,那么这个异常依旧会向外抛

注意:
在进行多catch异常处理时,父类异常不能放在子类异常的前面,因为父类异常的catch会拦截掉所有能捕获到的异常。

public class Demo09TryCatchCatch {
    public static void main(String[] args) {
        try {
            System.out.println("1. try...start");
            Object obj = null;
            obj.toString();//NullPointerException
            System.out.println("2. try...end");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("3. ArrayIndexOutOfBoundsException");
        } catch (NullPointerException e) {
            System.out.println("4. NullPointerException");
        } catch (ClassCastException e) {
            System.out.println("5. ClassCastException");
        } catch (Exception e) {
            System.out.println("6. Exception");
        }

        System.out.println("7. main...end");
    }
}

1.11 Throwable中操作异常信息的方法

在Throwable中有一些方法可以获取异常信息:

  • void printStackTrace():使用标准的错误流将异常信息输出到控制台【非常详细的异常信息】
  • String getMessage():获取异常信息的字符串【简短的异常信息】

上面的这些方法要通过异常对象去调用【要通过捕获到的异常对象调用】

public class Demo10ThrowableMethod {
    public static void main(String[] args) {
        try {
            int[] arr = new int[2];
            System.out.println(arr[10]);//throw new ArrayIndexOutOfBoundsException(..);
        } catch (ArrayIndexOutOfBoundsException e) {//e = new ArrayIndexOutOfBoundsException(..);
            //变量e就表示捕获到的异常对象。我们可以通过e调用方法获取到异常对象中的异常信息。
            //void printStackTrace():使用标准的错误流将异常信息输出到控制台【非常详细的异常信息】
            e.printStackTrace();//java.lang.ArrayIndexOutOfBoundsException: 10at com.itheima.promoteDay07.demo01.Test.main(Test.java:9)
            // String getMessage():获取异常信息的字符串【简短的异常信息】
            String msg = e.getMessage();
            System.out.println(msg);//10
        }
        //System.out.println("main...end");
    }
}

二、初始线程

2.1 线程概述

进程:正在(内存中)运行的程序

线程:程序中的执行单元(执行任务的单位)
在这里插入图片描述

2.2 并发和并行

在这里插入图片描述

2.3 程序中的主线程

每一个程序都至少包含一个线程,我们写的Java程序也一样,当程序启动时,JVM会创建一个main线程,并执行main方法。

  • 在程序中只有一个执行线程main线程,该程序是单线程程序
  • 如果程序中有多个线程,那么这个程序就是一个多线程程序

一个线程同时只能执行一个任务

  • 如果是单线程程序,同时只能做一件事情

  • 如果是多线程程序,同时可以做多件事情(多线程程序可以同时执行多个线程)

2.4 多线程程序的第一种实现方式

在Java中有一个类叫做Thread,这个类表示线程类,我们可以使用这个类完成多线程程序。

多线程的第一种实现方式步骤:

  • 1.定义类继承Thread
  • 2.重写Thread中的run方法,并在run方法中定义线程要执行的任务。
  • 3.创建Thread子类对象。
  • 4.通过Thread子类对象调用start方法,启动线程,线程会执行自己的run方法

Thread中的run方法:

  • void start():让线程执行,线程会执行自己的run方法
/*
    Thread是线程类,当MyThread继承Thread后,MyThread也就变成了线程类。
 */
public class MyThread extends Thread{
    //重写run方法,在run方法中定义线程要执行的任务(输出100次HelloWorld)
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println("新线程在输出HelloWorld:" + i);
        }
    }
}
public class Demo02ThreadTest {
    public static void main(String[] args) {
        System.out.println("main...start");
        //创建Thread子类对象
        //表示创建了一个线程,只不过该线程并没有执行。
        MyThread m = new MyThread();
        //通过Thread子类对象调用start方法,启动线程,线程会执行自己的run方法。
        m.start();
        //输出100次HelloJava
        for (int i = 1; i <= 100; i++) {
            System.out.println("main线程在输出HelloJava:" + i);
        }
    }
}

在这里插入图片描述

2.4.1 Thread中的方法

构造方法:

  • Thread():空参数的构造方法。
  • Thread(String name):一个参数是字符串的构造方法,参数表示线程名字。

其他方法:

  • String getName():获取线程名字。
  • void setName(String name):设置线程名字。
  • static Thread currentThread():获取当前正在执行的线程对象。
  • static void sleep(long millis):线程休眠,参数是休眠的毫秒值。
public class Demo01ThreadMethodTest {
    public static void main(String[] args) {
        //创建MyThread对象
        MyThread m = new MyThread("大黄");
        //m.setName("旺财");
        //调用start方法启动线程
        m.start();

        //获取main线程的名字。
        //先获取当前正在执行的线程对象,然后调用getName获取线程名字
        //static Thread currentThread():获取当前正在执行的线程对象【该方法是通过哪个线程调用的,那么获取到的就是哪个线程对象】
        Thread mainThread = Thread.currentThread();
        System.out.println(mainThread.getName());


        //创建线程并调用start方法启动
        //new MyThread().start();
        //new MyThread().start();
        //new MyThread().start();
    }
}
public class MyThread extends Thread{

    //提供构造方法,用来设置线程名字
    public MyThread(String name) {
        super(name);
    }

    //重写run方法,定义线程要执行的任务
    @Override
    public void run() {
        //setName("狗蛋");
        System.out.println(getName() + "执行了");
    }
}
public class MyThread extends Thread{

    //提供构造方法,用来设置线程名字
    public MyThread(String name) {
        super(name);
    }

    //重写run方法,定义线程要执行的任务
    @Override
    public void run() {
        //setName("狗蛋");
        System.out.println(getName() + "执行了");
    }
}
/*
    Thread的sleep方法
        static void sleep(long millis):线程休眠,参数是休眠的毫秒值。
 */
public class Demo02Sleep {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("开始...");
        //让程序停(休眠)5秒钟(5000毫秒)
        Thread.sleep(5000);
        System.out.println("结束...");
    }
}

2.5 多线程的第二种实现方式

实现步骤:

  • 1.定义类,然后实现Runnable接口
  • 2.重写Runnable接口中的run方法,在run方法中定义线程要执行的任务
  • 3.创建Runnable实现类对象
  • 4.创建Thread线程对象,并将Runnable实现类对象作为参数传递
  • 5.调用线程对象的start方法,启动线程,线程会执行对应的run方法
public class Demo01RunnableTest {
    public static void main(String[] args) {
        //创建Runnable实现类对象【task对象表示线程要执行的任务】
        Task task = new Task();
        //创建Thread线程对象,并将Runnable实现类对象作为参数传递。
        Thread t = new Thread(task);//表示将线程和task任务进行绑定,该线程要执行的任务是task中的run方法。
        //调用线程对象的start方法,启动线程,线程会执行对应的run方法。
        t.start();
        //输出100次HelloJava
        for (int i = 1; i <= 100; i++) {
            System.out.println("main线程在输出HelloJava:" + i);
        }

    }
}
/*
    在java中Thread类才表示线程类。
    Task这个类和Thread没有关系,所以Task并不是线程类。
    我们可以将Task看成线程任务类,因为该类只有一个run方法,在该类中只需要关注线程要执行的任务。
 */
public class Task implements Runnable{
    //定义线程要执行的任务【输出100次HelloWorld】
    @Override
    public void run() {
        for(int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "在输出HelloWorld:" + i);
        }
    }
}

多线程有两种实现方式

  • 1.继承Thread类
  • 2.实现Runnable接口

推荐使用实现Runnable:

  • 1.解决了Java中类与类单继承的局限性
  • 2.降低了耦合性(关联性)
  • 3.Runnable中只有一个run方法,没有getName,sleep,setName…,功能更加纯粹,我们只需要在里面关注线程要执行的任务

2.6 匿名内部类实现多线程

匿名内部类的格式:
    new 父类或接口() {
        //重写的方法
    }
public class Demo01InnerThread {
    public static void main(String[] args) {
        //创建Runnable的实现类对象【匿名内部类】
        Runnable r = new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行了");
            }
        };
        //创建线程对象并启动
        new Thread(r).start();

        //最终版
        //创建线程对象并启动
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行了");
            }
        }).start();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值