文章目录
关于IO流
IO流的概述
IO流是用来处理设备之间的数据传输,比如上传文件和下载文件;在上传和下载的时候,可能需要我们去处理一些比如下载中断的问题;这些数据在电脑中,是以文件的形式存在的;
IO流前奏
在学习IO流之前,我们需要学习异常机制和File类;
讲解IO流之前为什么先讲解异常和File类呢?
因为File表示的是IO流将来要操作的文件,所以我们需要学习File类;
而常见操作文件无非就是上传文件和下载文件,在这个操作的过程中可能出现问题,出现问题后,我们需要对对应的代码进行处理。所以我们需要学习异常;
异常机制
故事引入
张三是一个骑行爱好者,他在骑行遇到以下几种情况:
在骑行之前发现车闸松了,这个问题属于必须解决,不解决就不能出发;——编译期异常
在骑行途中遇到了自行车没气了,可以想办法解决,也可以不解决,推着自行车返回去;——错误
在骑行途中,如果车轱辘飞了,这个属于大问题,必须解决;——运行期异常
异常的概述
- 在Java中,对于遇到的问题,有一个类来描述:Throwable,它是所有异常或者错误的父类;
- 对于一般性的问题使用:Exception 类来描述;
- 严重性问题或者错误使用:Error 类描述;
异常继承图解
运行期异常
交由Java默认处理运行期异常
代码示例:
public class MyTest {
public static void main(String[] args) {
int a=10;
int b=0;
System.out.println(a/b);
}
}
输出结果:
Exception in thread “main” java.lang.ArithmeticException: / by zero
at demo4.MyTest.main(MyTest.java:7)ArithmeticException:当出现异常的运算条件时,抛出此异常,它是运行期异常RuntimeException的子类;
我们事先并不知道出现此错误,只是说有可能出现这个错误,并没有自己解决;
这个运行期的异常,我们交给了Java默认处理,它的处理方式就是:遇到异常,Java虚拟机打印异常的堆栈信息,然后退出Java虚拟机,也就是后面的代码也不会执行了。如果你觉得这种默认的处理方式不够"友好",你可以自己去处理;
手动处理运行期异常
- 遇到异常了,我么可以自己去提示异常信息,但是不退出Java虚拟机,怎么处理?使用关键字try、catch;
- 语法:
try{
有可能出现问题的代码 ;
}catch(异常名 变量名){
针对问题的处理 ;
}
try里面放的是有可能出现问题的代码;
catch里面的参数是捕获何种类型的异常类名,即一旦出现这种异常处理的逻辑是什么;
catch括号里的参数没有出现你要捕获的异常类型,catch块里面的代码就不会执行;
有些异常我们无法预知,只是说有可能出现,我们就去捕获一下;
- 代码示例:
public class MyTest {
public static void main(String[] args) {
int a=10;
int b=0;
try {
System.out.println(a/b);
} catch (ArithmeticException e) {
System.out.println("除数为0了!");
}
System.out.println("下面的代码!");
}
}
输出结果:
除数为0了!
下面的代码!
多种异常并列捕获
- 语法:
try {
可能出现问题的代码 ;
}catch(异常名1 变量名1){
对异常的处理方式 ;
}catch (异常名2 变量名2){
对异常的处理方式 ;
}....
- 代码示例:
public class MyTest {
public static void main(String[] args) {
int a = 10;
int b = 0;
int[] ints = new int[2];
try {
System.out.println(ints[3]);
System.out.println(a / b);
} catch (ArithmeticException e) {
System.out.println("除数为0了!");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("角标越界了!");
}
System.out.println("下面的代码!");
}
}
输出结果:
角标越界了!
下面的代码!
- try-catch可以捕获多种异常,写多个catch并列捕获,算术异常、空指针异常、角标越界异常等等;如果你无法预知到底会出现什么错误,我们可以直接捕获一个大异常,也就是Exception异常;
public class MyTest {
public static void main(String[] args) {
int a = 10;
int b = 0;
int[] ints = new int[2];
try {
System.out.println(ints[3]);
System.out.println(a / b);
} catch (Exception e) {
System.out.println("代码出现问题了!");
}
System.out.println("下面的代码!");
}
}
/*代码出现问题了!
下面的代码!*/
- 但是不建议这样使用,为什么?
能明确的异常尽量明确,不能一捕了之
,这样写不太规范;
并且你发现如果这时候再把算术异常等写在这个大异常后面,语法会报错,这是因为:捕获的多个异常之间有父子继承关系,父类异常放在最后,子类并列异常谁前谁后没关系;
try块里面尽量不要放一些废代码
,比如定义变量等语句,会造成性能的浪费
;
代码示例:
public class MyTest {
public static void main(String[] args) {
int a = 10;
int b = 0;
int[] ints = new int[2];
ArrayList<Integer> list = new ArrayList<>();
list = null;
try {
list.add(200);
System.out.println(ints[3]);
System.out.println(a / b);
} catch (ArithmeticException e) {
System.out.println("除数为0了!");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("角标越界了!");
} catch (NullPointerException e) {
System.out.println("空指针异常了!");
} catch (Exception e) {
System.out.println("代码出问题了!");
}
System.out.println("下面的代码!");
/*空指针异常了!
下面的代码!*/
}
}
JDK1.7之后的新语法
- JDK 1.7之后,支持使用catch( 异常 1 | 异常2 |……),这种写法就是简化了代码,将多个异常处理逻辑写在一个catch块里面,但是不能明确异常,你也可以在catch里面使用
关键字instanceof
去判断该异常的类型,但是这样就显得没必要了; - 注意:
1、处理方式是一致的,(实际开发中,好多时候可能就是针对同类型的问题,给出同一个处理);
2、多个异常间必须是平级关系; - 语法:
try {
可能出现问题的代码 ;
} catch(异常名1 | 异常名2 | .... 变量名){
对异常的处理方案 ;
}
代码示例:
public class MyTest {
public static void main(String[] args) {
int a = 10;
int b = 0;
int[] ints = new int[2];
ArrayList<Integer> list = new ArrayList<>();
list = null;
try {
list.add(200);
System.out.println(ints[3]);
System.out.println(a / b);
} catch (ArithmeticException | ArrayIndexOutOfBoundsException | NullPointerException e) {
if (e instanceof ArithmeticException) {
System.out.println("除数为0了!");
} else if (e instanceof ArrayIndexOutOfBoundsException) {
System.out.println("角标越界了!");
} else if (e instanceof NullPointerException) {
System.out.println("空指针异常了!");
}
}
System.out.println("下面的代码!");
/*空指针异常了!
下面的代码!*/
}
}
finally块
- try-catch-finally 的完整语法是:
try {
可能出现问题的代码 ;
}catch(异常名 变量名){
针对问题的处理 ;
}finally{
释放资源;
}
- 不管try里面的代码有没有遇到异常,finally里面的代码一定会执行,一般我们会把一些善后收尾的代码写在里面,比如释放资源;finally块不是必须要写的,可以写也可以不写,根据自己的需求来设计程序;
注意:在善后收尾的时候,做一下非空判断,否则就会报空指针异常的错误;
public class MyTest {
public static void main(String[] args) {
int a = 10;
int b = 0;
int[] ints = new int[2];
Scanner sc = new Scanner(System.in);
ArrayList<Integer> list = new ArrayList<>();
list = null;
try {
list.add(200);
System.out.println(ints[3]);
System.out.println(a / b);
} catch (ArithmeticException | ArrayIndexOutOfBoundsException | NullPointerException e) {
if (e instanceof ArithmeticException) {
System.out.println("除数为0了!");
} else if (e instanceof ArrayIndexOutOfBoundsException) {
System.out.println("角标越界了!");
} else if (e instanceof NullPointerException) {
System.out.println("空指针异常了!");
}
} finally {
if (sc != null) {
sc.close();
}
}
System.out.println("下面的代码!");
/*空指针异常了!
下面的代码!*/
}
}
finally块与return关键字
代码示例1:
public class MyTest {
public static void main(String[] args) {
int a = 10;
int b = 0;
try {
System.out.println(a / b);
} catch (ArithmeticException e) {
System.out.println("除数为0了!");
} finally {
System.out.println("你会执行吗?");
}
System.out.println("下面的代码!");
/*除数为0了!
你会执行吗?
下面的代码!*/
}
}
代码示例2:
public class MyTest {
public static void main(String[] args) {
int a = 10;
int b = 0;
try {
System.out.println(a / b);
} catch (ArithmeticException e) {
System.out.println("除数为0了!");
return;
} finally {
System.out.println("你会执行吗?");
}
System.out.println("下面的代码!");
/*除数为0了!
你会执行吗?*/
}
}
代码示例3:
public class MyTest {
public static void main(String[] args) {
int a = 10;
int b = 0;
try {
System.out.println(a / b);
} catch (ArithmeticException e) {
System.out.println("除数为0了!");
System.exit(0);//程序正常退出
} finally {
System.out.println("你会执行吗?");
}
System.out.println("下面的代码!");
/*除数为0了!*/
}
}
- 由上面的代码可以知道:
即使
存在return关键字,finally块里面的代码仍然执行,并且是在return关键字之前执行
,否则的话就不会输出了;
如果是程序正常退出,终止当前正在运行的 Java 虚拟机,finally块里面的代码也就不会执行了
;
编译期异常
- 是指非RuntimeException及其子类,编译期异常必须处理,否则程序无法运行;
- 代码示例:
public class MyTest {
public static void main(String[] args) throws ParseException {
String sDate = "2012-08-23";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date date = format.parse(sDate);
System.out.println(date);
//Thu Aug 23 00:00:00 CST 2012
}
}
在学习SimpleDateFormat类的parse()的时候,我们遇到一个编译期的异常,当时处理的方法是使用IDEA的纠错键直接抛出这个错误;
编译期异常的处理的两种方式
- 上面的程序使用向上抛出和自己手动处理的区别:
向上抛出
public class MyTest {
public static void main(String[] args) throws ParseException {
String sDate = "2012-08-==23";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date date = format.parse(sDate);
System.out.println(date);
/*Exception in thread "main" java.text.ParseException: Unparseable date: "2012-08-==23"
at java.text.DateFormat.parse(DateFormat.java:366)
at org.westos.demo4.MyTest.main(MyTest.java:11)*/
}
}
手动解决
public class MyTest {
public static void main(String[] args) {
String sDate = "2012-08-==23";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = format.parse(sDate);
} catch (ParseException e) {
System.out.println("解析异常");
}
System.out.println(date);
/*解析异常
null*/
}
}
处理异常的时机选择
- 看一段代码:
public class MyTest {
public static void main(String[] args) throws ParseException {
String sDate = "2012-08-==23";
dateParse(sDate);
/*解析异常
null*/
}
private static void dateParse(String sDate) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
date = format.parse(sDate);
System.out.println(date);
}
}
- 我们把错误向上抛出,一层甩给一层,上面的代码里面,自定义方法抛给main(),如果main()还不想处理,仍然向上抛出,这时候只能是Java虚拟机来使用默认的方式进行处理;
- 一般来说,到了main()就不再继续抛出了,而是选择自己捕获处理,因为自己处理的方式友好;
- 还有就是假如我们上面的解析日期的方法写在工具类里面,这时候异常选择向上抛出,谁调用,谁解决,因为别人的处理异常的方式有可能和你的不一样;
- 对于异常的处理,不要做空处理(也就是catch块里面的代码不能为空),哪怕是一句简单的提示语句;
异常里面的几个方法
String getMessage()——返回此 throwable 的详细消息字符串;
StackTraceElement[] getStackTrace() ——提供编程访问由 printStackTrace() 输出的堆栈跟踪信息;
void printStackTrace()——将此 throwable 及其追踪输出至标准错误流;虚拟机默认调用该方法打印堆栈信息;
public class MyTest {
public static void main(String[] args) {
String sDate = "2012-08-==23";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = format.parse(sDate);
} catch (ParseException e) {
System.out.println(e.getMessage());
//Unparseable date: "2012-08-==23"
System.out.println(e.getStackTrace());
//[Ljava.lang.StackTraceElement;@60e53b93
e.printStackTrace();
/*java.text.ParseException: Unparseable date: "2012-08-==23"
at java.text.DateFormat.parse(DateFormat.java:366)
at org.westos.demo4.MyTest.main(MyTest.java:13)*/
}
System.out.println(date);
}
}
关键字throw与throws的区别
- 相同点:两者都可以进行异常的抛出;
- 不同点:throws用在方法的声明上,throw用在方法的内部;
throws抛出来的是一个类,而throw则是new出来的对象;
代码示例:(throws之前已经遇到过了,这里练习一下throw的用法)
public class MyTest {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入第一个数字:");
int one = sc.nextInt();
System.out.println("请输入第二个数字:");
int tow = sc.nextInt();
if (tow == 0) {
throw new ArithmeticException("除数为0了~");
} else {
System.out.println(one / tow);
}
}
}
- 其实在学习ArrayList里面的内部迭代的时候,就已经接触到了throw关键字,内部迭代是ArrayList类里面的一个内部类,他的很多方法使用到了throw关键字;
自定义异常
- 在我们进行实际的开发时,会遇到各种异常,
Java给我们提供的异常类,并不能完全描述我们遇到的异常
;比如你在做银行类的项目,可能遇到余额不足的问题,这时候程序检测到这个异常,就需要抛出异常,然程序终止;Java没有提供给我们这样的异常,还有诸如人脸识别的异常、指纹识别的异常等,业务需要这些异常,我们可以自定义它们; - 如何把自定义异常纳入异常体系当中?自定义的异常都需要继承运行期异常类;
- 代码示例:
---测试类:
public class MyTest {
public static void main(String[] args) {
//输入成绩
System.out.println("请输入你的成绩:");
Scanner sc = new Scanner(System.in);
int score = sc.nextInt();
if (score < 0 || score > 100) {
throw new ScoreLegalException("成绩不合格!");
} else {
System.out.println("你的成绩合格!");
}
}
}
----异常类:
public class ScoreLegalException extends RuntimeException{
public ScoreLegalException(String message) {
super(message);
}
}
使用异常需要注意的问题
- 子类在重写父类的方法时,如果父类没有抛出异常,子类也不能抛出异常,这时候,一旦有异常,你就内部消化,使用try-catch块处理;
public class Fu {
public void show() {
String sDate = "2012-08-==23";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = format.parse(sDate);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(date);
}
}
class Zi extends Fu{
@Override
public void show() {
System.out.println("这是子类~");
}
}
- 子类不能抛出父类没有抛出过的异常;
- 子类抛出的异常必须是和父类异常一样或者是父类异常的子类,不能是基类;
- 父类抛出异常,子类可以不抛出;
public class Fu {
public void show() throws ParseException {
String sDate = "2012-08-==23";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
date = format.parse(sDate);
System.out.println(date);
}
}
class Zi extends Fu{
@Override
public void show() {
try {
super.show();
} catch (ParseException e) {
e.printStackTrace();
}
}
}