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 里面纠正程序错误
优化语法
文章图片
假设我们写了一个读入文件的函数 readFile 那么需要考虑:
- 文件是否存在
- 内存是否足以读入全部字节
- 读入失败怎么样
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");
}
}
}
【cs61b week5 -- Exceptions, Iterators, Iterables】那么报错就会消失,因为 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 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指针后移一位
文章图片
其原理是
首先编译器检查 Lists 是否有 iterator()方法并返回 Iterator
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 = 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;
}
}
推荐阅读
- Java|Java基础——数组
- 人工智能|干货!人体姿态估计与运动预测
- java简介|Java是什么(Java能用来干什么?)
- Java|规范的打印日志
- Linux|109 个实用 shell 脚本
- 程序员|【高级Java架构师系统学习】毕业一年萌新的Java大厂面经,最新整理
- Spring注解驱动第十讲--@Autowired使用
- SqlServer|sql server的UPDLOCK、HOLDLOCK试验
- jvm|【JVM】JVM08(java内存模型解析[JMM])
- 技术|为参加2021年蓝桥杯Java软件开发大学B组细心整理常见基础知识、搜索和常用算法解析例题(持续更新...)