# day06—异常处理
一、异常
1.1 认识异常
接下来,我们学习一下异常,学习异常有利于我们处理程序中可能出现的问题。我先带着同学们认识一下,什么是异常?
我们阅读下面的代码,通过这段代码来认识异常。 我们调用一个方法时,经常一部小心就出异常了,然后在控制台打印一些异常信息。其实打印的这些异常信息,就叫做异常。
那肯定有同学就纳闷了,我写代码天天出异常,我知道这是异常啊!我们这里学习异常,其实是为了告诉你异常是怎么产生的?只有你知道异常是如何产生的,才能避免出现异常。以及产生异常之后如何处理。
因为写代码时经常会出现问题,Java的设计者们早就为我们写好了很多个异常类,来描述不同场景下的问题。而有些类是有共性的所以就有了异常的继承体系
先来演示一个运行时异常产生
int[] arr = {11,22,33}; //5是一个不存在的索引,所以此时产生ArrayIndexOutOfBoundsExcpetion System.out.println(arr[5]);
下图是API中对ArrayIndexOutOfBoundsExcpetion类的继承体系,以及告诉我们它在什么情况下产生。
再来演示一个编译时异常
我们在调用SimpleDateFormat对象的parse方法时,要求传递的参数必须和指定的日期格式一致,否则就会出现异常。 Java比较贴心,它为了更加强烈的提醒方法的调用者,设计了编译时异常,它把异常的提醒提前了,你调用方法是否真的有问题,只要可能有问题就给你报出异常提示(红色波浪线)。
编译时异常的目的:意思就是告诉你,你小子注意了!!,这里小心点容易出错,仔细检查一下
有人说,我检查过了,我确认我的代码没问题,为了让它不报错,继续将代码写下去。我们这里有两种解决方案。
- 第一种:使用throws在方法上声明,意思就是告诉下一个调用者,这里面可能有异常啊,你调用时注意一下。
/** * 目标:认识异常。 */ public class ExceptionTest1 { public static void main(String[] args) throws ParseException{ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date d = sdf.parse("2028-11-11 10:24"); System.out.println(d); } }
- 第二种:使用try…catch语句块异常进行处理。
public class ExceptionTest1 { public static void main(String[] args) throws ParseException{ try { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date d = sdf.parse("2028-11-11 10:24"); System.out.println(d); } catch (ParseException e) { e.printStackTrace(); } } }
好了,关于什么是异常,我们就先认识到这里。
1.2 自定义异常
同学们经过刚才的学习已经认识了什么是异常了,但是无法为这个世界上的全部问题都提供异常类,如果企业自己的某种问题,想通过异常来表示,那就需要自己来定义异常类了。
我们通过一个实际场景,来给大家演示自定义异常。
需求:写一个saveAge(int age)方法,在方法中对参数age进行判断,如果age<0或者>=150就认为年龄不合法,如果年龄不合法,就给调用者抛出一个年龄非法异常。
分析:Java的API中是没有年龄非常这个异常的,所以我们可以自定义一个异常类,用来表示年龄非法异常,然后再方法中抛出自定义异常即可。
- 先写一个异常类AgeIllegalException(这是自己取的名字,名字取得很奈斯),继承
// 1、必须让这个类继承自Exception,才能成为一个编译时异常类。 public class AgeIllegalException extends Exception{ public AgeIllegalException() { } public AgeIllegalException(String message) { super(message); } }
- 再写一个测试类,在测试类中定义一个saveAge(int age)方法,对age判断如果年龄不在0~150之间,就抛出一个AgeIllegalException异常对象给调用者。
public class ExceptionTest2 { public static void main(String[] args) { // 需求:保存一个合法的年 try { saveAge2(225); System.out.println("saveAge2底层执行是成功的!"); } catch (AgeIllegalException e) { e.printStackTrace(); System.out.println("saveAge2底层执行是出现bug的!"); } } //2、在方法中对age进行判断,不合法则抛出AgeIllegalException public static void saveAge(int age){ if(age > 0 && age < 150){ System.out.println("年龄被成功保存: " + age); }else { // 用一个异常对象封装这个问题 // throw 抛出去这个异常对象 throw new AgeIllegalRuntimeException("/age is illegal, your age is " + age); } } }
- 注意咯,自定义异常可能是编译时异常,也可以是运行时异常
1.如果自定义异常类继承Excpetion,则是编译时异常。 特点:方法中抛出的是编译时异常,必须在方法上使用throws声明,强制调用者处理。 2.如果自定义异常类继承RuntimeException,则运行时异常。 特点:方法中抛出的是运行时异常,不需要在方法上用throws声明。
1.3 异常处理
同学们,通过前面两小节的学习,我们已经认识了什么是异常,以及异常的产生过程。接下来就需要告诉同学们,出现异常该如何处理了。
比如有如下的场景:A调用用B,B调用C;C中有异常产生抛给B,B中有异常产生又抛给A;异常到了A这里就不建议再抛出了,因为最终抛出被JVM处理程序就会异常终止,并且给用户看异常信息,用户也看不懂,体验很不好。
此时比较好的做法就是:1.将异常捕获,将比较友好的信息显示给用户看;2.尝试重新执行,看是是否能修复这个问题。
我们看一个代码,main方法调用test1方法,test1方法调用test2方法,test1和test2方法中多有扔异常。
- 第一种处理方式是,在main方法中对异常进行try…catch捕获处理了,给出友好提示。
public class ExceptionTest3 { public static void main(String[] args) { try { test1(); } catch (FileNotFoundException e) { System.out.println("您要找的文件不存在!!"); e.printStackTrace(); // 打印出这个异常对象的信息。记录下来。 } catch (ParseException e) { System.out.println("您要解析的时间有问题了!"); e.printStackTrace(); // 打印出这个异常对象的信息。记录下来。 } } public static void test1() throws FileNotFoundException, ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date d = sdf.parse("2028-11-11 10:24:11"); System.out.println(d); test2(); } public static void test2() throws FileNotFoundException { // 读取文件的。 InputStream is = new FileInputStream("D:/meinv.png"); } }
- 第二种处理方式是:在main方法中对异常进行捕获,并尝试修复
/** * 目标:掌握异常的处理方式:捕获异常,尝试修复。 */ public class ExceptionTest4 { public static void main(String[] args) { // 需求:调用一个方法,让用户输入一个合适的价格返回为止。 // 尝试修复 while (true) { try { System.out.println(getMoney()); break; } catch (Exception e) { System.out.println("请您输入合法的数字!!"); } } } public static double getMoney(){ Scanner sc = new Scanner(System.in); while (true) { System.out.println("请您输入合适的价格:"); double money = sc.nextDouble(); if(money >= 0){ return money; }else { System.out.println("您输入的价格是不合适的!"); } } } }
好了,到此我们关于异常的知识就全部学习完了
二、File类
接下来,我们要学习的知识是一个File类。但是在讲这个知识点之前,我想先和同学们聊点别的,聊完之后再回过来学习File你会更容易理解一些。
- 先问大家一个问题,目前你写代码时存储数据,可以用哪些方案?
答案如下图所示:可以是变量、可以是数组、可以是对象、可以是集合,但是这些数据都是存储在内存中的,只要程序执行结束,或者断点了,数据就消失了。不能永久存储。
- 有些数据要长久保存,该怎么办呢?
答案如下图所示:可以将数据以文件的形式存在硬盘里,即使程序结束了,断点了只要硬盘没坏,数据就永久存在。
而现在要学习的File类,它的就用来表示当前系统下的文件(也可以是文件夹),通过File类提供的方法可以获取文件大小、判断文件是否存在、创建文件、创建文件夹等。
**但是需要我们注意:**File对象只能对文件进行操作,不能操作文件中的内容。
2.1 File对象的创建
学习File类和其他类一样,第一步是创建File类的对象。 想要创建对象,我们得看File类有哪些构造方法。
下面我们演示一下,File类创建对象的代码
需求我们注意的是:路径中"\"要写成"\\", 路径中"/"可以直接用
/** * 目标:掌握File创建对象,代表具体文件的方案。 */ public class FileTest1 { public static void main(String[] args) { // 1、创建一个File对象,指代某个具体的文件。 // 路径分隔符 // File f1 = new File("D:/resource/ab.txt"); // File f1 = new File("D:\\resource\\ab.txt"); File f1 = new File("D:" + File.separator +"resource" + File.separator + "ab.txt"); System.out.println(f1.length()); // 文件大小 File f2 = new File("D:/resource"); System.out.println(f2.length()); // 注意:File对象可以指代一个不存在的文件路径 File f3 = new File("D:/resource/aaaa.txt"); System.out.println(f3.length()); System.out.println(f3.exists()); // false // 我现在要定位的文件是在模块中,应该怎么定位呢? // 绝对路径:带盘符的 // File f4 = new File("D:\\code\\javasepromax\\file-io-app\\src\\itheima.txt"); // 相对路径(重点):不带盘符,默认是直接去工程下寻找文件的。 File f4 = new File("file-io-app\\src\\itheima.txt"); System.out.println(f4.length()); } }
2.2 File判断和获取方法
各位同学,刚才我们创建File对象的时候,会传递一个文件路径过来。但是File对象封装的路径是存在还是不存在,是文件还是文件夹其实是不清楚的。好在File类提供了方法可以帮我们做判断。
话不多少,直接上代码
/** 目标:掌握File提供的判断文件类型、获取文件信息功能 */ public class FileTest2 { public static void main(String[] args) throws UnsupportedEncodingException { // 1.创建文件对象,指代某个文件 File f1 = new File("D:/resource/ab.txt"); //File f1 = new File("D:/resource/"); // 2、public boolean exists():判断当前文件对象,对应的文件路径是否存在,存在返回true. System.out.println(f1.exists()); // 3、public boolean isFile() : 判断当前文件对象指代的是否是文件,是文件返回true,反之。 System.out.println(f1.isFile()); // 4、public boolean isDirectory() : 判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之。 System.out.println(f1.isDirectory()); } }
除了判断功能还有一些获取功能,看代码
File f1 = new File("D:/resource/ab.txt"); // 5.public String getName():获取文件的名称(包含后缀) System.out.println(f1.getName()); // 6.public long length():获取文件的大小,返回字节个数 System.out.println(f1.length()); // 7.public long lastModified():获取文件的最后修改时间。 long time = f1.lastModified(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); System.out.println(sdf.format(time)); // 8.public String getPath():获取创建文件对象时,使用的路径 File f2 = new File("D:\\resource\\ab.txt"); File f3 = new File("file-io-app\\src\\itheima.txt"); System.out.println(f2.getPath()); System.out.println(f3.getPath()); // 9.public String getAbsolutePath():获取绝对路径 System.out.println(f2.getAbsolutePath()); System.out.println(f3.getAbsolutePath());
2.3 创建和删除方法
刚才有同学问老师,我们不能不用Java代码创建一个文件或者文件夹呀?答案是有的,不光可以创建还可以删除。
File类提供了创建和删除文件的方法,话不多少,看代码。
/** * 目标:掌握File创建和删除文件相关的方法。 */ public class FileTest3 { public static void main(String[] args) throws Exception { // 1、public boolean createNewFile():创建一个新文件(文件内容为空),创建成功返回true,反之。 File f1 = new File("D:/resource/itheima2.txt"); System.out.println(f1.createNewFile()); // 2、public boolean mkdir():用于创建文件夹,注意:只能创建一级文件夹 File f2 = new File("D:/resource/aaa"); System.out.println(f2.mkdir()); // 3、public boolean mkdirs():用于创建文件夹,注意:可以创建多级文件夹 File f3 = new File("D:/resource/bbb/ccc/ddd/eee/fff/ggg"); System.out.println(f3.mkdirs()); // 3、public boolean delete():删除文件,或者空文件,注意:不能删除非空文件夹。 System.out.println(f1.delete()); System.out.println(f2.delete()); File f4 = new File("D:/resource"); System.out.println(f4.delete()); } }
需要注意的是:
1.mkdir(): 只能创建单级文件夹、 2.mkdirs(): 才能创建多级文件夹 3.delete(): 文件可以直接删除,但是文件夹只能删除空的文件夹,文件夹有内容删除不了。
2.4 遍历文件夹方法
有人说,想获取到一个文件夹中的内容,有没有方法呀?也是有的,下面我们就学习两个这样的方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iOVatIwh-1690250314968)(assets/1667659732559.png)]
话不多少上代码,演示一下
/** * 目标:掌握File提供的遍历文件夹的方法。 */ public class FileTest4 { public static void main(String[] args) { // 1、public String[] list():获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。 File f1 = new File("D:\\course\\待研发内容"); String[] names = f1.list(); for (String name : names) { System.out.println(name); } // 2、public File[] listFiles():(重点)获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点) File[] files = f1.listFiles(); for (File file : files) { System.out.println(file.getAbsolutePath()); } File f = new File("D:/resource/aaa"); File[] files1 = f.listFiles(); System.out.println(Arrays.toString(files1)); } }
这里需要注意几个问题
1.当主调是文件时,或者路径不存在时,返回null 2.当主调是空文件夹时,返回一个长度为0的数组 3.当主调是一个有内容的文件夹时,将里面所有一级文件和文件夹路径放在File数组中,并把数组返回 4.当主调是一个文件夹,且里面有隐藏文件时,将里面所有文件和文件夹的路径放在FIle数组中,包含隐藏文件 5.当主调是一个文件夹,但是没有权限访问时,返回null
关于遍历文件夹的基本操作就学习完了。 但是有同学如果想要获取文件夹中子文件夹的内容,那目前还做不到。但是学习下面了下面的递归知识就,很容易做到了。