Java 第九周总结
第九周的作业。
目录
1.本章学习总结
2.Java Q&A
3.码云上代码提交记录及PTA实验总结
1.本章学习总结
1.1 以你喜欢的方式(思维导图或其他)归纳总结异常相关内容。
Java的基本理念是“结构不佳的代码不能运行”。
编写程序时会出现错误,一般来说可以分为两种:一种是语法错误,这类错误往往可以通过编译器来发现并且帮助我们检查;还有一种是运行时错误,就是做OJ时经常出现的Runtime Error。一般来说,出现这个问题的时候主要是出现了:
-
数组越界的错误
-
除0、对负数开根号等算术错误
-
堆栈溢出(比如递归调用层数过多)。
那一般针对这样的问题,我们也可以通过改写代码来解决: -
数组越界,可能是数组开的不够大,或者是边界写错了
-
算术错误也可以通过检查来发现
-
至于最后堆栈溢出,我一般是在用深度优先搜索的时候,因为DFS太深,才会出现,这时候改成广度优先搜索来改进,或者是自己编写递归的栈(当然我从来不这样做)。
发现错误的理想时机是在编译阶段,然而,编译期间不能找出所有的错误,余下的问题必须在运行期间解决掉。现实当中的编程会遇到各种各样的问题,我们不能把所有的异常都预料到,或者可以通过修改代码来解决所有的错误。这个时候,就需要有错误的解决方案: -
对于C来说,出错之后一般都会返回特定的错误代码,如果正常运行就会出现我们期望看到的
return 0
,如果出现其他错误,则会返回与这个错误相对应的错误编号。这样做有一个不好的地方,就是人们很难通过看错误编号来判断到底是什么地方出现了问题。 -
所以Java引入了异常:将程序出现的错误当做一种异常类包装起来。发生错误时,就会有一个异常的对象生成并且被JVM发现,然后找到该异常的相关代码进行处理。
这种处理方式的好处就是允许我们强制程序停止运行,并告诉我们出现了什么问题,或者强制程序处理问题,并返回到稳定状态。而且对于C来说,如果想处理错误的话,势必程序业务代码要和错误处理的代码混在一块,导致程序的可读性下降。而异常处理可以: -
使用户充分了解错误的详细信息
-
让用户更有效地处理和发现错误
-
会出错的业务代码和错误处理代码有效分离,这是异常的一个重要目标,可以在一段代码中专注于要完成的事情,将如何处理错误,放在另一段代码中完成。
异常继承架构:
注明图片来源:Java 异常类层次结构
图片中粉色框标注的异常是受检异常,蓝色框标注的是非受检异常。
2.Java Q&A
1. 常用异常
1.1 截图你的提交结果(出现学号)
1.2 自己以前编写的代码中经常出现什么异常、需要捕获吗(为什么)?应如何避免?
之前编写代码遇到的常见异常
Checked Exception需要捕获,我们之前遇到的比较多的异常都属于Unchecked Exception,Unchecked Exception的前三个异常往往都是变成不当所造成的,比如空指针异常就是对null对象调用方法,那么在使用之前进行判断即可。至于后面三个异常可能会在程序提供输入的时候,用户输入不当的时候造成,这时候可以使用捕获来防止程序中断或者提供更加友好的输入。
1.3 什么样的异常要求用户一定要使用捕获处理?
在编译时强制检查的异常就是Checked Exception。Checked Exception抛出之后要求程序员要么使用try-catch块来捕获并处理该异常,要么就在方法之后添上throws关键字,来告诉用户这个方法可能会抛出的异常,然后用户就能根据异常的类型进行处理。
不过编程思想里面还提到了一种方法,就是声明方法将抛出异常,但是实际上并没有任何异常抛出,相当于给以后可能会出现的异常占个空位,以后抛出的时候就可以不用修改相应的代码。
在定义抽象基类和接口时这种能力很重要,这样派生类或接口实现就能够抛出这些预先声明的异常。
举个例子,不是抽象基类也不是接口,但是如果我们想使类具有克隆能力,除了重写Object类的clone()方法,还有使当前类操作Cloneable接口,否则就会抛出CloneNotSupportedException的异常。
2.处理异常使你的程序更加健壮
2.1 截图你的提交结果(出现学号)
2.2 实验总结
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
scanner.nextLine();
int[] a = new int[n];
for (int i = 0; i < n; i++) {
String string = scanner.nextLine();
try {
int x = Integer.parseInt(string);
a[i] = x;
} catch (Exception e) {
// TODO: handle exception
System.out.println(e);
i--;
}
}
System.out.println(Arrays.toString(a));
其实这个程序当中可能出现运行时异常的地方有两处,都出现在输入的时候。不过题目要求我们处理输入数组元素时,如果发现是非整型字符串的时候,就要提示有异常。所以只要对对应输入进行捕获即可。捕获到后,直接输出异常信息,并且为了最后装满数组,出现异常的时候计数器需要自减一次。
3.throw与throws
3.1 截图你的提交结果(出现学号)
3.2 阅读Integer.parsetInt源代码,结合3.1说说抛出异常时需要传递给调用者一些什么信息?
public static int parseInt(String s) throws NumberFormatException {
return parseInt(s,10);
}
如果省略第二个参数,则默认转换为10进制。
public static int parseInt(String s, int radix)
throws NumberFormatException
{
/*
* WARNING: This method may be invoked early during VM initialization
* before IntegerCache is initialized. Care must be taken to not use
* the valueOf method.
*/
if (s == null) {
throw new NumberFormatException("null");
}
if (radix < Character.MIN_RADIX) {
throw new NumberFormatException("radix " + radix +
" less than Character.MIN_RADIX");
}
if (radix > Character.MAX_RADIX) {
throw new NumberFormatException("radix " + radix +
" greater than Character.MAX_RADIX");
}
int result = 0;
boolean negative = false;
int i = 0, len = s.length();
int limit = -Integer.MAX_VALUE;
int multmin;
int digit;
if (len > 0) {
char firstChar = s.charAt(0);
if (firstChar < '0') { // Possible leading "+" or "-"
if (firstChar == '-') {
negative = true;
limit = Integer.MIN_VALUE;
} else if (firstChar != '+')
throw NumberFormatException.forInputString(s);
if (len == 1) // Cannot have lone "+" or "-"
throw NumberFormatException.forInputString(s);
i++;
}
multmin = limit / radix;
while (i < len) {
// Accumulating negatively avoids surprises near MAX_VALUE
digit = Character.digit(s.charAt(i++),radix);
if (digit < 0) {
throw NumberFormatException.forInputString(s);
}
if (result < multmin) {
throw NumberFormatException.forInputString(s);
}
result *= radix;
if (result < limit + digit) {
throw NumberFormatException.forInputString(s);
}
result -= digit;
}
} else {
throw NumberFormatException.forInputString(s);
}
return negative ? result : -result;
}
这个方法只抛一种异常,那就是NumberFormatException
出现以下几种情况抛异常
- 针对s为null,输入进制小于2或者大于36。
- s为空串。
- 第一个字符既不是+,也不是-,还不是数字。
- 只有+或-
- 如果在后面出现了非数字字符,则Character的digit()方法会返回-1
- 出现溢出的时候
if (result < multmin) {
throw NumberFormatException.forInputString(s);
}
mutmin是边界值除以进制得到的,这边做这样一个处理,就是如果result乘上对应的进制即使超出了极值,那么Integer也是放不下的,所以需要在还没乘上去的时候就判断。
if (result < limit + digit) {
throw NumberFormatException.forInputString(s);
}
其实就是result - digit < limit,但是同理,先去做减运算,可能向下溢出,所以这边加上这个digit,再进行比较。
4.多种异常的捕获
4.1 截图你的提交结果(出现学号)
4.2 一个try块中如果可能抛出多种异常,捕获时需要注意些什么?
catch (Exception e) {
System.out.println(e);
continue;
}
对于这道题来说,反正捕获到了异常就输出,所以一个Exception就够了。
但是我们通常会根据异常类型的不同,而需要进行不同的处理,要注意的就是父类的异常处理要放在子类的后面,否则父类的异常处理就会抢在子类的之前就进行了,那么子类的异常处理程序就永远都处理不到,这个是没有意义的。不过反正编译器会报错的,调整一下顺序就好了。
即使多个异常只需要一个catch块,即使用多重捕捉的方法,也要注意左边的异常不能是右边异常的父类,否则也会发生编译错误。
5.为如下代码加上异常处理
byte[] content = null;
FileInputStream fis = new FileInputStream("testfis.txt");
int bytesAvailabe = fis.available();//获得该文件可用的字节数
if(bytesAvailabe>0){
content = new byte[bytesAvailabe];//创建可容纳文件大小的数组
fis.read(content);//将文件内容读入数组
}
System.out.println(Arrays.toString(content));//打印数组内容
5.1 改正代码,让其可正常运行。注意:里面有多个方法均可能抛出异常
- FileInputStream的构造方法需要抛出FileNotFoundException异常,所以加在throws的参数列表中。
- FileInputStream的available()方法需要抛出IOException异常。又FileNotFoundException是IOException的子类,所以现在参数列表中仅有一个IOException
- read()方法同样也需要抛出IOException异常。
所以改完之后就变成了这样:
public static void main(String[] args) throws IOException {
byte[] content = null;
FileInputStream fis = new FileInputStream("testfis.txt");
int bytesAvailabe = fis.available();// 获得该文件可用的字节数
if (bytesAvailabe > 0) {
content = new byte[bytesAvailabe];// 创建可容纳文件大小的数组
fis.read(content);// 将文件内容读入数组
}
System.out.println(Arrays.toString(content));// 打印数组内容
fis.close();
}
还有一种就是这样
public static void main(String[] args) {
byte[] content = null;
FileInputStream fis = null;
try {
fis = new FileInputStream("testfis.txt");
int bytesAvailabe = fis.available();// 获得该文件可用的字节数
if (bytesAvailabe > 0) {
content = new byte[bytesAvailabe];// 创建可容纳文件大小的数组
fis.read(content);// 将文件内容读入数组
}
System.out.println(Arrays.toString(content));// 打印数组内容
} catch(IOException e) {
e.printStackTrace();
} finally {
// TODO: handle finally clause
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
用try-catch来代替throws关键字。
5.2 如何使用Java7中的try-with-resources来改写上述代码实现自动关闭资源?
public static void main(String[] args) throws IOException {
byte[] content = null;
try(FileInputStream fis = new FileInputStream("testfis.txt");) {
int bytesAvailabe = fis.available();// 获得该文件可用的字节数
if (bytesAvailabe > 0) {
content = new byte[bytesAvailabe];// 创建可容纳文件大小的数组
fis.read(content);// 将文件内容读入数组
}
System.out.println(Arrays.toString(content));// 打印数组内容
}
}
try-with-resources声明确保在声明结束时每个在声明开始打开的资源都关闭(不管是正常结束还是突然结束)。try-with-resources方法所使用的对象必须得操作AutoCloseable接口。
try-with-resources也可以同时打开多个资源,在圆括号当中用分号隔开,值得注意的是多个资源的close()方法的调用与创建资源的顺序是相反的。
6.重点考核:使用异常改进购物车系统
举至少两个例子说明你是如何使用异常机制让你的程序变得更健壮。
说明要包含2个部分:1. 问题说明(哪里会碰到异常)。2.解决方案(关键代码)
1.为了可以加入异常,特地把所有商品的信息写在了文件里面再读出。所以文件读入了之后就可能会出现文件找不到的异常。
FileInputStream fileInputStream = null;
Scanner scanner = null;
try {
fileInputStream = new FileInputStream("AllGoods.txt");
scanner = new Scanner(fileInputStream, "UTF-8");
while (scanner.hasNextLine()) {
String string = scanner.nextLine();
System.out.println(string);
String[] strings = string.split(" ");
if (strings.length > 2) {
goodsList.add(
new Book(strings[0], Double.parseDouble(strings[1]),
strings[2], strings[3], strings[4]));
} else {
goodsList.add(new ClothesType(strings[0], Double.parseDouble(strings[1])));
}
}
}
2.在文件关闭的时候也可能会出现输入输出的错误,这个时候就要抛IOException。
if (scanner != null) {
scanner.close();
}
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
前面自己都提到了,对于一般的运行时异常,如果能通过修改代码来完善,就不要用try-catch。而且,我的购物车可能会涉及到受检异常的地方又比较少。加入文件读写差不多都是属于上面的这两种情况,所以就只将商品信息用文件存储起来了。
7.JavaFX入门
完成其中的作业3。内有代码,可在其上进行适当的改造。建议按照里面的教程,从头到尾自己搭建。
这边做作业4,按照教程添加CSS样式:
将界面改成黑色的主题,右边详细信息的表项设置为暗,具体信息设置为亮:
编辑框,在输入信息的时候,确认按钮作为缺省按钮:
8.课外阅读
8.1 Is the following code legal?
try {
} finally {
}
是合法的,如果有finally块的话就不一定要加catch块,Eclipse的代码补全里面就提供了try-catch和try-finally两种代码补全。finally的好处就是不管程序是怎么离开try块的,finally块里的代码保证可以被执行到,通常是用来恢复资源的,比如把打开的文件关闭这样的操作。
8.2 What exception types can be caught by the following handler?
catch (Exception e) {
}
What is wrong with using this type of exception handler?
所有的异常都将被捕获,因为所有异常都是由Exception导出。缺点就是不能根据特定的异常做特定的处理,所有的异常都以一种方式进行处理。
8.3 Is there anything wrong with the following exception handler as written? Will this code compile?
try {
} catch (Exception e) {
} catch (ArithmeticException a) {
}
Exception是所有异常的父类,所以在第一个catch块,异常就被捕获了,永远到不了第二个catch块,编译不会通过。
8.4 Match each situation in the first list with an item in the second list.
a.int[] A;
A[0] = 0;
b.The JVM starts running your program, but the JVM can't find the Java platform classes. (The Java platform classes reside in classes.zip or rt.jar.)
c.A program is reading a stream and reaches the end of stream marker.
d.Before closing the stream and after reaching the end of stream marker, a program tries to read the stream again.
1._b_error
2._d_checked exception
3._a_compile error
4._c_no exception
E1.Add a readList method to ListOfNumbers.java. This method should read in int values from a file, print each value, and append them to the end of the vector. You should catch all appropriate errors. You will also need a text file containing numbers to read in.
用Vector改写,从文件中读数据,可能会出现文件找不到的异常,还有输入的异常都需要捕获并处理。
E2.Modify the following cat method so that it will compile.
public static void cat(File file) {
RandomAccessFile input = null;
String line = null;
try {
input = new RandomAccessFile(file, "r");
while ((line = input.readLine()) != null) {
System.out.println(line);
}
return;
} finally {
if (input != null) {
input.close();
}
}
}
- finally里面关闭资源的时候close()方法也可能抛异常,所以要用try-catch块包起来
- 初始化RandomAccessFile需要抛FileNotFoundException 异常,使用readLine()方法都需要抛IOException异常
注明出处:The Java™ Tutorials
3.码云上代码提交记录及PTA实验总结
3.1 码云代码提交记录
- 在码云的项目中,依次选择“统计-Commits历史-设置时间段”, 然后搜索并截图
4. 课外阅读
任选下面一篇文章阅读,列举出几点自己能理解的异常处理最佳实践。
Java theory and practice:The exceptions debate
关于异常的包装,如果异常出现在底层的话,那么等到它最终被处理的时候,它会经过很多层的代码,被捕获,包装,抛出。每迭代一次,栈轨迹都可能会重复被记录很多次,所以等到异常最终被记录下来的时候,栈轨迹可能有很多页。所以有时候抛出异常的时候,控制台就是红红的一页。
关于代码的可读性,因为很多代码可能会扔出一连串的异常,所以错误处理的代码相对于真正的业务代码的比例会比较高,以至于要在这个方法中找到到底做什么事情是比较困难的。异常应该是通过集中错误处理使得代码更精简,一个只有三行代码却有六个catch块的看上去就很臃肿了。
关于非受检异常,大家的观点都不统一,Sun公司的观点是不要用,C#的方法是哪里都用,也有些人持中立。
非受检异常的最大风险就是它并没有自我记录,而受检异常是有的。除非API的创建者显式地记录被抛出的异常,不然调用者根本就不知道。这在C++当中非常常见,但是对于非常依赖于非受检异常的Java类库来说,这种现象就没有那么普遍。
所以如果决定使用非受检异常,我们就需要详尽地记录下,包括这个方法可能抛出的所有非受检异常。当涉及到非受检异常的时候,也要记住要使用try...finally即使没有任何异常被捕捉到,确保清理工作肯定会被执行到。
看的不过瘾的请点下面
回到顶部