20145215《Java程序设计》第5周学习总结
20145215《Java程序设计》第五周学习总结
教材学习内容总结
异常处理
语法与继承架构
异常就是程序在运行时出现不正常情况,异常的由来是因为Java把出现的问题封装成了对象,换句话说Java中的错误也是以对象的方式呈现为java.lang.Throwable
的各种子类实例,那么对于我们来说,当然是希望能解决程序的异常,因此在Java中也提供了特有的处理异常的语句。
- 使用try、catch: Java中所有错误都会被包装成对象,可以尝试(try)执行程序并捕捉(catch)代表错误的对象后做一些处理。使用了try、catch语法,JVM会尝试执行try区块中的程序代码,如果发生错误,执行程序会跳离错误发生点,然后比对catch括号中声明的类型,是否符合被抛出的错误对象类型,如果是就执行catch中的程序代码。try、catch的基本用法如下:
try
{
需要被检测的代码;
}
catch(异常类 变量)
{
处理异常的代码(处理方式);
}
- 异常继承架构:错误会被包装为对象,这些对象均可抛出,因此设计错误对象都继承自
java.lang.Throwable
类,Throwable定义了取得错误信息、堆栈追踪(Stack Trace)等方法,它有两个子类:java.lang.Error与java.lang.Exception。在此简述一下Error类与Exception类的区别,在Java中对于比较严重的问题,是通过Error类来进行描述的,而对于非严重的问题,则是通过Exception类来进行描述的。对于Error,一般不编写针对性的代码对其进行处理,因为此时已经超出了JVM的运行能力范围之外了,例如下面这串简单的代码:
public class ErrorTest {
public static void main(String[] args) {
byte[] arr=new byte[1024*1024*600];
}
}
便会弹出Error:
这是因为这个程序已经超出了JVM的内存空间,自然就会报出Error了,因此对于Error对象抛出时,基本上不用处理,任其传播至JVM为止。而对于Exception,我们可以编写针对性的代码对其进行处理,所以我们通常也称错误处理为异常处理。
- 单就语法与继承架构上来说,如果某个方法声明会抛出Throwable或子类实例,只要不是属于Error或java.lang.RuntimeException或其子类实例,就必须明确使用try、catch语法加以处理,或者在方法中用throws声明这个方法会抛出异常,否则会编译失败,throws的使用也提高了代码的安全性。
- Exception中有一个特殊的子类异常叫RuntimeException异常,就是运行时异常,它的特点是如果在函数内容抛出该异常,函数上可以不用声明,编译一样通过,如果在函数上声明了该异常,调用者可以不用进行处理,编译一样通过。Exception或其子对象,但非属于RuntimeException或其子类对象,称为受检异常(Checked Exception),属于RuntimeException衍生出来的类实例亦称为非受检异常(Unchecked Exception),受检异常存在的目的,在于API设计者实现某方法时,某些条件成立时会引发错误,而且认为调用方法的客户端有能力处理错误,要求编译程序提醒客户端必须明确处理错误,不然不可通过编译,API客户端无权选择要不要处理。
- 如果父类异常对象在子类异常对象前被捕捉,则catch子类异常对象将永远不会被执行,编译程序会检查出这个错误。从JDK7开始,可以使用多重捕捉语法(详情见教材234页),不过仍需注意异常的继承,catch括号中列出的异常不得有继承关系,否则会发生编译错误。
- 在catch区块进行完部分错误处理之后,可以使用throw将异常再度抛出,这里将throw和throws要区分开,throws使用在函数上,后面跟的是异常类,可以跟多个,用逗号隔开,而throw是使用在函数内,后面跟的是异常对象。
- 若想得知异常发生的根源,以及多重方法调用下异常的堆栈传播,可以利用异常对象自动收集的堆栈追踪来取得相关信息,例如调用异常对象的
printStackTrace()
、getStackTrace()
等方法。要善于堆栈追踪,前提是程序代码中不可有私吞异常的行为、对异常做了不适当的处理,或显示了不正确信息。在使用throw重抛异常时,异常的追踪堆栈起点,仍是异常的发生根源,而不是重抛异常的地方。如果想要让异常堆栈起点为重抛异常的地方,可以使用fillInStackTrace()
,这个方法会重新装填异常堆栈,将起点设为重抛异常的地方,并返回Throwable对象。
异常与资源管理
- finally区块:finally代码块定义一定会执行的代码,它通常用于关闭资源。对于异常的部分,如果没有做finally处理,那么这个程序是有缺陷的,每次调用完资源再把资源释放掉是必须的,否则对对方的运行压力会特别大,由此也可以看出finally的重要性。finally经常会与try、catch语法一起用,它的基本用法如下:
try
{
需要检测的代码;
}
catch(异常类 变量)
{
异常处理代码;
}
finally
{
一定会执行的代码;
}
如果程序撰写的流程中先return了,也有finally区块,finally区块会先执行完后,再将值返回,例如下面的例子:
public class FinallyDemo {
public static void main(String[] args) {
System.out.println(test(true));
}
static int test(boolean flag) {
try {
if (flag) {
return 1;
}
} finally {
System.out.println("finally…");
}
return 0;
}
}
它会先显示finally…再显示1。Finally代码块只有一种情况不会被执行,就是在之前执行了System.exit(0),System.exit(0)是将你的整个虚拟机里的内容都停掉了,也就是退出了JVM,括号里面的0表示的是正常退出程序,如果是1则表示非正常退出。
- 想要尝试关闭资源(Try-With-Resources)的对象,是撰写在try之后的括号中,如果无须catch处理任何异常,可以不用撰写,也不要撰写finally。尝试关闭资源语法可套用的对象,必须操作
java.lang.AutoCloseable
接口,该语法也可以同时关闭两个以上的对象资源,只要中间以分号隔开。在try的括号中,越后面撰写的对象资源会越早被关闭。
Collection与Map
使用Collection收集对象
- 收集对象的行为,像是新增对象的add()方法、移除对象的remove()方法等,都是定义在
java.util.Collection
中。既然可以收集对象,也要能逐一取得对象,这就是java.lang.Iterable
定义的行为,它定义了iterable()方法返回java.util.Iterator
操作对象,可以让你逐一取得收集的对象。Collection接口中有三个子接口,分别是List、Set和Queue。如果希望收集时记录记录每个对象的索引顺序,并可依索引取回对象,可以使用java.util.List
接口,如果希望收集的对象不重复,具有集合的行为,可以使用java.util.Set
接口,如果希望收集对象时以队列方式,收集的对象假如至尾端,取得对象时从前端,则可以使用java.util.Queue
接口,如果希望对Queue的两端进行加入、移除等操作,则可以使用java.util.Deque
。
- List接口中常用的类有:ArrayList和LinkedList,ArrayList的特点是线程不安全,但查询速度快,像排序时使用ArrayList便是利用了它速度快这一特点。LinkedList在操作List接口时,采用了链接结构,不会事先耗费内存,它的增删速度比较快。
- 不论是List、Set或者是Queue,都会有个iterator()方法,增强式for循环可运用在数组上,还可运用在操作Iterable接口的对象上,例如在foreach()方法中,用增强式for循环更加简化:
public class ForEach {
public static void main(String[] args) {
List names = Arrays.asList("Justin", "Monica", "Irene");
forEach(names);
forEach(new HashSet(names));
forEach(new ArrayDeque(names));
}
static void forEach(Iterable iterable) {
for (Object o : iterable) {
System.out.println(o);
}
}
}
- 在Java中,跟顺序有关的行为,通常要不对象本身是Comparable,要不就是另行指定Comparator对象告知如何排序。JDK8在List上增加了sort()方法,可接受Comparator实例来指定排序方式。
键值对应的Map
-
可以事先利用
java.util.Map
接口的操作对象来建立键值对应数据,之后若要取得值,只要用对应的键就可以迅速取得。判断键是否重复是根据hashcode()与equals(),所以作为键的对象必须操作hashcode()与equals()。常用Map操作类有HashMap、TreeMap和Properties。HashMap的特点是线程不安全,速度快,允许存放null 键,null值,TreeMap会对键进行排序,条件是作为键的对象必须操作Comparable接口,或者是在创建TreeMap时指定操作Comparable接口的对象,Properties的setProperty()可以指定字符串类型的键值,getProperty()可以指定字符串类型的键,取回字符串类型的值,通常称为属性名称与属性值。
-
如果想取得Map所有键,可以调用Map的keySet()返回Set对象,如果想取得Map中所有的值,则可以使用values()返回Collection对象。如果想同时取得Map的键与值,可以使用entrySet()方法,这回返回一个Set对象,每个元素都是Map.Entry实例,可以调用getKey()取得键,调用getvalue()取得值。
教材学习中的问题和解决过程
本周的学习发现了教材中的两个错误:
- 教材269页的Students.java原代码如下:
class Student {
private String name;
private String number;
Student(String name, String number) {
this.name = name;
this.number = number;
}
@Override
public String toString() {
return String.format("(%s,%s)", name, number);
}
}
public class Students {
public static void main(String[] args) {
Set students = new HashSet();
students.add(new Student("Justin", "B835031"));
students.add(new Student("Monica", "B835032"));
students.add(new Student("Justin", "B835031"));
System.out.println(set);
}
}
编译时出现错误:
仔细一想,这里根本就没有set这个关键字,怎么可能不报错呢,也不可能是Set,因为Set是一个类,不可能用println,所以println括号里面应该跟的是students,这样运行结果就正确了:
- 教材上266页SimpleLinkedList.java编译时弹出如下错误:
一开始我也没注意到为什么用elem会报错,后来看到了蔡野的博客(http://www.cnblogs.com/20145208cy/p/5343281.html),才发现原来代码中前面用的是Object o,后面却用了elem,程序自然也就报错了。所以,这也侧面反映出了多看看别人博客的好处,大家可以一起讨论,共同进步。
- 关于finally块中的代码是否一定会被执行的问题,在进过多次验证之后,我发现finally块中的代码一定会被执行,后来查阅网上的资料,发现只有一种情况下finally块不会被执行,那就是使用System.exit(0),这相当于直接关掉了JVM,并没有太大意义,出此之外就没有其他方法了。
- 对于迭代,教材中的描述不是很清楚,在查阅资料后,我对迭代有了更深的印象。迭代是取出集合中元素的一种方式,因为Collection中有iterator方法,所以每一个子类集合对象都具备迭代器,用法如下:
for(Iterator iter = iterator();iter.hasNext(); )
{
System.out.println(iter.next());
}
迭代器的next方法是自动向下取元素,要避免出现NoSuchElementException,迭代器的next方法返回值类型是Object,所以要记得类型转换。
代码调试中的问题和解决过程
以下是我自己编的一段测试代码:
class FinallyTest{
public static void main(String args[]) {
try{
int x=0;
int y=20;
int z=y/x;
System.out.println("y/x 的值是 :"+z);
}catch(ArithmeticException e){
System.out.println(" 捕获到算术异常 : "+e);
}
finally{
System.out.println(" 执行到 finally 块内!");
try{
String name;
if(name.equals(" LXM ")){
System.out.println(" 你的名字叫LXM!");
}
}
catch(Exception e){
System.out.println(" 又捕获到另一个异常 : "+e);
}
finally{
System.out.println(" 执行到内层的 finally 块内!");
}
}
}
}
编译时弹出了下面的错误:
一开始我以为定义name时后面什么都不加,系统会自动对它进行初始化,就和C语言一样,后来想起来在Java中是对对象进行操作,所以必须要绑定对象,于是我在String name
后面加了一个null,代码成功按照我预想的进行:
心得体会
经过这五周的学习,可以说,我们已经基本学完了Java的基础知识,但是学完并不代表掌握了,如果现在让我们独立去编写一个程序,我觉得我们可能依旧会错误百出。但是在我看来,真正的知识就是在实践的基础上获得的,我们之前的学习,包括敲书上的代码,只是为了让我们的脑子里对这些概念有个了解,真正想要掌握这些知识,只能是通过自己独立编写程序来获得。可能第一次编的时候会出现许多的错误,就像我一样,但凡事都有第一次,这次出现的错误,当你思考分析出问题所在之后,下次编的时候就能够避免,经验也是这样通过一点一滴累积起来的。相反,如果只是一味的照着书上的代码敲上去,我觉得我们可能很难有什么突破,当然,我不是说书上的代码毫无意义,我们在看书上代码的同时应该更多地去独立编写一些代码,这样对一些知识点也会理解的更加透彻。总而言之,多动手只会有益无害,学习Java是这样,学习其他的课程也是一样。以上便是我本周学习的一些感悟,也希望对大家能够起到一定的帮助!
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | 编写了Hello Java代码 |
第一周 | 100/100 | 2/2 | 12/12 | 编写了Hello Java代码 |
第二周 | 200/300 | 2/4 | 15/27 | 理解了printf和println的区别 |
第三周 | 450/750 | 1/5 | 22/49 | 对对象有了更深层次的理解 |
第四周 | 869/1619 | 1/6 | 28/77 | 对对象的三大特征有了更全面的认识 |
第五周 | 1123/2742 | 1/7 | 25/102 | 学会了异常处理 |
【附1】本周学习的代码已经成功托管,截图如下:
【附2】利用wc统计代码行数,截图如下: