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_第1张图片
假设我们写了一个读入文件的函数 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_第2张图片
假设peek()方法执行的时候抛出了异常,那么异常就会从栈顶向下追踪(类似于逐步pop栈元素),寻找其他方法里是否会有 catch 异常,如果到达栈底都没找到,程序就会崩溃并且Java会打印出栈的追踪信息
cs61b week5 -- Exceptions, Iterators, Iterables_第3张图片

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()是需检查的异常,一些具体的异常区分如图:
cs61b week5 -- Exceptions, Iterators, Iterables_第4张图片
对比RuntimeException()与IOException()
cs61b week5 -- Exceptions, Iterators, Iterables_第5张图片
关于 RuntimeException(),再具体介绍一下:
cs61b week5 -- Exceptions, Iterators, Iterables_第6张图片
对于这些需要检查的异常,我们的办法是

  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_第7张图片


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

接着,编译器检查 Iterator 是否有hasNext() 和 next()

How: Iterator接口明确定义了这些抽象的方法
cs61b week5 -- Exceptions, Iterators, Iterables_第10张图片

构建我们自己的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 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 = keys[Position];
            Position = Position + 1;
            return value;
        }
    }
    

你可能感兴趣的