一、异常
1.定义
异常:异常就是代表程序出现的问题
体系结构:
最上层的是 Throwable 类,下面有两个子类:
① Error:代表系统级别的问题(属于严重问题,比如:内存溢出)。
系统一旦出现问题,Sun 公司就会把这些错误封装成 Error 对象。
Error 是给 Sun 公司自己用的,不是给程序员用的。(如硬件问题:开发人员无法处理)。
② Exception:异常,代表程序可能出现的问题。、
我们通常会用 Exception 以及他的子类来封装程序出现的问题。
异常分为两类:
① 运行时异常:RuntimeException及其子类,编译阶段不会出现异常提醒。
运行时出现的异常(如:数组索引越界异常)
细节:
编译阶段不会报错,运行时代码出错而导致程序出现的问题,一般是由参数传递错误带来的问题。
② 编译时异常:直接继承于 Exception,编译阶段就会出现出现异常提醒(如:日期解析异常)。
手动处理后,代码则不会报错:
细节:
① 编译阶段:java不会运行代码,只会检查语法是否错误,或者做一些优化。
② 编译阶段需要进行处理, 作用在于提醒程序员检查本地信息(语法是否正确,文件是否存在)。
2.作用
① 异常用来查看 bug 的关键参考信息
② 异常可以作为方法内部的一种特殊返回值,以便通知调用者底层的执行情况。
需求:创建一个学生对象,年龄只能是20-30岁,否则报错
在过去,我们只能通过打印到控制台的方式, 但调用者并不知晓,程序仍旧正常运行。
我们可以通过将异常作为返回值进行返回,这样调用者就会拿到一个异常,知晓底层出现的问题。
调用者在拿到异常之后,会有两种选择:
① 自己单独处理
② 打印在控制台上(默认)
3.异常的处理方式
(1)JVM 默认的处理方式:
把异常的名称,异常原因以及异常出现的位置等信息输出在控制台。
程序立即停止执行,下面的代码则不会再执行了。
(2)自己处理(捕获异常)
目的:当代码出现异常时,可以让程序继续往下执行,而不是停止虚拟机。
细节:
在 try 中运行错误代码后,程序就会创建一个对应的异常对象(new ArithmeticException);
再拿着这个对象,和 catch 的小括号进行比对,看括号中的异常变量 e 是否可以接收这个对象:
如果能被接收,就表示异常被捕获,继续执行 catch 中的代码,然后往下执行。
如果不能接收,异常交给 JVM 进行处理。
Question1:如果 try 中没有遇到问题,怎么执行?
如果 try 中没有遇到问题,会把 try 中的代码全部执行完毕,不会执行 catch 中的代码。
结论:只有当出现了异常,才会执行 catch 中的代码。
Question2: 如果 try 中可能会遇到多个问题,会怎么执行?
如果 try 中出现多个问题,在遇到第一个问题后,就会直接跳转到对应的 catch 中进行捕获。
捕获成功后,执行 catch 中的代码。但 try 中剩下的代码不会在看了,直接跳过。
如果没有对应的 catch 与之匹配,则交给 JVM 进行处理。
Tips1:一般情况下,当可能出现多种异常时,我们会写多个异常与之对应,使其一定能成功捕获。
Tips2: JDK7后,我们可以在一个 catch 中捕获多个异常,中间用 | 隔开。
表示如果出现了A异常或B异常,采用同一种处理方案。
注意:
如果要捕获多个异常,这些异常中如果存在继承关系的话,那么父类一定要写在最下面,否则报错。
因为 Exception 是异常体系中的顶级父类,任何异常都属于它,都可以被它接收(多态)。
所以 多个异常出现时,父类只能写在最下方。
如果写在上方,则一定能够被捕获,下方的异常则不会执行。
在上面,我们捕获异常之后,都是通过直接书写打印语句进行处理的。
Throwable 定义了 3 个成员方法,可以直接 返回 / 输出 错误信息。
细节:
① getMessage 和 toString 方法,返回的都是 String 类型的错误提示信息,需要书写打印语句。
② printStackTrace 方法的返回值类型是 void,会直接在控制台进行输出错误信息。
而且错误信息要比前两个方法更为全面,所以最为常用。
③ printStackTrace 方法底层是利用 System.err.println 进行输出,将异常的错误信息以红色字体输出在控制台上(因为多线程的关系,输出顺序和代码的顺序可能不一致)。
④ printStackTrace 方法仅仅是打印异常的错误信息,并不会停止程序运行。
(3)抛出异常
在一个方法中,如果出现了异常。
方法就没有继续运行下去的意义了,此时可以采取抛出处理。
让该方法结束运行,并告诉调用者出现了问题。
抛出异常分为两种:throws 和 throw
① throws:写在方法定义处,表示声明一个异常。告诉调用者,使用本方法可能会有那些异常。
作用:声明可能出现的异常,向上抛出,让调用者知晓,方便快速进行处理。
(调用者可以立马知晓会出现什么异常,进行捕获,可以避免不匹配的异常处理)
注意:
编译时异常:必须要声明
运行时异常:可以不声明
② throw:写在方法内,用于结束方法。
手动抛出异常对象,交给调用者,调用者针对异常进行处理。
public class CatchDemo4 {
public static void main(String[] args) {
int[] arr = {};
int max = 0;
try {
max = getMax(arr);
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
}
System.out.println(max);
}
//求数组的最大值
public static int getMax(int[] arr) {
if (arr == null) {
//手动抛出异常
throw new NullPointerException();
}
if (arr.length == 0) {
//手动抛出异常
throw new ArrayIndexOutOfBoundsException();
}
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
}
注:手动抛出异常后,就结束了方法,方法中下面的的代码就不会再执行了。
4.自定义异常
(1)引言
Question:为什么需要自定义异常呢?
假设现在要录入一个学生信息,姓名长度需要在 3~10 之间,年龄需要在 18~40 之间。
那么在 set 方法进行赋值时,数据不正确的话,该抛出何种异常呢?
我们发现,找不到一个合适的异常来描述这两个问题信息。
所以只能被迫采用运行时异常的父类 RuntimeException。
但这样的缺点是:无法单独处理这两个异常的信息,不能把姓名和年龄的处理方案分开。
所以,我们需要自定义异常类来描述这两个异常。
(2)步骤:
① 定义异常类(类名:异常名+Exception)
② 写继承关系(运行时异常:继承 RunTimeException;编译时异常:继承 Exception)
③ 写空参构造
④ 写带参构造
意义:为了让控制台的报错信息更加的见名知意。
通过自定义异常,可以针对不同的异常,设置不用的处理方案,且这些方案可以独立存在。
细节:
① 选择继承关系时,遵循:
运行时异常 RunTimeException :表示由于参数错误而导致的问题
编译时异常 Exception :提醒程序员检查本地信息
② 如果继承的是编译时异常 Exception,那么在方法中抛出该异常时,必须同时用 throws 声明可能出现的异常,否则报错。
二、File文件
1.定义
File 对象就表示一个路径,可以是文件的路径,也可以是文件夹的路径
这个路径可以是存在的,也允许是不存在的。
路径分为相对路径和绝对路径两种:
绝对路径:带盘符的;
相对路径: 不带盘符的,默认在当前项目下找。
2.方法
(1)获取 File 对象
public class FileObject {
public static void main(String[] args) {
//1.根据字符串表示的路径,变成File对象
String str = "C:\\Users\\24285\\Desktop\\a.txt";
File f1 = new File(str);
System.out.println(f1);// C:\Users\24285\Desktop\a.txt
//2.根据父路径字符串和子路径字符串创建File对象
// 父级路径:C:\Users\24285\Desktop
// 子级路径:a.txt
String parent = "C:\\Users\\24285\\Desktop";
String child = "a.txt";
File f2 = new File(parent, child);
System.out.println(f2);// C:\Users\24285\Desktop\a.txt
//3.将一个File表示的路径和一个字符串表示的路径进行拼接,创建File对象
File parent2 = new File("C:\\Users\\24285\\Desktop");
String child2 = "a.txt";
File f3 = new File(parent2, child2);
System.out.println(f3);// C:\Users\24285\Desktop\a.txt
}
}
细节:
① 路径只有变成 File 对象后,才能调用 File 类的一系列方法(如修改、删除等),否则 Java 只认为是一个普通的字符串。
② 根据父路径字符串和子路径字符串创建 File 对象时:
最好直接使用:File f2 = new File(parent, child);
而不是手动拼接:File f2 = new File(parent + "\\" + child);
因为在不同的操作系统下,路径的分隔符不一样,windows系统中用 \\,Linux中用 / 。
此时如果代码部署到另一个系统上运行,就会发生错误。
直接使用 new File(parent, child) 的话,在底层会先获取 os ,根据不同的 os 选择不同的分隔符。
(2)常见成员方法
① 判断
细节:
存在是前提条件,只有存在,才能判断是不是文件或者文件夹。
如果文件或者文件夹根本不存在,那么即使名字的确是文件名或者文件夹名,也返回 false。
② 获取
I. length 方法
细节:
① length 方法只能获取文件的大小,单位是字节。
② length 方法不能获取文件夹的大小,如果想要获取文件夹的大小,需要计算这个文件夹中所有文件的大小,再累加计算总和。
II. getAbsolutePath 方法
III. getPath 方法
public class FileGet {
public static void main(String[] args) {
File f1 = new File("D:\\text\\aaa.txt");//绝对路径
File f2 = new File("File\\a.txt");//相对路径
//3.getPath 返回定义文件对象时使用的路径
String path1 = f1.getPath();
System.out.println(path1);//D:\text\aaa.txt
String path2 = f2.getPath();
System.out.println(path2);//File\a.txt
}
}
简单来说:就是构造方法中的参数是什么,返回的就是什么
Ⅳ. getName 方法
public class FileGet {
public static void main(String[] args) {
File f1 = new File("D:\\text\\aaa.txt");
File f2 = new File("D:\\text");
//4.getName 获取文件的名字(带后缀名)
String name1 = f1.getName();
System.out.println(name1);//aaa.txt
String name2 = f2.getName();
System.out.println(name2);//text
}
}
细节:
① 如果是文件,就会返回 文件名.后缀名(aaa:文件名 .txt:后缀名、扩展名)
② 如果是文件夹,返回的就是文件夹的名字
Ⅴ. lastModified 方法
public class FileGet {
public static void main(String[] args) {
File f1 = new File("D:\\text\\aaa.txt");
//5.lastModified 返回文件的最后修改时间(时间毫秒值)
long time = f1.lastModified();
System.out.println(time);//1718450498844
//将毫秒值转换成字符串表示的时间
ZonedDateTime date = Instant.ofEpochMilli(time).atZone(ZoneId.of("Asia/Shanghai"));
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EE a");
String str = dtf.format(date);
System.out.println(str);//2024-06-15 19:21:38 周六 下午
}
}
③ 创建和删除
I. createNewFile 方法
public class FileCreate {
public static void main(String[] args) throws IOException {
//1.createNewFile 创建一个新的空的文件
File f1 = new File("D:\\text\\bbb.txt");
boolean b1 = f1.createNewFile();
System.out.println(b1);//true
try {
File f2 = new File("D:\\text\\ccc\\aaa.txt");
boolean b2 = f2.createNewFile();
System.out.println(b2);
} catch (IOException e) {
e.printStackTrace();
}
File f3 = new File("D:\\text\\ddd");
boolean b3 = f3.createNewFile();
System.out.println(b3);//true
}
}
运行结果:
细节:
① 如果当前路径表示的文件是不存在的,则创建成功,方法返回 true;
如果当前路径表示的文件是存在的,则创建失败,方法返回 false。
② 如果父级路径是不存在的,那么方法会产生 IOException 异常。
③ createNewFile 方法创建的一定是文件,如果路径中不含后缀名,则创建一个没有后缀的文件。
II. mkdir 方法
public class MakeDir {
public static void main(String[] args) {
//2.mkdir make directory 创建文件夹(目录)
File f1 = new File("D:\\text\\ccc");
boolean b1 = f1.mkdir();
System.out.println(b1);//true
File f2 = new File("D:\\text\\ddd");
boolean b2 = f2.mkdir();
System.out.println(b2);//false
File f3 = new File("D:\\text\\aaa\\bbb");
boolean b3 = f3.mkdir();
System.out.println(b3);//false
}
}
运行结果:
细节:
① windows 当中路径是唯一的,如果当前路径已经存在,则创建失败,返回 false。
虽然 ddd 文件夹不存在,但 ddd 文件存在。路径唯一,所以创建失败。
② mkdir 方法只能创建单级文件夹,无法创建多级文件夹。
III. mkdirs 方法
public class MakeDirs {
public static void main(String[] args) {
//3.mkdirs 创建多级文件夹
File f1 = new File("D:\\text\\aaa\\bbb");
boolean b1 = f1.mkdirs();
System.out.println(b1);//true
//3.mkdirs 创建多级文件夹
File f2 = new File("D:\\text\\bbb");
boolean b2 = f2.mkdirs();
System.out.println(b2);//true
}
}
运行结果:
细节:
mkdirs 既可以创建多级文件夹,也可以创建单级文件夹。
Question:既然 mkdirs 都可以,那为什么还需要 mkdir 呢?
在 mkdirs 的底层,也会先使用 mkdir 方法。
如果不行,才会执行自己的代码逻辑。
结论:在以后只要创建文件夹,都用 mkdirs 就行了
Ⅳ. delete方法
public class FileDelete {
public static void main(String[] args) {
//4.delete 删除文件和空文件夹
File f1 = new File("D:\\text\\aaa.txt");
boolean b1 = f1.delete();
System.out.println(b1);//true
File f2 = new File("D:\\text\\aaa");
boolean b2 = f2.delete();
System.out.println(b2);//false
File f3 = new File("D:\\text\\bbb");
boolean b3 = f3.delete();
System.out.println(b3);//true
}
}
运行结果:
细节:
delete 方法只能删除文件和空文件夹,且直接删除,不走回收站。
如果删除的是非空文件夹,则删除失败,返回 false 。
Question:如何删除一个有内容的文件夹(多级文件夹)呢?--> 递归
public class MultilevelFileDelete {
public static void main(String[] args) {
File file = new File("D:\\text");
delete(file);
}
public static void delete(File src) {
File[] files = src.listFiles();
//1.先删除文件夹中的所有内容
for (File f : files) {
//是文件 --> 删除
//不是 --> 递归进入
if (f.isFile()) {
f.delete();
} else {
delete(f);
}
}
//2.再删除文件夹本身
src.delete();
}
}
④ 获取并遍历
I. listFiles 方法 (重点掌握)
public class ListFile {
public static void main(String[] args) {
File f = new File("D:\\text");
//listFiles 获取当前该路径下所有内容(文件和文件夹)
File[] files = f.listFiles();
for (File file : files) {
//file依次表示text文件夹中的每一个文件或者文件夹
System.out.println(file);
}
}
}
运行结果:
细节:
① 当调用者 File 表示的路径不存在时,返回 null。
② 当调用者 File 表示的路径是文件时,返回 null。
③ 当调用者 File 表示的路径是一个空文件夹时,返回一个长度为 0 的数组。
④ 当调用者 File 表示的路径是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回。
⑤ 当调用者 File 表示的路径是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File 数组中返回,包含隐藏文件。
⑥ 当调用者File表示的路径是需要权限才能访问的文件夹时,返回null。
II. listRoots 和 list 方法
public class FileDemo1 {
public static void main(String[] args) {
//1.listRoots 获取系统中所有的盘符
File[] arr = File.listRoots();
System.out.println(Arrays.toString(arr));
//2.list() 获取当前该路径下所有内容(仅仅能获取名字)
File f1 = new File("D:\\text");
String[] arr2 = f1.list();
for (String s : arr2) {
System.out.println(s);
}
}
}
运行结果:
细节:
① 文件系统根就是盘符
② list 方法也可以获取当前该路径下所有内容,但仅仅能获取名字
Ⅲ. 文件名过滤器
需求:获取 D:\\text 文件夹里面所有的 txt 文件
1) list(FilenameFilter filter)
public class FileDemo2 {
public static void main(String[] args) {
File f = new File("D:\\text");
String[] arr = f.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
File src = new File(dir, name);
return src.isFile() && name.endsWith(".txt");
}
});
System.out.println(Arrays.toString(arr));//[bbb.txt]
}
}
细节:
① accept 方法的形参,依次表示 text 文件夹里面每一个文件或者文件夹的路径
参数一:父级路径
参数二:子级路径
返回值:如果返回值为true,就表示当前路径保留
如果返回值为false,就表示当前路径舍弃不要
② FilenameFilter 是一个函数式接口,可以使用 Lambda 表达式。
③ 用 list 方法实现的文件过滤器,获取的内容也仅仅只有名字。
2)listFiles(FileFilter filter)
public class FileDemo3 {
public static void main(String[] args) {
File f = new File("D:\\text");
//调用listFiles(FileFilter filter)
File[] arr = f.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isFile() && pathname.getName().endsWith(".txt");
}
});
System.out.println(Arrays.toString(arr));//[D:\text\bbb.txt]
}
}
3)listFiles(FilenameFilter filter)
public class FileDemo4 {
public static void main(String[] args) {
File f = new File("D:\\text");
//调用listFiles(FilenameFilter filter)
File[] arr = f.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
File src = new File(dir, name);
return src.isFile() && name.endsWith(".txt");
}
});
System.out.println(Arrays.toString(arr));//[D:\text\bbb.txt]
}
}
细节:
accept 中的形参:
dir:表示父级路径
name:表示子级路径
dir + name 就是 2)中 accept 的形参 pathname