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 ExceptionDemoException 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 reasonactually 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.javaWhat.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 Iterable Interface
下面咱们只是简略介绍了iterator(),其内在的原理咱们并不分明,“就像在墙的另一端,一些僧侣举着火把挪动,咱们能看到的只是影子”
其原理是
首先编译器查看 Lists 是否有 iterator()办法并返回 Iterator<Integer>.
How:
List接口 extends Iterable 接口,继承了 Iterable接口的形象办法 iterator()
(实际上,List extends Collection,而Collection extends Iterable,但这曾经足够靠近事实了。
另外我还省略了Iterable接口中的一些默认办法)
接着,编译器查看 Iterator 是否有hasNext() 和 next()
How: Iterator接口明确定义了这些形象的办法
构建咱们本人的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()); } }}
打印后果:
hellosyrupskingdom
至此实现了咱们的KeyIterator,值得注意的是
ArrayMap.KeyIterator ami = am.new KeyIterator();
以上是演示如何应用嵌套类
如果咱们要发明一个非动态的嵌套类,必须有一个特定的实例,如果咱们发明的KeyIterator没有与ArrayMap相关联,那将没有意义,实质上KeyIterator的职能是迭代拜访ArrayMap的keys[]数组
即使是咱们曾经胜利创立了咱们本人的KeyIterator class,然而还是不能实现咱们所期待的加强循环:
ArrayMap<String, Integer> am = new ArrayMap<String, Integer>(); 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<K> iterator() { return new KeyIterator(); } public class KeyIterator implements Iterator<K> { ......}
然而,程序依然不能运行,因为Java回绝对数据结构进行 for each循环,除非你还申明了该数据结构实现了可迭代的接口,即在ArrayMap的header继承Iterable<K>:
public class ArrayMap<K, V>implements Map61B<K, V>, Iterable<K> { ....... }
至此实现了用咱们本人写的迭代器KeyIterator应用增强循环
总结:
Implement iterable interface to support enhanced for loop.
iterator() method must return an object that implements the Iterator interface.
当然,你也能够应用内置的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; } }