Bug是所有程序的”附属品“,而处理异常可以帮助我们解决Bug带来的异常问题,这一章我们会先学习如何处理异常。而处理数据是电脑的主要功能,文件则储存数据的基本单位,第二部分我们就学习处理文件这项重要的工作。在下一章,我会介绍如何进阶学习Java的方法,希望帮助大家不要仅仅只是一个编程语言的使用者,还能对Java内部的运行机制和语法特征有更深入的理解,这样我们学习其他编程语言的时候,才能融会贯通,将掌握的知识迁移到新的技术上。

异常处理

不知道大家运行电脑的时候,有没有碰到过以下类似的窗口:

这就是程序中再正常不过的Bug了,Java程序运行时也会出现错误,每当错误出现的时候,Java程序就会生成一个错误信息并停止,也就是Java扔出了一个异常(exception)。

try…catch…finally

要处理异常,我们需要用到try….catch,首先我们看一段会生成异常的代码:

public class Main {
    public static void main(String[] args) {
        System.out.println(1 / 0);
    }
}

运行后会生成以下异常信息:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at JavaException.main(JavaException.java:3)

要处理异常,我们需要把会生成异常的代码放入try中,然后在catch之后定义好异常的类,并写好如何处理此异常的方式:

public class Main {
    public static void main(String[] args) {
        try {
            System.out.println(1 / 0);
        } catch (ArithmeticException e) {
            System.out.println("An exception occurs.");
        }
    }
}

运行此代码后,我们会看到以下的输出,没有具体错误的信息:

An exception occurs.

要注意我们在catch后面定义了 ArithmeticException 异常,可是如果其他的异常出现怎么办呢,比如下面这样:

public class Main {
    public static void main(String[] args) {
        try { 
            System.out.println(0 / 1);
            int[] array = new int[]{1, 2};
            System.out.println(array[3]);
        } catch (ArithmeticException e) {
            System.out.println("An exception occurs.");
        }
    }
}

我们创建的数组只有两个元素,而我们想要拿到第四元素,运行后会得到与数组相关的异常信息:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
	at JavaException.main(JavaException.java:6)

我们需要多创建一个新的catch来处理新的异常:

public class JavaException {
    public static void main(String[] args) {
        try {
            System.out.println(0/1);
            int[] array = new int[]{1, 2};
            System.out.println(array[3]);
        } catch (ArithmeticException e) {
            System.out.println("An exception occurs.");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("An exception occurs.");
        }
    }
}

可见Java中异常真是多种多样,这些异常之间彼此有关联,我们来看看异常相关的继承树:

从图中看出,Throwable是异常体系的根,其中有两个大类:Error和Exception。Error表示严重的错误,程序对此无能为力,比如内存耗尽,栈溢出等等。而Exception则是运行时的错误,可以被catch并被处理,我们遇到的ArithmeticException和IndexOutOfBoundsException都继承于Exception。所以我们也可以将我们的代码简化成如下:

public class Main {
    public static void main(String[] args) {
        try {
            System.out.println(0/1);
            int[] array = new int[]{1, 2};
            System.out.println(array[3]);
        } catch (Exception e) {
            System.out.println("An exception occurs.");
        }
    }
}

我们再来学习try…catch中另外一个重要的关键词finally,不管是否有异常出现,finally之后的代码都会被执行:

public static void main(String[] args) {
    try {
        System.out.println(0/1);
        int[] array = new int[]{1, 2};
        System.out.println(array[3]);
    } catch (Exception e) {
        System.out.println("An exception occurs.");
    } finally {
        System.out.println("After try...catch block");
    }
}

这是结果:

0
An exception occurs.
After try...catch block

这就是处理异常的方法,将可能会出错的代码放入try,然后用在catch后定义好异常的类型,并把处理异常的代码放入catch中,如果有无论如何都要执行的代码,就放入finally中。

throws, throw

接下来我们来学习另外两个与异常相关的关键词:throws和throw。首先我们创建一个能正常处理异常的方法,然后在main函数中调用其方法:

public class Main {
    public static void main(String[] args) {
        badCode();
    }
    public static void badCode() {
        try {
            System.out.println(0 / 1);
            int[] array = new int[]{1, 2};
            System.out.println(array[3]);
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

如果我们不想要在当前的代码块处理异常,我们可以使用throws来扔出异常,交给上一层调用异常方法的代码来处理:

public class Main {
    public static void main(String[] args) {
        try {
            badCode();
        } catch (Exception e) {
            System.out.println(e);
        }
    }
    public static void badCode() throws ArithmeticException, IndexOutOfBoundsException{
        System.out.println(0 / 1);
        int[] array = new int[]{1, 2};
        System.out.println(array[3]);
    }
}

throws关键字要写在会出现异常的方法后面,并定义好会出现的异常类型,如有多个异常,则用逗号隔开。这样我们就可以将处理异常这种麻烦事交给上层处理啦。

如果我们要创建一个自定义的异常,我们可以使用throw关键字来扔出一个异常:

public class Main {
    public static void main(String[] args) {
        checkAge(17);
    }
    public static void checkAge(int age) {
        if (age < 18) {
            throw new ArithmeticException("Access denied: You must be at least 18 years old.");
        } else {
            System.out.println("Access granted: You are old enough!");
        }
    }

在上面的代码中,我们创建会扔出异常的checkAge方法,如果参数age小于18一个异常就会被抛出,所以main函数中调用checkAge(17)后就会有以下的异常出现:

Exception in thread "main" java.lang.ArithmeticException: Access denied: You must be at least 18 years old.
	at JavaException.checkAge(JavaException.java:8)
	at JavaException.main(JavaException.java:4)

这些便是关于Java异常的主要内容了,我们学习了try…catch….finally来解决异常出现后的处理方式,使用throws将处理异常的责任推给上层,通过throw丢出一个自定义的异常。

文件读写

文件是存储数据的基本单位,而操作文件便是处理数据必备可少的过程。接下来,我们就来学习使用Java处理文件:创建文件、读取文件、更新文件、和删除文件。处理文件需要 File 类,所以在文件中我们必须导入此类:

import java.io.File;

创建文件和写入内容

如果要创建文件,我们可以使用 createNewFile() 这个方法,此方法会返回一个布尔值:true 代表此文件成功创建,false 代表此文件以及存在。要注意此方法要放入 try … catch 中,防止 IOException 异常的出现。

import java.io.File;
import java.io.IOException;

public class FileHandler{
    public static void main(String[] args) {
        File newFile = new File("newFile.txt");
        try {
            newFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

如果要给一个文件写入内容,则需要 FileWriter 这个类,以下的代码便会把 Hello World 写入 newFile.txt 文件中:

try {
    FileWriter writer = new FileWriter("newFile.txt");
    writer.write("Hello World!");
    writer.close();
} catch (IOException e) {
    e.printStackTrace();
}

读取、删除文件

如果要读取文件内部的信息,我们可以使用 Scanner 类来实现:

import java.util.Scanner;
import java.io.FileNotFoundException;
public class CreateFile {
    public static void main(String[] args) {
        try {
            File myFile = new File("newFile.txt");
            Scanner scanner = new Scanner(myFile);
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                System.out.println(line);
            }
            scanner.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

如果要得到关于文件的更多信息,可以通过 File 类的内置方法获取信息:

import java.io.File;
public class FileHandler {
    public static void main(String[] args) {
        File newFile = new File("newFile.txt");
        if(newFile.exists()) {
            System.out.println("File name: " + newFile.getName());
            System.out.println("Absolute path: " + newFile.getAbsolutePath());
            System.out.println("Writeable: " + newFile.canWrite());
            System.out.println("Readable: : " + newFile.canRead());
            System.out.println("File size in bytes: : " + newFile.length());
        } else {
            System.out.println("The file does not exist");
        }
    }
}

如果文件存在,运行后我们会得到以下的信息:

File name: newFile.txt
Absolute path: /Users/haozheng/newFile.txt
Writeable: true
Readable: : true
File size in bytes: : 12

最后我们来学习如何删除文件和文件夹,删除文件夹要注意一点:只有空文件夹才能被删除

import java.io.File;
public class CreateFile {
    public static void main(String[] args) {
        File newFile = new File("newFolder/newFile.txt"); // Delete file
        if (newFile.delete()) {
            System.out.println("Deleted file: " + newFile.getName());
        } else {
            System.out.println("Failed to delete the file.");
        }
        File newFolder = new File("./newFolder"); // Delete folder
        if (newFolder.delete()) {
            System.out.println("Delete folder: " + newFolder.getName());
        } else {
            System.out.println("Failed to delete the folder.");
        }
    }
}

以上便是使用Java操作文件的基本内容,大家可以通过实践练习来加强对异常处理和文件操作的掌握:

实践练习

创建一个专门操作文件的类FileHandler,其中包括三个static方法:

  • createOrDeleteFile(String, boolean) 用于创建和删除文件,第一个参数是要操作的文件名,第二个布尔参数决定操作类型:True就是创建,否则删除。
  • writeToFile(String, String) 将内容写入文件,第一个参数是文件名,第二个参数是具体的字符串内容。
  • readFile(String) 用于读取文件,参数是文件名,返回数据是文件内容。

完成类的设计后,如果要创建一个新文件 myFile.txt,并将 Hello Word 写入文件,最后读取文件后再删除文件,我只需要调用以下的代码即可:

String filename = "myFile.txt";
FileHandler.createOrDeleteFile(filename, true);
FileHandler.writeToFile(filename, "Hello World");
System.out.println(FileHandler.readFile(filename));
FileHandler.createOrDeleteFile(filename, false);