1,异常处理
异常是导致程序中断运行的一种指令流,如果不对异常进行正确的处理,则可能导致程序的中断执行,造成不必要的损失,所以在程序的设计中要考虑各种异常的发生,并正确地做好相应的处理,这样才能保证程序的正常地执行。
为什么需要异常处理:
- 在没有异常处理的语言中如果要回避异常,就必须使用大量的判断语句,配合所想到的错误状况来捕捉程序中所有可能发生的错误。但是这样也未必能捕捉到所有的错误,而且这样做势必导致程序运行效率的降低。
- Java的异常处理机制恰好改进了这一点。它具有易于使用、可自定义异常、处理抛出的异常同时又不会降低程序运行的速度等优点。因而在Java程序时,应充分利用Java的异常处理机制,以增进程序的稳定性及效率。
- JVM的垃圾回收机制不会回收类似于数据库连接、网络连接、磁盘文件等,因为它只负责回收堆内存中分配出来的内存,至于程序中打开的物力资源,垃圾回收机制无能为力,就需要异常处理。
- 最直观的解释:用户遇到BUG更希望是人性化的展示,而不是直接展示一堆乱码或者BUG日志。
异常机制:
- 异常处理:在Java中,处理异常的语句由try、catch、finally三部分组成。其中,try块用于包裹业务代码,catch块 用于捕获并处理某个类型的异常,finally块则用于回收资源。当业务代码发生异常时,系统会创建一个 异常对象,然后由JVM寻找可以处理这个异常的catch块,并将异常对象交给这个catch块处理。若业务代码打开了某项资源,则可以在finally块中关闭这项资源,因为无论是否发生异常,finally块一定会执行。
- 抛出异常:当程序出现错误时,系统会自动抛出异常。除此以外,Java也允许程序主动抛出异常。当业务代码中, 判断某项错误的条件成立时,可以使用throw关键字向外抛出异常。在这种情况下,如果当前方法不知 道该如何处理这个异常,可以在方法签名上通过throws关键字声明抛出异常,则该异常将交给JVM处理。
- 异常跟踪栈:程序运行时,经常会发生一系列方法调用,从而形成方法调用栈。异常机制会导致异常在这些方法之间 传播,而异常传播的顺序与方法的调用相反。异常从发生异常的方法向外传播,首先传给该方法的调用者,再传给上层调用者,以此类推。最终会传到main方法,若依然没有得到处理,则JVM会终止程序,并打印异常跟踪栈的信息。
1.1,在程序中使用异常处理
try{ //有可能出现异常的语句 }catch(异常类 异常类对象){ //异常处理语句 }finally{ 一定会运行的程序代码 }
异常处理的步骤:
(1)产生异常:一旦产生异常,则首先会产生一个异常类的实例化对象。
(2)捕获异常:将业务代码包裹在try块内部,当业务代码中发生任何异常时,系统都会为此异常创建一个异常对 象。创建异常对象之后,JVM会在try块之后寻找可以处理它的catch块,并将异常对象交给这个 catch块处理。
(3)处理异常:在catch块中处理异常时,应该先记录日志,便于以后追溯这个异常。然后根据异常的类型、结合 当前的业务情况,进行相应的处理。比如,给变量赋予一个默认值、直接返回空值、向外抛出一个 新的业务异常交给调用者处理,等等。
(4)回收资源:如果业务代码打开了某个资源,比如数据库连接、网络连接、磁盘文件等,则需要在这段业务代码 执行完毕后关闭这项资源。并且,无论是否发生异常,都要尝试关闭这项资源。将关闭资源的代码 写在finally块内,可以满足这种需求,即无论是否发生异常,finally块内的代码总会被执行。
【问题】如果在try语句之中产生了异常,则程序会自动跳转到catch语句中找到匹配的异常类型进行相应的处理。最后不管程序是否产生异常,则肯定都会执行到finally语句,finally语句就作为异常的统一出口。finally块可以省略的,在catch块运行结束后,程序跳到try-catch块之后继续执行。要尽可能的不要出现throw或return这样的语句。另外return和throw不能同时出现在一个语句块。
public class HelloWord { public static void main(String args[]) { System.out.println(print()); } public static int print() { try { System.out.println(1 / 0); } catch (Exception e) { throw e; } finally { System.out.println("计算完成了"); } return 10; //不会执行 } } ============================================= 计算完成了 Exception in thread "main" java.lang.ArithmeticException: / by zero at Package2.HelloWord.print(HelloWord.java:10) at Package2.HelloWord.main(HelloWord.java:5)
1.2,finally
【问题】finally是无条件执行的吗?
【答案】不管try块中的代码是否出现异常,也不管哪一个catch块被执行,甚至在try块或catch块中执行了return语句,finally块总会被执行。
【注意】如果在try块或catch块中使用 System.exit(1/0); 来退出虚拟机,则finally块将失去执行的机会。但是我们在实际的开发中,从来都不会这样做,所以尽管存在这种导致finally块无法执行的可能,也只是一 种可能而已。
public class Main { public static void main(String[] args) { try { System.exit(0); }finally { System.out.println("finally执行了"); } } } ============================ 无输出
【问题】在finally中return会发生什么?
【答案】在通常情况下,不要在finally块中使用return、throw等导致方法终止的语句,一旦在finally块中使用了 return、throw语句,将会导致try块、catch块中的return、throw语句失效。
【解析】当Java程序执行try块、catch块时遇到了return或throw语句,这两个语句都会导致该方法立即结束,但是系统执行这两个语句并不会结束该方法,而是去寻找该异常处理流程中是否包含finally块,如果没有finally块,程序立即执行return或throw语句,方法终止;如果有finally块,系统立即开始执行finally 块。只有当finally块执行完成后,系统才会再次跳回来执行try块、catch块里的return或throw语句;如果finally块里也使用了return或throw等导致方法终止的语句,finally块已经终止了方法,系统将不会跳回去执行try块、catch块里的任何代码。
public class Main { public static int test(){ int count = 5; try{ return count++; }finally { System.out.println("finally正在执行"); return ++count; } } public static void main(String[] args) { System.out.println(test()); } } ========================== finally正在执行 7
count++成功执行,但是return ++count没有成功执行。
public class Main { public static int test(){ int count = 5; try{ return count/0; }finally { System.out.println("finally正在执行"); return count; } } public static void main(String[] args) { System.out.println(test()); } } ================================= finally正在执行 5
final、finally、finalize 的区别:
- final:修饰符(关键字)有三种用法:如果一个类被声明为 final,意味着它不能再派生出新的子类,即不 能被继承,因此它和 abstract 是反义词。将变量声明为 final,可以保证它们在使用中不被改变,被声明 为 final 的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为 final 的方法也 同样只能使用,不能在子类中被重写。
- finally:通常放在 try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异 常,这里的代码只要 JVM 不关闭都能执行,可以将释放外部资源的代码写在 finally 块中。
- finalize:Object 类中定义的方法,Java 中允许使用 finalize()方法在垃圾收集器将对象从内存中清 除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写 finalize()方法可 以整理系统资源或者执行其他清理工作。
1.3,异常类的继承结构
整个Java的异常结构中,实际上有两个常用的异常类,分别时Exception和Error,这两个类全都是Throwable的子类。
(1)Exception:它被分为两大类,分别是Checked异常和Runtime异常。
- RuntimeException:是那些可能在Java虚拟机正常运行期间抛出的异常的超类。如果出现RuntimeException,那么一 定是程序员的错误。如NullPointerException、ClassCastException。
- CheckedException:一般是外部错误,这种异常都发生在编译阶段,Java 编译器会强制程序去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行 try-catch。如 I/O 错误导致的 IOException、SQLException。
(2)Error:一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败等,这种错误无法恢复或不可能捕获,将导致应用程序中断。通常应用程序无法处理这些错误,因此应用程序不应该 试图使用catch块来捕获Error对象。在定义方法时,也无须在其throws子句中声明该方法可能抛出 Error及其任何子类。
【问题】一般情况下将Exception和Error统称为异常,而算数异常、数字格式化异常等都属于Exception的子类。为何不直接使用Throwable类替代常用的Exception来捕获异常?
【答案】不建议,最大只能捕获Exception。首先使用Exception捕获异常,在代码中没有任何问题,因为Throwable捕获的范围是最大的。但一般开发中是不会直接使用Throwable进行捕获的,对于Throwable来说又Exception、Error两个子类,Error类本身不需要程序处理,而程序中需要处理的只是Exception,所以没必要使用Throwable。另外,对于一个程序来说,如果又多个异常最好分别捕捉,而不要直接使用Exception捕获全部异常。
【问题】主线程可以捕获到子线程的异常吗?
【答案】主线程默认情况下是无法捕获子线程抛出的异常的。因为每个线程都有自己的执行上下文和栈空间,所以子线程抛出的异常只会在子线程自己的上下文中被捕获或者抛出。如果子线程抛出未被捕获的异常,程序会终止并打印异常信息,但是这并不会被主线程感知到。但是,可以通过一些手段使得主线程可以感知到子线程的异常,比如:
在子线程中使用try...except捕获异常,并将异常信息通过队列或者其他方式传递给主线程。
使用threading模块的Thread.join方法,在主线程中等待子线程的结束,并在join方法的try...except块中捕获子线程抛出的异常。
自定义异常:
public class HelloWord { public static void main(String args[]) { try { throw new ysyException("我丢出了异常"); }catch (Exception e){ System.out.println(e); } } } class ysyException extends Exception{ public ysyException(String msg){ super(msg); //调用父类的构造方法 } } ========================================= Package2.ysyException: 我丢出了异常
1.4,throws与throw
throws关键字:在定义一个方法时可以使用throws关键字声明,使用throws声明的方法表示此方法不处理异常,而交给方法的调用者处理。不要在主方法中使用throws,否则程序出现问题会将问题交给JVM处理,将导致程序中断。
public class HelloWord { public static void main(String args[]) { try { print(); }catch (Exception e){ System.out.println("我出现了问题"); } } public static void print() throws Exception{ System.out.println(1/0); } } =============================================== 我出现了问题
即便出现了错误,也不会在该方法中报错,而是上报给上一级(这里是main())处理。
throw关键字:与throws关键字不同,可以直接使用throw关键字抛出一个异常,抛出时直接抛出异常的实例化对象即可。
public class HelloWord { public static void main(String args[]) { try { throw new Exception("我抛出了一个异常!!!"); }catch (Exception e){ e.printStackTrace(); System.out.println("我接到了你的异常!!!"); } } } ======================================================= java.lang.Exception: 我抛出了一个异常!!! at Package2.HelloWord.main(HelloWord.java:6) 我接到了你的异常!!!
throw和thros的区别:
- 位置不同:throws用在函数上,后面跟的是异常类,可以跟多个;而 throw 用在函数内,后面跟的是异常对象。
- 功能不同:
- throws 用来声明异常,让调用者只知道该功能可能出现的问题,可以给出预先的处理方 式;throw 抛出具体的问题对象,执行到 throw,功能就已经结束了,跳转到调用者,并 将具体的问题对象抛给调用者。也就是说 throw 语句独立存在时,下面不要定义其他语 句,因为执行不到。
- throws 表示出现异常的一种可能性,并不一定会发生这些异常;throw 则是抛出了异常, 执行 throw 则一定抛出了某种异常对象。
- 两者都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数去处理异 常,真正的处理异常由函数的上层调用处理。
1.5,Exception类与RuntimeException类
public static void main(String args[]) { String str = "123"; int temp = Integer.parseInt(str); System.out.println(temp*temp); }
public static int parseInt(String s) throws NumberFormatException { return parseInt(s,10); }
查看parseInt的源码,为啥throws了异常,而不需要上层try...catch?通过查看jdk,可以知道下面的继承关系:Exception>RuntimeException>IllegalArgumentException>NumberFormatException
- Exception在程序中必须使用try...catch进行处理;
- RuntimeException可以不用try...catch进行处理,但是如果有异常产生,则将由JVM进行处理。所以最好也使用异常处理机制。
1.6,断言
断言:就是肯定某一结果的返回值是正确的(不会发生任何事情),如果最终结果是错误的,则通过断言检查肯定会提示错误信息。
断言的使用:(1)虽然断言返回的是boolean值,但是并不能将其作为条件判断语句。(2)断言虽然有检查运行结果的作用,但是在开发中不提倡使用断言。
public class HelloWord { public static void main(String args[]) { int x[] = {1,2,3}; assert x.length==0:"数组长度不为0"; } }
程序不会出现了任何结果,那是因为Java在设计此关键字的时候考虑到系统的应用,防止某些用户使用assert作为关键字,所以程序正常运行时不会起任何作用,如果想让断言起作用,则在Java运行时添加如下参数:-enableassertions或-ea
Exception in thread "main" java.lang.AssertionError: 数组长度不为0 at Package2.HelloWord.main(HelloWord.java:6)
2,包
包:实际上就是一个文件夹,在需要定义多个类或接口时,为了避免名称重复而采用的一种措施。
格式:package 包名称.子包名称
package Package2;
2.1,导入:import
如果几个类存放在不同的包中,则使用其他包的类就必须通过import语句导入。
import 包名称.子包名称.类名称; import 包名称.子包名称.*;
上面两种并没有太大的区别,一般习惯于后者。两者的性能一样,不存在任何性能问题。因为使用“*”时程序也是自动加载所需要的类,而不需要的类根本是不会被加载进来的。
另外,如果同时导入两个包同名类,在使用时就必须明确地写出完整的“包.类名称”。
2.2,静态导入:import static
如果一个类中的所有方法都是使用static声明的静态方法,则在导入时就可以直接使用import static的方式导入。
格式:import static 包.类.*;
package Package2; public class HelloJava { public static int add(int a,int b){ return a+b; } } --------------------------------------- package Package2; import static Package2.HelloJava.*; public class HelloWord { public static void main(String args[]) { System.out.println(add(1,2)); } } ======================================= 3
2.3,系统常见包
包名称 | 作用 |
java.lang | 此包作为基本的包,String、Integer等常用的类都保存在此包中 |
java.lang.reflect | 此包为反射机制的包,是java.lang的子包 |
java.util | 此包为工具包,一些常见的类库、日期操作等都在此包中 |
java.text | 提供一些国际显示的处理类库 |
java.sql | 数据库操作包,提供各种数据库操作的类和接口 |
java.net | 完成网络编程 |
java.io | 输入、输出处理 |
java.awt | 包含了各种构成抽象窗口工具集的多个类,这些类被用来管理和控制GUI |
java.swing | 用于建立图形用户界面,是java.awt的轻量级组件 |
3,访问控制权限
- private:private属于私有访问权限,可以用在属性的定义、方法的声明上,一旦使用了private关键字声明,则只能在本类中进行访问。
- default(默认):如果一个类中的属性或方法没有使用任何访问权限声明,则就是默认的访问权限,默认的访问权限可以被本包中的其他类所访问,但是不能被其他包的类所访问。
- protected:protected属于受保护的访问权限。一个类中的成员如果使用了protected访问权限,则只能被本包及不同包的子类所访问。
- public:public属于公共访问权限。如果一个类中的成员使用了public访问权限,就可以在所有的类中被访问,不管是否在同一个包中。
范围 private default protected public 同一类 √ √ √ √ 同一包中的类 √ √ √ 不同包的子类 √ √ 其他包中的类 √
Java命名规则:
(1)类:所有单词的首字母大写,如TestJava。
(2)方法:第一个单词首字母小写,之后每个单词的首字母大写,如getInfo()。
(3)属性:第一个单词首字母小写,之后每个单词的首字母大写,如studentName。
(4)包:所有单词的字母小写,如org.ysy.demo。
(5)常量:所有单词的字母大写,如FLAG。