cs61b week5 -- Exceptions, Iterators, Iterables

1.抛出异常
本节课我们使用上一节创建的ArrayMap进行讲解,假设你在main()中get()一个并不存在的key,如:

public static void main(String[] args) { ArrayMap am = new ArrayMap(); am.put("hello", 5); System.out.println(am.get("yolp")); }

那么当你运行时,会得到如下报错信息:
$ java ExceptionDemo Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1 at ArrayMap.get(ArrayMap.java:38) at ExceptionDemo.main(ExceptionDemo.java:6)

依据报错信息我们可以得知在ArrayMap.java代码第38行与ExceptiomDemo.java第6行出错,出错原因是数组越界。
当遇到错误时,Java能够自动抛出异常并停止程序的运行。有时候,Java给出的报错信息并不明显,不容易得出错误,我们可以手动添加报错信息,也就是抛出一些异常。
使用关键字 throw 我们可以抛出自定义的异常:
public V get(K key) { int location = keyIndex(key); if (location < 0) { throw new IllegalArgumentException("Key " + key + " does not exist in map."); } return values[keyIndex(key)]; }

一些容易出现异常的例子:
  • You try to use 383,124 gigabytes of memory.
  • You try to cast an Object as a Dog, but dynamic type is not Dog.
  • You try to call a method using a reference variable that is equal to null.
  • You try to access index -1 of an array.
    抛出异常其实是创建一个异常类型的Object,也就是相当于实例化一个异常类,所以即使程序本身并无错误,你也可以无缘无故地抛出一个异常:
public static void main(String[] args) { throw new RuntimeException("For no reason."); }

得到:
Exception in thread "main" java.lang.RuntimeException: For no reason.

2.捕获异常
假如我们仅仅只是在程序的某处抛出异常而不做任何处理,程序便会崩溃。但是我们可以通过捕获异常从而让程序继续运行下去,使用
try { throw new SomeException(); } catch (Exception e) { doSomething; }

例如:
try { throw new RuntimeException("for no reason"); } catch (Exception e) { System.out.println(e); } System.out.println("actually it's still running");

可以看到即使抛出RuntimeException,程序并没有崩溃:
java.lang.RuntimeException: for no reason actually it's still running

除了打印出异常信息 Excepetion e 之外,也可以在 catch 里面纠正程序错误
优化语法 cs61b week5 -- Exceptions, Iterators, Iterables
文章图片

假设我们写了一个读入文件的函数 readFile 那么需要考虑:
  • 文件是否存在
  • 内存是否足以读入全部字节
  • 读入失败怎么样
那么按照常规的使用 if 判断这些特殊情况:
func readFile: { open the file; if (theFileIsOpen) { determine its size; if (gotTheFileLength) { allocate that much memory; } else { return error("fileLengthError"); } if (gotEnoughMemory) { read the file into memory; if (readFailed) { return error("readError"); } ... } else { return error("memoryError"); } } else { return error("fileOpenError") } }

可见当 if else 很多时影响可读性,此时可以使用 try catch 进行优化语法:
func readFile: { try { open the file; determine its size; allocate that much memory; read the file into memory; close the file; } catch (fileOpenFailed) { doSomething; } catch (sizeDeterminationFailed) { doSomething; } catch (memoryAllocationFailed) { doSomething; } catch (readFailed) { doSomething; } catch (fileCloseFailed) { doSomething; } }

Exceptions and the Call Stack 假设某main()中方法调用是
GuitarHeroLite.main()-->GuitarString.sample()-->ArrayRingBuffer.peek()
cs61b week5 -- Exceptions, Iterators, Iterables
文章图片

假设peek()方法执行的时候抛出了异常,那么异常就会从栈顶向下追踪(类似于逐步pop栈元素),寻找其他方法里是否会有 catch 异常,如果到达栈底都没找到,程序就会崩溃并且Java会打印出栈的追踪信息
cs61b week5 -- Exceptions, Iterators, Iterables
文章图片

java.lang.RuntimeException in thread “main”: at ArrayRingBuffer.peek:63 at GuitarString.sample:48 at GuitarHeroLite.java:110

Checked Exceptions 我们平时写的代码大多都没有使用 try catch 捕获异常(unchecked exceptions),但是有时候,如果你不进行异常检查,编译器会给出"Must be Caught or Declared to be Thrown" 这样的报错信息
基本的思想是
编译器需要这些异常被catch 或被确定,其被编译器认为是可避免的程序崩溃
比如,当我们 throw new IOException()的话:
public class Eagle { public static void gulgate() { if (today == “Thursday”) { throw new IOException("hi"); } } } public static void main(String[] args) { Eagle.gulgate(); }

以上代码便会报错:
$ javac What.java What.java:2: error: unreported exception IOException; must be caught or declared to be thrown Eagle.gulgate();

但是简单改一下,当我们 throw new RuntimeException():
public class UncheckedExceptionDemo { public static void main(String[] args) { if (today == “Thursday”) { throw new RuntimeException("as a joke"); } } }

【cs61b week5 -- Exceptions, Iterators, Iterables】那么报错就会消失,因为 RuntimeException()是无需检查的异常,而 IOException()是需检查的异常,一些具体的异常区分如图:
cs61b week5 -- Exceptions, Iterators, Iterables
文章图片

对比RuntimeException()与IOException()
cs61b week5 -- Exceptions, Iterators, Iterables
文章图片

关于 RuntimeException(),再具体介绍一下:
cs61b week5 -- Exceptions, Iterators, Iterables
文章图片

对于这些需要检查的异常,我们的办法是
  1. Catch Exception
    使用 try catch 语法捕获异常:
public static void gulgate() { try { if (today == “Thursday”) { throw new IOException("hi"); } } catch (Exception e) { System.out.println("psych!"); } }

  1. 以关键字 throws 在method的header末尾声明异常的:
public static void gulgate() throws IOException { ... throw new IOException("hi"); ... }

相当于告诉编译器 I'm dangerous method
如果一个method 调用一个 dangerous method,那么需要小心该方法本身也会变成 dangerous方法,正如
“He who fights with monsters should look to it that he himself does not become a monster. And when you gaze long into an abyss the abyss also gazes into you.” - Beyond Good and Evil (Nietzsche)
当我们在main()中调用 dangerous method,main()本身也变成了 dangerous method,需要对main()进行修正:
public static void main(String[] args) { Eagle.gulgate(); }

cs61b week5 -- Exceptions, Iterators, Iterables
文章图片

3.Iteration
以前我们有在List中使用过 加强循环去迭代其中的元素( called the “foreach” or “enhanced for” loop):
List friends = new ArrayList(); friends.add(5); friends.add(23); friends.add(42); for (int x : friends) { System.out.println(x); }

本小节的目标是构建一个属于我们自己的加强循环( enhance loop )
先介绍一下Java内置的List interface 的 iterator() method:
public Iterator iterator();

使用如下:
List friends = new ArrayList(); ... Iterator seer = friends.iterator(); while (seer.hasNext()) { System.out.println(seer.next()); }

可见iterator接口包含两个方法;
  • hasNext():检查是否存在下一项
  • next():返回当前项的值并将next指针后移一位
The Iterable Interface 上面我们只是简单介绍了iterator(),其内在的原理我们并不清楚,“就像在墙的另一端,一些僧侣举着火把移动,我们能看到的只是影子”
cs61b week5 -- Exceptions, Iterators, Iterables
文章图片

其原理是
首先编译器检查 Lists 是否有 iterator()方法并返回 Iterator.
How:
List接口 extends Iterable 接口,继承了 Iterable接口的抽象方法 iterator()
(实际上,List extends Collection,而Collection extends Iterable,但这已经足够接近事实了。
另外我还省略了Iterable接口中的一些默认方法)
cs61b week5 -- Exceptions, Iterators, Iterables
文章图片

接着,编译器检查 Iterator 是否有hasNext() 和 next()
How: Iterator接口明确定义了这些抽象的方法
cs61b week5 -- Exceptions, Iterators, Iterables
文章图片

构建我们自己的KeyIterator() 在ArrayMap()中定义内部类 KeyIterator,并声明hasNext()和next():
public class KeyIterator {public boolean hasNext() { return false; }public K next() { return null } }

接下来按照上面所说的 hasNext()与next()的功能完成KeyIterator:
public class KeyIterator {private int Position; public KeyIterator() { Position = 0; }public boolean hasNext() { return Position < size; }public K next() { K value = https://www.it610.com/article/keys[Position]; Position = Position + 1; return value; } }

在IteratorDemo.java中测试:
public class IterationDemo { public static void main(String[] args) { ArrayMap am = new ArrayMap(); am.put("hello", 5); am.put("syrups", 10); am.put("kingdom", 10); ArrayMap.KeyIterator ami = am.new KeyIterator(); while(ami.hasNext()) { System.out.println(ami.next()); } } }

打印结果:
hello syrups kingdom

至此完成了我们的KeyIterator,值得注意的是
ArrayMap.KeyIterator ami = am.new KeyIterator();

以上是演示如何使用嵌套类
如果我们要创造一个非静态的嵌套类,必须有一个特定的实例,如果我们创造的KeyIterator没有与ArrayMap相关联,那将没有意义,本质上KeyIterator的职能是迭代访问ArrayMap的keys[]数组
即便是我们已经成功创建了我们自己的KeyIterator class,但是还是不能实现我们所期待的增强循环:
ArrayMap am = new ArrayMap(); am.put("hello", 5); am.put("syrups", 10); am.put("kingdom", 10); for (String s : am) { System.out.println(s); }

因为Java也不知道如何去获取它,它不知道如何去实例化iterator,因此在这种情况下我们需要做的是,确保我们的class 拥有 iterator()方法
public Iterator iterator() { return new KeyIterator(); }public class KeyIterator implements Iterator { ......}

然而,程序仍然不能运行,因为Java拒绝对数据结构进行 for each循环,除非你还声明了该数据结构实现了可迭代的接口,即在ArrayMap的header继承Iterable:
public class ArrayMapimplements Map61B, Iterable { ....... }

至此实现了用我们自己写的迭代器KeyIterator使用加强循环
总结:
Implement iterable interface to support enhanced for loop.
iterator() method must return an object that implements the Iterator interface.
当然,你也可以使用内置的Iterator:
public Iterator iterator() { List keylist = keys(); return keylist.iterator(); }

以上三行代码等效于使用内部类实现iterator:
public Iterator iterator() { return new KeyIterator(); }public class KeyIterator implements Iterator {private int Position; public KeyIterator() { Position = 0; }public boolean hasNext() { return Position < size; }public K next() { K value = https://www.it610.com/article/keys[Position]; Position = Position + 1; return value; } }

    推荐阅读