Skip to content

一、复习

1.1 包装类

8 种基本数据类型对应 8 个包装类:

txt
8种基本数据类型: byte, short, int ,long, float, double, char, boolean
8个包装类:     Byte,  Short, Integer,Long, Float, Double, Character, Boolean

基本数据类型 与 对应的包装类 之间可以 自动实现装箱与拆箱。

  • 装箱:基本数据类型 -> 包装类对象
  • 拆箱:包装类对象 -> 基本数据类型

包装类对象的特点:

  • 包装类对象不可变
  • 部分包装类对象可以被共享,对于整数类型缓存范围:[-128, 127],对于 Character 缓存范围[0, 127],Boolean 只有 2 个值:true,false
  • 包装类对象用运算符计算时只有:两个包装类对象做==和!=计算时不拆箱,比较地址值。其余计算情况都拆箱为基本数据再计算。

包装类的一些方法:(以 Integer 和 Double 为例)

  • 把字符串的转为基本数据类型值:

    • Integer.parseInt(字符串) 得到 int
    • Double.parseDouble(字符串) 得到 double
  • 比较两个值的大小

    • Integer.compare(整数1,整数2):得到 int 值。当第 1 个整数>,<,=第 2 个整数,依次返回正整数、负整数、0。
    • Double.compare(小数1,小数2):得到 int 值。当第 1 个小数>,<,=第 2 个小数,依次返回正整数、负整数、0。
  • 获取某中类型最大值、最小值

    • 最大值:Integer.MAX_VALUE
    • 最小值:Integer.MIN_VALUE
  • Character 还有 2 个比较好用的方法:

    • Character.toUpperCase(字符):转大写
    • Character.toLowerCase(字符):转小写
java
package com.atguigu.review;

public class TestWrapper {
    public static void main(String[] args) {
        /*
        'a'的编码值 97
        'A'的编码值是65
        相差32
         */
//        char c1 = 'a';
        char c1 = 'A';
        c1 = (char) (c1 - 32);
        System.out.println("c1 = " + c1);

//        char c2 = 'a';
        char c2 = 'A';
        c2 = Character.toUpperCase(c2);
        System.out.println("c2 = " + c2);
    }
}

1.2 比较器

例如:学生类的对象要比较大小(找最大值、排序等)

1、java.lang.Comparable 接口(自然比较接口):

  • 抽象方法:int compareTo(Object obj)
  • 谁来实现它:学生类实现它即可

2、java.util.Comparator 接口(定制比较器接口:

  • 抽象方法:int compare(Object o1, Object o2)
  • 谁来实现它:单独写一个类实现它。这个单独的类可以是有名字的类,也可以是匿名内部类

1.3 File 类

File 类的对象用于代表一个文件或文件夹。这个文件或文件夹可能在硬盘中存在,也可能不存在。

通过 File 类对象可以:

  • 获取文件或文件夹的一些属性:名称、路径值、大小、是否存在、是否是文件、是否是文件夹、最后修改时间.......

  • 创建或删除、重命名:

    • 创建文件:createNewFile。创建文件夹:mkdir、mkdirs
    • 删除文件或空文件夹:delete
    • 重命名:renameTo
  • 获取上级目录:getParent、getParentFile

  • 文件夹获取下级:list 或 listFiles

1.4 文件 IO 流

  • FileInputStream:文件字节输入流。最小可以读取 1 个字节。
    • 可以读取任意类型的文件
  • FileOutputStream:文件字节输出流。
    • 可以将任意类型的数据写到文件中
  • FileReader:文件字符输入流。 要从一个纯文件文件中读取文件内容,最小可以读取 1 个字符。
  • FileWriter:文件字符输出流。把字符串等纯文本数据写到纯文件中。

如何选择?

(1)如果能够明确这接下写的这段代码,一定是针对纯文本文件进行读和写,那么优先考虑:FileReader、FileWriter

(2)如果接下来你要写的这段代码是针对所有类型的文件的通用代码,那么一定是选择 FileInputStream、 FileOutputStream

关于方法的总结:

  • FileInputStream、FileReader: 调用 read 方法读数据

    • FileInputStream:
      • read()读 1 个字节, 返回的就是你读取的这个字节的值
      • read(byte[] b)可以读取多个字节到 byte[]数组中:返回的是这次读取的字节的数量
      • 如果已经到达流末尾,返回的都是 -1
    • FileReader:
      • read()读 1 个字符, 返回的就是你读取的这个字符的编码值
      • read(char[] b)可以读取多个字符到 char[]数组中:返回的是这次读取的字符的数量
      • 如果已经到达流末尾,返回的都是 -1
  • FileOutputStream、FileWriter:调用 write 方法进行写/输出数据

    • FileOutputStream:

      • write(int):输出 1 个字节
      • write(byte[] b, int offset, int len):输出多个字节。从 byte[]数组中输出多个字节。从[offset]开始,输出 len 个字节。
      • flush():刷新,从 JVM 中刷到文件中
    • FileWriter

      • write(int):输出 1 个字符

      • write(char[] b, int offset, int len):输出多个字符。从 char[]数组中输出多个字符。从[offset]开始,输出 len 个字符。

      • flush():刷新,从 JVM 中刷到文件中

  • 所有 IO 流都有close()方法。

二、IO 流(续)

2.1 读写纯文本文件过程中编码问题

image-20250714091154857

image-20250714091236391

image-20250714092406746

image-20250714092455737

java
package com.atguigu.encoding;

import org.junit.jupiter.api.Test;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;

public class TestReadWriteFile {
    @Test
    public void test1() throws IOException {
        //文件的编码是GBK,现在的程序编码是UTF-8。
        FileReader fr = new FileReader("d:\\gbk.txt");
        char[] arr = new char[5];
        while(true){
            int len = fr.read(arr);
            if(len==-1){
                break;
            }
            System.out.println(new String(arr,0,len));
        }
        fr.close();
        /*
        结果乱码。
        原因:
        此时文件编码是GBK,解密密码本的编码是UTF-8
         */
    }

    @Test
    public void test2()throws IOException{
        FileReader fr = new FileReader("d:\\gbk.txt", Charset.forName("GBK"));
        char[] arr = new char[5];
        while(true){
            int len = fr.read(arr);
            if(len==-1){
                break;
            }
            System.out.println(new String(arr,0,len));
        }
        fr.close();
    }

    @Test
    public void test3() throws IOException {
        FileReader fr = new FileReader("d:\\utf8.txt");
        char[] arr = new char[5];
        while(true){
            int len = fr.read(arr);
            if(len==-1){
                break;
            }
            System.out.println(new String(arr,0,len));
        }
        fr.close();
        //结果:尚硅谷
    }

    @Test
    public void test4()throws IOException{
        FileReader fr = new FileReader("d:\\utf8.txt", Charset.forName("GBK"));
        char[] arr = new char[5];
        while(true){
            int len = fr.read(arr);
            if(len==-1){
                break;
            }
            System.out.println(new String(arr,0,len));
        }
        fr.close();
        //乱码
        //此时文件编码是UTF-8,解密密码本的编码是GBK
    }

    @Test
    public void test5()throws IOException{
        //往gbk.txt文件中追加   “AI应用Java方向”
        FileWriter fw = new FileWriter("d:\\gbk.txt",true);
        fw.write("AI应用Java方向");
        fw.close();
        //文件乱码了
        //原因:文件是GBK编码,  程序是UTF-8编码,  加密过程选择了UTF-8方式
    }

    @Test
    public void test6()throws IOException{
        //往gbk.txt文件中追加   “AI应用Java方向”
        FileWriter fw = new FileWriter("d:\\gbk.txt",Charset.forName("GBK"),true);
        fw.write("AI应用Java方向");
        fw.close();
    }
}

总结:在创建 FileReader 和 FileWriter 时,指定与 【文件】 相同的编码。如果文件编码与程序运行环境的编码一致,不用指定编码。

2.2 纯文本按行读和写

单纯的使用 FileReader、FileWriter 是无法实现按行读和写的。可以借助其他 IO 流来完成:

  • 方案一:借助 BufferedReader 和 BufferedWriter(我演示一下,但是不用掌握)
  • 方案二:借助 Scanner(读)和 PrintStream(写)(更简洁)
java
package com.atguigu.line;

import org.junit.jupiter.api.Test;

import java.io.*;
import java.util.Scanner;

public class TestLine {
    @Test
    public void test1()throws IOException {
        FileReader fr = new FileReader("D:\\manylines.txt");
        BufferedReader br = new BufferedReader(fr);
        while (true){
            String line = br.readLine();//按行读
            if(line == null){
                break;
            }
            System.out.println(line);
        }
        br.close();
        fr.close();
        //上面这个给大家看一下即可
    }
    @Test
    public void test2()throws IOException{
        FileReader fr = new FileReader("D:\\manylines.txt");
        Scanner input = new Scanner(fr);
        while(input.hasNextLine()){
            String line = input.nextLine();
            System.out.println(line);
        }
        input.close();
        fr.close();
    }

    @Test
    public void test3()throws IOException{
        //从键盘输入一些话,按行保存到  messages.txt 文件中(留言)
        Scanner input = new Scanner(System.in);

        FileWriter fw = new FileWriter("d:\\messages.txt");

        while (true) {
            System.out.print("请输入你要说的话(stop结束):");
            String line = input.nextLine();
            if("stop".equals(line)){
                break;
            }

            fw.write(line+"\n");//可以,但是不够标准和完美
        }

        fw.close();
        input.close();
    }

    @Test
    public void test4()throws IOException{
        //从键盘输入一些话,按行保存到  messages.txt 文件中(留言)
        Scanner input = new Scanner(System.in);

        //System.out.println();//往控制台打印
        PrintStream ps = new PrintStream("d:\\messages.txt");

        while (true) {
            System.out.print("请输入你要说的话(stop结束):");
            String line = input.nextLine();
            if("stop".equals(line)){
                break;
            }

            ps.println(line);
        }

        ps.close();
        input.close();
    }
}

2.3 直接读写 Java 对象

涉及的 IO 流:

  • ObjectOutputStream:对象输出流
  • ObjectInputStream:对象输入流

新名称:

  • 输出对象的过程被称为序列化。(序列化的意思,就是将对象变为一串字节序列)
  • 读取对象的过程被称为反序列化。

案例 1:对象的读和写

要求:

Java 中规定凡是 Java 对象需要序列化,就必须:

  • 实现 java.io.Serializable 接口:哪个类的对象要序列化,哪个类就实现 Serializable 接口。如果是学生类的对象要序列化,那么就让学生类实现 Serializable 接口
java
package com.atguigu.object;

import com.atguigu.bean.Student;
import org.junit.jupiter.api.Test;

import java.io.*;

public class TestObjectReadWrite {
    @Test
    public void test1()throws IOException {
        //创建一个学生对象
        Student s = new Student("张三", 96);

        //把这个对象直接存到 d:\\student.txt
        FileWriter fw = new FileWriter("d:\\student.txt");
        fw.write(s.toString());
        fw.close();
        /*
        看起来写出去了,但是并不符合我们的要求,这里是把Student变为String写出去的。
        对将来读数据是有影响的
         */
    }

    @Test
    public void test2()throws IOException{
        //读 d:\\student.txt
        FileReader fr = new FileReader("d:\\student.txt");
        char[] arr = new char[5];
        while(true){
            int len = fr.read(arr);
            if(len==-1){
                break;
            }
            //怎么把arr中的字符  变回 Student对象 ?????(有问题)
        }
    }

    @Test
    public void test3()throws IOException {
        //创建一个学生对象
        Student s = new Student("张三", 96);

        //把这个对象直接存到 d:\student.txt
        FileOutputStream fos = new FileOutputStream("d:\\student.txt");
       // fos.write();//()里面需要byte[],那么s对象怎么转为byte[]????(有问题)
    }

    @Test
    public void test4()throws IOException{
        //创建一个学生对象
        Student s = new Student("张三", 96);

        FileOutputStream fos = new FileOutputStream("d:\\student.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        oos.writeObject(s);
        //java.io.NotSerializableException: com.atguigu.bean.Student
        //如果Student类没有实现 Serializable接口,就会报不支持序列化的异常NotSerializableException

        oos.close();
        fos.close();
        /*
        因为对象序列化后的结果并不是给“普通人”看,而是给另一段Java程序看的,所以不建议序列化的结果存到 .txt的文件中。
        因为.txt的文件,“普通人”看到.txt的文件,默认为是可以用记事本,notepad++能正常打开的文件,会误导别人。
        建议用非常规的后缀名/文件扩展名来表示。
        常规:.txt, .jpg, .mp3, .mp4, .doc,......
        非常规的:.aaa,  .stu ...

         */
    }

    @Test
    public void test5()throws IOException{
        //创建一个学生对象
        Student s = new Student("张三", 96);

        FileOutputStream fos = new FileOutputStream("d:\\student.aaa");
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        oos.writeObject(s);

        oos.close();
        fos.close();
    }


    @Test
    public void test6() throws IOException, ClassNotFoundException {
        //用ObjectOutputStream输出的数据,要用ObjectInputStream读取。
        FileInputStream fis = new FileInputStream("d:\\student.aaa");
        ObjectInputStream ois = new ObjectInputStream(fis);

        Object object = ois.readObject();
        System.out.println("object = " + object);

        ois.close();
        fis.close();
    }

}

案例 2:序列化版本 ID 的问题

场景描述:

  • 当我已经把一个学生对象写到 student.aaa 文件中
  • 现在我要修改 Student 类
  • 修改类之后,重新读取 student.aaa 文件中 学生对象时,报错了
txt
java.io.InvalidClassException:  类无效异常
com.atguigu.bean.Student;       Student类无效
local class incompatible:       本地的字节码文件(Student.class)不兼容的、矛盾的
stream classdesc serialVersionUID = -481976849751154153,   流(ObjectInputStream)中类的描述序列化版本ID(类版本)是 -481976849751154153
local class serialVersionUID = -5258063863544938063       本地字节码文件中 序列化版本ID(类版本)是-5258063863544938063
简单说:当初输出学生对象时Student类的版本,与现在读取学生对象的Student类版本 不是一个版本

如果解决已经发生的问题?

  • 要给 Student 类增加一个序列化版本 ID 且它的值 必须与 stream 流中的一样。
  • private static final long serialVersionUID = stream 中 serialVersionUID;

如果避免此类问题发生?

  • 如果是新的类,它在实现 Serializable 接口时,最好就加上序列化版本 ID,此时它的值随意。
  • private static final long serialVersionUID = 任意值;

示例代码 1:旧类问题

java
package com.atguigu.bean;

import lombok.*;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor
public class Student implements Serializable {
    @NonNull
    private String name;
    @NonNull
    private int score;
    private int age;

    private static final long serialVersionUID = -481976849751154153L;
}
java
package com.atguigu.object;

import org.junit.jupiter.api.Test;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class TestReadObjectAfterUpdateStudent {
    //在我修改了Student类后,读取旧文件 student.aaa文件
    @Test
    public void test1() throws IOException, ClassNotFoundException {
        //用ObjectOutputStream输出的数据,要用ObjectInputStream读取。
        FileInputStream fis = new FileInputStream("d:\\student.aaa");
        ObjectInputStream ois = new ObjectInputStream(fis);

        Object object = ois.readObject();
        System.out.println("object = " + object);

        ois.close();
        fis.close();
        /*
        java.io.InvalidClassException:  类无效异常
        com.atguigu.bean.Student;       Student类无效
        local class incompatible:       本地的字节码文件(Student.class)不兼容的、矛盾的
        stream classdesc serialVersionUID = -481976849751154153,   流(ObjectInputStream)中类的描述序列化版本ID(类版本)是 -481976849751154153
        local class serialVersionUID = -5258063863544938063       本地字节码文件中 序列化版本ID(类版本)是-5258063863544938063
        简单说:当初输出学生对象时Student类的版本,与现在读取学生对象的Student类版本 不是一个版本.

        如何解决?给Student类加   private static final long serialVersionUID = -481976849751154153L;
        此时 serialVersionUID的值必须与stream中描述的一样。
         */
    }


}

示例代码 2:新类

java
package com.atguigu.bean;

import lombok.*;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor
public class Teacher implements Serializable {
    @NonNull
    private String name;
    @NonNull
    private double salary;
    private int age;

    private static final long serialVersionUID = 1L;//此时因为Teacher对象还未参与序列化,它的值随意写
}
java
package com.atguigu.object;

import com.atguigu.bean.Teacher;
import org.junit.jupiter.api.Test;

import java.io.*;

public class TestTeacherReadAndWrite {
    @Test
    public void test2()throws IOException {
        //Teacher是新的类
        Teacher t = new Teacher("柴",1500000);
        FileOutputStream fos = new FileOutputStream("d:\\teacher.aaa");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(t);
        oos.close();
        fos.close();
    }

    @Test
    public void test3() throws IOException, ClassNotFoundException {
        //用ObjectOutputStream输出的数据,要用ObjectInputStream读取。
        FileInputStream fis = new FileInputStream("d:\\teacher.aaa");
        ObjectInputStream ois = new ObjectInputStream(fis);

        Object object = ois.readObject();
        System.out.println("object = " + object);

        ois.close();
        fis.close();
    }
}

案例 3:哪些成员变量的值不会序列化

  • 凡是 static 的成员变量,不会参与序列化。因为序列化是针对“对象”,而静态的是属于类的,所有对象的共享的。不需要单独某个对象单独存储。(序列化到文件就是永久存储,专业名称称为持久化)。
  • 如果非静态的成员变量中,也有不想参与序列化的,需要加 transient(瞬时的,临时的)。
  • 如果说程序员想要单独定制序列化和反序列化规则,那么必须在实现 Serializable 接口,除了加 serialVersionUID 之外,还要加 2 个方法:
    • private void writeObject(java.io.ObjectOutputStream out) throws IOException{}
    • private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
java
package com.atguigu.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data   //不会给静态变量生成get/set
@AllArgsConstructor
@NoArgsConstructor
public class Employee implements Serializable {
    private static String company; //静态变量
    private String name;
    private transient double salary;//transient瞬时的,临时的
    private static final long serialVersionUID = 1L;


    public static String getCompany() {
        return company;
    }

    public static void setCompany(String company) {
        Employee.company = company;
    }


    //除非对Employee对象的序列化和反序列化有必要的特殊需求,才会加下面的方法,否则不要加
  /*  private void writeObject(java.io.ObjectOutputStream out) throws IOException{
        //定制序列化过程
        out.writeUTF(name);
        out.writeDouble(salary);
        out.writeUTF(company);
    }
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
        //定制反序列化规则
        //读的顺序、类型必须与写的顺序类型一致
        name = in.readUTF();
        salary = in.readDouble();
        company = in.readUTF();
    }*/

}
java
package com.atguigu.object;

import com.atguigu.bean.Employee;
import org.junit.jupiter.api.Test;

import java.io.*;

public class TestEmployeeReadAndWrite {
    @Test
    public void test1()throws IOException {
        Employee.setCompany("尚硅谷");
        Employee e = new Employee("李刚",20000);

        FileOutputStream fos = new FileOutputStream("D:\\employee.aaa");
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        oos.writeObject(e);

        oos.close();
        fos.close();
    }

    @Test
    public void test2()throws IOException,ClassNotFoundException{
        FileInputStream fis = new FileInputStream("d:\\employee.aaa");
        ObjectInputStream ois = new ObjectInputStream(fis);

        Object object = ois.readObject();
        System.out.println("object = " + object);

        System.out.println("公司名:" + Employee.getCompany());

        ois.close();
        fis.close();
    }
}

2.4 IO 流小结

看图片或 xmind 文档。

三、认识异常和异常处理

3.1 认识异常

Java 程序运行过程中出现的问题都是异常,但是不包括语法错误和逻辑错误。

如果 Java 程序运行时遇到异常,没有合理的处理,那么程序会挂掉。这样的话,程序的健壮性就很差,所以为了提高程序的健壮性,必须引入异常处理机制。

Java 中使用对象来表示各种异常和错误。而对象是由 Java 类 new 出来的,所以下面要认识各种各样的异常和错误的类型:

1、Throwable 类

Throwable 类是 Java 语言中所有错误或异常的超类。

  • 只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。
  • 类似地,只有此类或其子类之一才可以是 catch(捕获) 子句中的参数类型。

异常对象可能是 JVM 抛出,或者程序员用 throw 语句抛出。一旦异常对象被抛出,没有被 catch 的话,程序就会挂掉。

两个子类的实例,[Error](../../java/lang/Error.html)[Exception](../../java/lang/Exception.html),通常用于指示发生了异常情况。通常,这些实例是在异常情况的上下文中新近创建的,因此包含了相关的信息(比如堆栈跟踪数据)。

image-20250714145110174

2、Error

ErrorThrowable 的子类,用于指示合理的应用程序不应该试图捕获(catch)的严重问题。

例如:VirtualMachineError (虚拟机错误)的子类 StackOverflowError(栈内存溢出)和 OutOfMemoryError(堆内存溢出错误)

遇到这里错误,通常应该停下来:

  • 升级硬件
  • 升级软件架构或修复 BUG(例如无条件递归)
  • JVM 调参数

3、Exception

Exception 类及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。

忠告:

(1)不要把所有的 Exception 都交给 try-catch 处理,否则会造成(1)程序的可读性差(2)程序性能低下。

(2)能通过条件判断、数据校验等操作避免的异常,就不要依赖 try-catch。例如:下标越界(下标应该在[0, 数组的长度-1])、空指针异常(引用数据类型的变量必须赋值正经的对象,不应该是 null 就去调用方法啥)、类型转换异常(向下转型之前用 instanceof 判断一下)等。

(3)实在避免不了的,要用 try-catch 处理。例如:FileNotFoundException 等

4、编译时异常和运行时异常

  • 编译时异常(受检异常,编译器可以检测出来的风险):是指编译器可以提前预警的异常类型。此时这个异常不见得一定会发生。例如:FileNotFoundException 等
  • 运行时异常(非受检异常,即不受编译器检查):是指编译器不会给出预警的异常类型,哪怕这个异常真的会发生,编译器也无视它。所有运行时异常都是 RuntimeException 及其子类。

image-20250714150613587

image-20250714150841454

最低要求:至少能写出 5 种以上的异常类型。建议大家写英文。

3.2 异常的处理

主要围绕 5 个关键字:try,catch,finally,throw,throws

3.2.1 try-catch

语法结构:

java
try{
    可能发生异常的代码1;
    可能发生异常的代码2;
    可能发生异常的代码3;
}catch(异常的类型1  参数名){//参数名通常是e
    异常处理的语句, 或 打印异常信息 ,或日志记录等
}catch(异常的类型2  参数名){//参数名通常是e
    异常处理的语句, 或 打印异常信息 ,或日志记录等
}catch(异常的类型3  参数名){//参数名通常是e
    异常处理的语句, 或 打印异常信息 ,或日志记录等
}
代码4;

1、情况 1:try 中三句代码都没有发生异常

  • 所有 catch 都不执行
  • 代码 4 正常执行

2、情况 2:try 中代码 2 发生异常

  • 首先,try 中代码 3 不执行
  • 其次,根据发生的异常的类型,来确定走哪个 catch。catch 是从上往下判断,如果上面的匹配了,下面的 catch 就不看了
  • 代码 4 正常执行

3、情况 2:try 中代码 2 发生异常,但是所有 catch 都不匹配

  • 首先,try 中代码 3 不执行
  • catch 会判断,但是{}都不执行
  • 当前方法就挂了,代码 4 就不执行了
java
package com.atguigu.exception;

import org.junit.jupiter.api.Test;

import java.util.InputMismatchException;
import java.util.Scanner;

public class TestTryCatch {
    @Test
    public void test1(){
        //需求:从键盘输入2个整数,求它们的商
        Scanner input = new Scanner(System.in);

        //选中可能发生异常的代码,然后按Ctrl + Alt + T,选中try-catch
        int a = 0;
        while (true) {
            try {
                System.out.print("请输入第1个整数:");
                a = input.nextInt();
                //java.util.InputMismatchException  输入不匹配,编译器没有提示,所以是运行时异常
                break;
            } catch (InputMismatchException e) {
//            System.out.println(e);//把异常当普通信息打印
//            System.out.println("输入的不是整数");//自己打印错误信息

//            System.err.println(e);//以错误信息的方式打印e对象
            System.err.println("输入的不是整数");//以错误信息的方式打印e对象
                //  e.printStackTrace();//打印e对象的详细信息,包括堆栈跟踪信息(初学时先用它,将来学了日志框架,再根据不同情况选择不同的等级信息打印)
                input.nextLine();//把现在输入的不匹配的信息全部读取调用,才能重新读取新的数据
            }
        }

        int b = 0;

        while(true) {
            try {
                System.out.print("请输入第2个整数:");
                b = input.nextInt();
                if(b==0){
                    System.out.println("除数不能为0,请重新输入!");
                }else{
                    break;
                }
            } catch (InputMismatchException e) {
                System.err.println("输入的不是整数");
                input.nextLine();//把现在输入的不匹配的信息全部读取调用,才能重新读取新的数据
            }
        }

        double shang = (double)a/b;
        System.out.println("shang = " + shang);

        input.close();
    }
}

3.2.2 finally 关键字

finally 是与 try-catch 结构搭配使用的。用于表示无论 try 中有没有异常,也不管 catch 能不能抓住异常,都要执行的代码块,哪怕 try 或 catch 中有 return 语句也挡不住 finally 执行。通常是资源释放代码,例如:IO 流的关闭、网络连接的断开等代码。

java
package com.atguigu.exception;

import org.junit.jupiter.api.Test;

public class TestFinally {
    @Test
    public void test1(){
        try{
            System.out.println(8/2);//执行,不发生异常
            System.out.println("java");//执行
        }catch (ArithmeticException e){
            System.out.println("发生算术异常");//不执行
        }finally {
            System.out.println("finally");//执行
        }
        System.out.println("atguigu");//执行
    }

    @Test
    public void test2(){
        try{
            System.out.println(8/0);//执行,发生异常
            System.out.println("java");//不执行
        }catch (ArithmeticException e){
            System.out.println("发生算术异常");//执行
        }finally {
            System.out.println("finally");//执行
        }
        System.out.println("atguigu");//执行
    }

    @Test
    public void test3(){
        try{
            System.out.println(8/0);//执行,发生异常 算术异常
            System.out.println("java");//不执行
        }catch (NullPointerException e){//异常类型不匹配
            System.out.println("发生空指针异常异常");//不执行
        }finally {
            System.out.println("finally");//执行
        }
        System.out.println("atguigu");//不执行
    }

    @Test
    public void test4(){
        try{
            System.out.println(8/2);//执行,不发生异常
            System.out.println("java");//执行
            return;//提前结束当前方法
        }catch (ArithmeticException e){
            System.out.println("发生算术异常");//不执行
        }finally {
            System.out.println("finally");//执行
        }
        System.out.println("atguigu");//不执行
    }

    @Test
    public void test5(){
        try{
            System.out.println(8/2);//执行,不发生异常
            System.out.println("java");//执行
            System.exit(0);//退出虚拟机
        }catch (ArithmeticException e){
            System.out.println("发生算术异常");//不执行
        }finally {
            System.out.println("finally");//不执行
        }
        System.out.println("atguigu");//不执行
    }
}

3.2.3 try-catch-with-resource

语法结构:

java
try(需要关闭的资源对象的声明){
    可能发生异常的业务代码
}catch(异常的类型1  参数名){//参数名通常是e
    异常处理的语句, 或 打印异常信息 ,或日志记录等
}catch(异常的类型2  参数名){//参数名通常是e
    异常处理的语句, 或 打印异常信息 ,或日志记录等
}catch(异常的类型3  参数名){//参数名通常是e
    异常处理的语句, 或 打印异常信息 ,或日志记录等
}finally{
    除了try()中资源关闭之外代码。(如果finally中只需要写try()资源的关闭语句,那么可以省略finally。
        因为try()中的资源对象会自动关闭。
}

try()中的资源类必须实现 Closeable 接口 或 AutoCloseable 接口,才能被自动关闭。

try()中的资源类对象默认是 final 的。

java
package com.atguigu.exception;

import com.atguigu.bean.Student;
import org.junit.jupiter.api.Test;

import java.io.*;

public class TestTryCatchWithResource {
    @Test
    public void test1(){
        //把Student对象写到student.aaa文件中
        Student s = new Student("张三",100);

        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        try {
            fos = new FileOutputStream("d:\\尚硅谷\\student.aaa");
            oos = new ObjectOutputStream(fos);

            oos.writeObject(s);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    public void test2(){
        //把Student对象写到student.aaa文件中
        Student s = new Student("张三",100);

        //JDK7之后支持
        try(FileOutputStream fos = new FileOutputStream("d:\\尚硅谷\\student.aaa");
            ObjectOutputStream oos = new ObjectOutputStream(fos);) {

            oos.writeObject(s);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //凡是在try()中声明的资源对象,都不需要手动关闭,可以自动关闭了。
    }

    @Test
    public void test3()throws IOException{
        FileWriter fw = new FileWriter("d:\\1.txt");
        BufferedWriter bw = new BufferedWriter(fw);
        bw.write("hello");
        fw.close();
        bw.close();//报错,流已经关闭了
        /*
        数据的流向:
            内存 -> bw -> fw -> "d:\\1.txt"文件
            这里先关闭fw。
            关闭流有先后顺序问题。遵循先new的后关。
         */
    }
}

3.2.4 throws

throws 是用于声明当前方法中

  • 可能发生某种类型的异常
  • 当前方法并未处理这个可能发生的异常,要由调用者处理,即谁调用谁处理

3.2.5 throw

throw:主动抛出异常对象

在方法体中有 throw 语句,方法签名中一般都有 throws。

方法体中没有 throw 语句,只要当前方法可能发生异常,你有不处理的,也要加 throws。

对比:throw 和 throws

throwthrows
编写的位置不同方法体 { }里【修饰符】 返回值类型 方法名(【形参列表】)throws 异常类型
后面跟的东西不同throw 异常对象;throws 异常的类型们
含义不同主动抛出异常对象,只要这句语句执行了,就代表异常确实发生了throws 只是表示可能发生 xx 类型的异常,需要调用者处理,并不代表这个异常真的发生。
java
package com.atguigu.exception;

import org.junit.jupiter.api.Test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class TestThrows {
    @Test
    public void test1() {
        try {
            copy("d:\\1.txt", "d:\\上谷歌\\3.txt");
            System.out.println("复制成功");
        } catch (IOException e) {
            System.out.println("复制失败");
        }
        //这里想要实现一个效果,复制成功,打印复制成功,否则打印复制失败
    }

    //代表copy方法内部(1)可能发生IOException(2)没有处理IOException异常
    //由调用者test1处理
    public void copy(String srcFile, String destFile) throws IOException {

        try(
            FileInputStream fis = new FileInputStream(srcFile);
            FileOutputStream fos = new FileOutputStream(destFile);
        ) {

            byte[] data = new byte[1024];
            while (true) {
                int len = fis.read(data);
                if (len == -1) {
                    break;
                }
                fos.write(data, 0, len);
            }
        }catch (IOException e){
            throw e;//继续抛出   e是异常对象
        }

    }
}

3.2.6 方法的重载与方法的重写的区别(完整版)

方法的重载(Overload)方法的重写(Override)
位置同一个类或父子类中父子类
方法名相同相同
形参列表(个数、类型、顺序,与形参名无关)不同相同
返回值类型不看总的来说:<=
细说:基本数据类型和 void 必须相同,引用数据类型必须是<=
权限修饰符不看>=,不能是 private
其他修饰符不看static,final 不能重写
throws 异常类型不看总的来说:<=

总结:重写的要求口诀,两同两小一大,三不能

关于 throws 异常类型重写方法时的详细要求:

  • 如果被重写方法的签名中没有 throws 编译时类型的异常,重写时不可以 throws 编译时类型的异常
  • 如果被重写方法的签名中有 throws 编译时类型的异常,重写时可以继续 throws 编译时类型的异常,但是异常的类型必须满足<=的关系
java
package com.atguigu.exception;

import java.io.IOException;

public class Father {
    public void m1(){
        //...
    }

    public void m2()throws IOException {
        //...
    }

    public void m3()throws IOException {
        //...
    }

    public void m4()throws IOException {
        //...
    }

    public Object m5(){
        return new Object();
    }

    public String m6(){
        return "hello";
    }

    void m7(){
        //...
    }
}
java
package com.atguigu.exception;

import java.io.FileNotFoundException;
import java.io.IOException;

public class Son extends Father{
/*    @Override
    public void m1() throws IOException {
        ///..
    }*/
    //如果被重写方法的签名中没有throws 编译时类型的异常,重写时不可以throws编译时类型的异常


    @Override
    public void m2() throws IOException {
        //,...
    }

    //FileNotFoundException < IOException
    //因为FileNotFoundException extends IOException
    @Override
    public void m3() throws FileNotFoundException {
        //...
    }

    //Exception > IOException
/*    @Override
    public void m4() throws Exception {//错误
        ///
    }*/

    @Override
    public String m5() {
        return "hello";
    }

/*    @Override
    public Object m6() {
        return new Object();
    }*/

    @Override
    public void m7() {
       ///...
    }
}
java
package com.atguigu.exception;

import java.io.IOException;

public class TestSon {
    public static void main(String[] args) {
        //多态引用,父类的变量指向子类的对象
        Father f = new Son();
        //编译时看左边,运行时看右边

        //编译时看父类,按照父类的方法没有异常发生,实际执行Son类方法时,也不能发生异常,否则不安全
        f.m1();

        //编译时看父类,按照父类的异常进行try-catch,实际执行Son类方法时,发生的异常必须 <=,否则catch不住
        try {
            f.m2();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            f.m3();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            f.m4();
        } catch (IOException e) {
            e.printStackTrace();
        }

        //编译时看父类,按照父类中方法的返回值类型接收,实际Son类中返回的结果只能更小,否则接不住
        Object obj = f.m5();
        System.out.println("obj = " + obj);

        String s = f.m6();
        System.out.println("s = " + s);

        f.m7();//父类编译能找到这个方法,运行时也得能找到这个方法
    }
}

3.2.7 对象克隆(了解)

java.lang.Object 根父类中有一个方法:

java
protected Object clone()  throws CloneNotSupportedException

父类声明的方法,子类会继承。子类重写这个方法时,必须同时实现 Cloneable 接口,否则会发生 CloneNotSupportedException 异常。

protected:本类(这里是 Object 本类中) + 本包(这里 java.lang 本包)+ 其他包的子类中。

java
package com.atguigu.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Circle implements Cloneable{
    private double radius;

    //权限修饰符可以往大了改
    //返回值类型可以往小了改
    @Override
    public Circle clone() throws CloneNotSupportedException {
        return (Circle) super.clone();//调用Object类的方法
    }
}
java
package com.atguigu.clone;

import com.atguigu.bean.Circle;
import org.junit.jupiter.api.Test;

public class TestClone {
    @Test
    public void test1() throws CloneNotSupportedException {
        Circle c1 = new Circle(2.5);
        Circle c2 = c1.clone();//错误,因为Circle类是Object的子类,得在Circle类中才能直接调用clone方法
        System.out.println(c1);
        System.out.println(c2);
    }
}