1.抛出异常
本节课我们使用上一节创建的ArrayMap进行讲解,假设你在main()中get()一个并不存在的key,如:
public static void main(String[] args) {
ArrayMap<String, Integer> am = new ArrayMap<String, Integer>();
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 我们可以抛出自定义的异常,具体使用格式为:
throw new ExceptionObject(parameter1, ...)
举例:
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 里面纠正程序错误
优化语法
假设我们写了一个读入文件的函数 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()
假设peek()方法执行的时候抛出了异常,那么异常就会从栈顶向下追踪(类似于逐步pop栈元素),寻找其他方法里是否会有 catch 异常,如果到达栈底都没找到,程序就会崩溃并且Java会打印出栈的追踪信息
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"); }
}
}
那么报错就会消失,因为 RuntimeException()是无需检查的异常,而 IOException()是需检查的异常,一些具体的异常区分如图:
对比RuntimeException()与IOException()
关于 RuntimeException(),再具体介绍一下:
对于这些需要检查的异常,我们的办法是
- Catch Exception
使用 try catch 语法捕获异常:
public static void gulgate() {
try {
if (today == “Thursday”) {
throw new IOException("hi"); }
} catch (Exception e) {
System.out.println("psych!");
}
}
- 以关键字 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();
}
3.Iteration
以前我们有在List中使用过 加强循环去迭代其中的元素( called the “foreach” or “enhanced for” loop):
List<Integer> friends =
new ArrayList<Integer>();
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<E> iterator();
因为iterator()的返回值是Iterator<E>类型,所以我们定义一个Iterator<Integer>类型的seer去接收:
List<Integer> friends =
new ArrayList<Integer>();
...
Iterator<Integer> seer
= friends.iterator();
while (seer.hasNext()) {
System.out.println(seer.next());
}
可见iterator接口包含两个方法:
- hasNext():检查是否存在下一项
- next():返回当前项的值并将next指针后移一位
The Secret of the Enhanced For Loop
如上图所示,左边的代码是增强循环,其原理等效于右边的迭代器版本,也即是说增强循环的源码即等同于图中右边的代码
- 首先编译器检查 Lists 是否有 iterator()方法并返回 Iterator<Integer>.
How:
List接口 extends Iterable 接口,继承了 Iterable接口的抽象方法 iterator()
(实际上,List extends Collection,而Collection extends Iterable,但这已经足够接近事实了。另外我还省略了Iterable接口中的一些默认方法) 接着,编译器检查 Iterator 是否有hasNext() 和 next()
How: Iterator接口明确定义了这些抽象的方法
因此,为了实现增强循环,即需要迭代器满足上述两个步骤(这里是fall2020的slides):
构建我们自己的KeyIterator()
在实现我们自己的增强循环之前,让我们先简单地构建属于我们自己的迭代器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 = keys[Position];
Position = Position + 1;
return value;
}
}
在IteratorDemo.java中测试:
public class IterationDemo {
public static void main(String[] args) {
ArrayMap<String, Integer> am = new ArrayMap<String, Integer>();
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[]数组。
接下来准备实现增强循环
ArrayMap<String, Integer> am = new ArrayMap<String, Integer>();
for (String s : am) {
System.out.println(s);
}
因为我们刚才说了,增强循环的源码即
按照两个步骤:
- 需要将KeyIterator class封装成一个Iterator方法,该方法的返回类型是Iterator<K>
public Iterator<K> iterator() {
return new KeyIterator();
}
- Iterator()方法返回的Iterator<K>类型的Object应该拥有hasNext()和next(),所以需要声明我们的KeyIterator 继承了 Iterator<K> interface,建立"is-a"关系,即KeyIterator是Iterator,且具备hasNext()和next()方法
public class KeyIterator implements Iterator<K> {
......
}
然而,程序仍然不能运行,
因为Java并不知道我们的ArrayMap class里面有iterator()方法,你可能会疑惑,这么大的方法不就明摆着在那里吗?怎么会不知道。
要知道,这是我们人看见的,编译器不是人(= =)
为了让Java知道我们的ArrayMap里面实现了iterator()方法,让ArrayMap 继承 iterable<T> 接口,建立"is-a"关系,即ArrayMap是iterable,承诺ArrayMap实现了iterable里面的iterator方法
public class ArrayMap<K, V>implements Map61B<K, V>, Iterable<K> {
....... }
至此实现了用我们自己写的迭代器KeyIterator使用加强循环
总结:
- Iterable 接口拥有Iterator()方法 返回一个 Iterator<T> Object
- Iterator<T> 接口 拥有 hasnext()和next()方法,也即实例化的Iterator<T> Object也拥有这两个方法
To support the enhanced for loop:
- Add an iterator() method to your class that returns an Iterator<T>.
- The Iterator<T> returned should have a useful hasNext() and next() method.
- Add implements Iterable<T> to the line defining your class.
当然,你也可以使用List内置的Iterator:
public Iterator<K> iterator() {
List<K> keylist = keys();
return keylist.iterator();
}
以上三行代码等效于使用内部类实现iterator:
public Iterator<K> iterator() {
return new KeyIterator();
}
public class KeyIterator implements Iterator<K> {
private int Position;
public KeyIterator() {
Position = 0;
}
public boolean hasNext() {
return Position < size;
}
public K next() {
K value = keys[Position];
Position = Position + 1;
return value;
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。