12 通过异常处理错误
错误分别编译时错误和运行时错误
编译时错误在编译的时候就会提示
运行时错误则要在允许时才会出现,而异常处理主要是处理运行时错误。
12.1 概念
对于异常,不应该置之不理,而是将其处理或者抛给更高的一级。
12.2 基本异常
比如说除0操作,就会产生一个异常。
当出现异常时,Java会new一个异常对象,然后弹出异常对象的引用。
如果可以处理,则当当前代码处理,如果处理不了则抛给上一级进行处理。
if (t==null)
throw new NullPointerException();//抛出空指针异常
当抛出异常时,如果不处理,则程序就会中止,不再允许
12.2.1 异常参数
所有的异常类都有两个构造器,一个是无参构造器,一个是带字符串的构造器
throw new NullPointerException("异常信息")
异常的根类是Throwable,所有抛出的异常都是Throwable的子类。
12.3 捕获异常
理解异常如何被捕获前,需要先理解监控区域
的概念,在监控区域中是可以会产生异常的。
12.3.1 try块
在try中的代码,如果出现了异常,则会进行处理
try{
// 可能出现异常的代码
}
12.3.2 异常处理程序
在try之后,需要紧跟catch来捕获异常
try{
//可以会出现的异常
}catch(Type1 a){
// 处理第一种异常
}catch(Type2 b){
// 处理第二种异常
}
每一个catch都可以处理不同的异常。
注意!异常只能被catch一次,即当前catch代码结束后,就不会继续寻找下一个catch
12.4 创建自定义异常
通过继承Exceptions,就可以设计自己的异常类
//自定义异常类
class MyException extends Exception{}
public class Test {
public static void fun() throws MyException{//抛出异常,给上一层处理
throw new MyException(); //抛出一个异常类
}
public static void main(String[] args) {
try {
fun();
}catch(MyException e) {
System.out.println("处理异常");
}
}
}
带参数的异常类
//自定义异常类
class MyException extends Exception{
public MyException() {}
public MyException(String msg) { super(msg);} //带参数的构造方法
}
public class Test {
public static void fun() throws MyException{//抛出异常,给上一层处理
throw new MyException("MyExcep"); //抛出一个异常类
}
public static void main(String[] args) {
try {
fun();
}catch(MyException e) {
System.out.println("处理异常");
e.printStackTrace(); //打印具体的信息
}
}
}
12.4.1 异常与记录日志
可以使用java.util.logging工具将记录输出到日志中。
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.*;
//自定义异常类
class MyException extends Exception{
private static Logger logger = Logger.getLogger("记录异常的日志");
public MyException() { //构造方法
StringWriter tra = new StringWriter();//新建一个写入类
printStackTrace(new PrintWriter(tra));//将内容写入tra中
logger.severe(tra.toString());//输出到日志中
}
}
public class Test {
public static void main(String[] args) {
try {
throw new MyException();
}catch(MyException e) {
System.out.println("处理异常");
}
}
}
12.5 异常说明
Java鼓励人们把可能抛出的异常告诉客户端的程序员。
这样方便程序员进行处理
使用throws关键字
void f() throws TooBig,TooSmall{ ... }
还可以将方法声明进行抛出,实际上没有抛出,却可以为以后的异常先占一个位置。
12.6 捕获所有的异常
可以通过其基类Exception,可以捕获所有的异常
try{
//可能出现异常的代码
}catch(Exception e){
System.out.println(" 捕获所有的异常 ");
}
Exception继承于Throwable类,所以在Throwable类中有方法
String getMessage()
String getLocalizedMessage()
String toString()
获取详细的信息
void printStackTrace()
void printStackTrace(PrintStream)
void printStackTrace(java.io.PrintWriter)
12.6.1 轨迹栈
printStackTrace() 方法所提供的信息可以通过getStackTrace()方法来访问,该方法返回一个数组,每个元素表示栈中的每一帧。
class TraceTest{
public void fun() {
try {
throw new Exception();
}catch(Exception e) {
for(StackTraceElement s : e.getStackTrace()) {
System.out.println(s.getMethodName());
}
}
}
public void fun2() {
fun();
}
}
public class Test {
public static void main(String[] args) {
TraceTest t = new TraceTest();
t.fun2();
}
}
// 输出结果
// fun
// fun2
// main
12.6.2 重新抛出异常
有时希望将刚捕获的异常重新抛出。
try{
int i = 1/0;
// ...
}catch(Exception e){
throw e ; //重新抛出异常,给上一级处理
}
12.6.3 异常链
在捕获异常后抛出异常,希望把原始异常信息保存下来,那么就需要用到异常链。
。。。。
12.7 Java标准异常
Throwable对象分为两种,分别是Error和Exception。
Error表示编译时和系统错误,Exception则是可以抛出的异常。
12.7.1 RuntimeException
运行时异常,比如说空指针异常NullPointerException。
在编译的时候不会报错,但是在允许的时候会出现错误
12.8 使用finally进行清理
无论是否发出异常,都要执行finally里的语句,保证内存的安全等。
try{
// 执行的语句
}catch(A a1){
// ...
}catch(B b){
// ...
}finally{
//无论如何,都会执行的语句
}
12.8.1 finally用来做什么
保证内存的释放,但是因为Java有垃圾回收机制,所以内存释放不再是问题。
finally语句还能将资源恢复到初始状态。保证下一次的执行。
12.8.2 在return中使用finally
即使是在finaly前执行return,其仍然会执行finally
public class Test {
public static void g() {
try {
return;
}finally {
System.out.println("执行finally");
}
}
public static void main(String[] args) {
g();
}
}
// 输出结果
// 执行finally
### 12.8.3 缺憾:异常丢失
Java的异常实现也会有瑕疵。finally会导致某些异常被忽略。
主要是由于嵌套try,catch 导致内部的try catch产生的异常无法被捕获。
public class Test {
public static void main(String[] args) {
try {
try {
throw new RuntimeException();//该异常丢失
}finally {
throw new NullPointerException();
}
}catch(Exception e) {
System.out.println("处理异常");
e.printStackTrace();
}
}
}
// 输出
// 处理异常
// java.lang.NullPointerException
// at com.test.Test.main(Test.java:14)
12.9 异常的限制
如果基类有个方法抛出异常, 那么其子类覆盖该方法,抛出的异常也得抛出异常。
//异常类
class SmallException extends Exception{}
interface Person{
void f() throws SmallException;
}
class Chinese implements Person{
@Override
public void f() throws SmallException {
System.out.println("必须抛出其异常");
}
}
12.10 构造器(构造方法)
如果在构造方法中出现异常,将会是一件很麻烦的事情
在构造方法中出现异常,最好的方法是使用try catch将其处理
12.11 异常匹配
当出现异常后,进行从上向下进行匹配,当出现匹配后就不会继续往下匹配。
class FatherExcep extends Exception{}
class SonExcep extends FatherExcep{}
public class Test {
public static void main(String[] args) {
try {
throw new SonExcep();
}catch(FatherExcep f) {
System.out.println("father");
}catch(Exception e) {
System.out.println("e");
}
}
}
// 输出结果
// father
12.12 其他可选方式
开发异常处理的初衷是为了方便程序员处理错误
只有在如何处理的情况下才捕获异常。
12.12.1 历史
异常的起源。。。
12.12.2 观点
“被检查的异常”的好处很明显。当程序开始变大的hi后,就会带来一些微妙的问题。
12.12.3 把异常传递给控制台
可以在main方法中,throws异常,不进行处理
12.12.4 把“被检查的异常”转换为“不检查的异常”
如果不知道这是什么异常时,又想查看异常信息
JDK1.4 的异常链提供了一种新的思路,将“被检查的异常”包装进RuntimeException中。
try{
// 执行的代码
}catch(IDontKnowWhatToDOWithThisCheckedException e){
throw new RunTimeException(e);
}
12.13 异常使用指南
在下列情况下使用异常
(1)在恰当的级别处理问题
(2)解决问题并重新调用产生异常的方法
(3)进行少许修补,然后绕过异常发送的地方继续执行
(4)用别的数据进行计算,以替代的方法返回值
(5)把相同的异常抛到最高层
(6)把不同的异常抛到最高处
(7)中止程序
(8)进行简化
(9)让类库和程序更安全
12.14 总结
异常是Java程序不可分割的一部分,如果不了解如何使用他们,那你只能完成很有限的工作。
异常处理使得更有精力处理你要解决的问题。
13 字符串
字符串操作是计算机程序设计中最常见的行为
13.1 不可变String
String对象是不可变的,你以为String好像修改了,实际上没有修改,而是创建一个新的String。
public class Test {
public static void main(String[] args) {
String a = "hello";
String b = a.toUpperCase();
System.out.println(b);
}
}
String是一个对象,所以a是一个引用。 当a使用toUpperCase()时,那么生成一个新的字符串“HELLO”,然后将地址赋值给b。
13.2 重载 “+” 与 StringBuilder
String对象是不可变的,不可变会带来效率问题。
为String对象重载+操作就是一个例子。
public class Test {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
String a = "hello";
for (int i = 0; i < 50000 ; i++) {
a = a+i;
}
long endTime = System.currentTimeMillis();
System.out.println("结束,消耗时间:"+(endTime-startTime));
}
}
// 输出结果
// 结束,消耗时间:3767
Java虚拟机上自动引入了StringBuilder,为了提高效率,但是效率依旧较低。
使用StringBuilder大大加快了拼接速度
public class Test {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
String a = "hello";
StringBuilder res = new StringBuilder(a);
for (int i = 0; i < 50000 ; i++) {
res.append(i);
}
long endTime = System.currentTimeMillis();
System.out.println("结束,消耗时间:"+(endTime-startTime));
}
}
// 输出结果
// 结束,消耗时间:7
StringBuilder提供丰富而全面的方法,包括insert()、replace()、substring()甚至reverse()。 最常用的还是append()、toString()和 delete()方法
StringBuilder是JavaSE5之后以后的,之前是StringBuffer。
但是StringBuffer是线程安全的。StringBuilder是线程不安全的。
但是StringBuilder的效率更高。
13.3 无意识的递归
Java的每个类都继承自Object类
但是Object有一个toString()方法,使对象能够生成String结果
比如ArrayList.toString()就能够遍历ArrayList中的所有对象。
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
Collections.addAll(list, 1,2,3,4,5);
System.out.println(list.toString());
}
}
// 输出结果
// [1, 2, 3, 4, 5]
13.4 String上的操作
length() 返回String中字符的个数
charAt() 返回String中该索引位置上的char
getChars()、getBytes() 复制char或byte到一个目标数组中
toCharArray() 生成一个char[]
equals(),equalsIgnoreCase() 比较两个字符串是否相同
compareTo() 按字典顺序比较String的内容
contains() String是否包含该内容
startsWith() 是否以什么为起点
endsWith() 是否以什么为后缀
indexOf(),lastIndexOf() 返回该字符串的字符下标
substring() 返回一个新的String
concat() 连接字符串
replace() 替换字符串
toLowerCase() toUpperCase() 改变大小写
trim() 删除两个的空白字符
13.5 格式化输出
有像C语言一样的printf,也有println
13.5.1 printf()
int a = 10;
System.out.printf("%d",a);
13.5.2 System.out.format()
format方法和printf没有啥区别。
13.5.3 Formatter类
在Java章,所有新的格式化功能都由java.util.Formatter类。
将Formatter看作一个翻译器,将内容翻译
public class Test {
public static void main(String[] args) {
StringBuilder s = new StringBuilder();
Formatter f = new Formatter(s); //传入一个StringBuilder对象
String name = "zhangsan";
int age = 18;
f.format("the student %s is %d years old", name,age);
System.out.println(s); //输出StringBuilder
StringBuilder s2 = new StringBuilder();
Formatter f2 = new Formatter(s2);
f2.format("the student %s is %d years old", "lisi",20);
System.out.println(s2);
}
}
// 输出结果
// the student zhangsan is 18 years old
// the student lisi is 20 years old
13.5.4 格式化说明符
使用数字可以将格式化输出进行对齐
f.format("the student %5s is %5d years old", "zhangsan",22);
13.5.5 Formatter转换
%d | 整数 | e | 浮点数(科学计数) |
%c | Unicode字符 | x | 整数(十六进制) |
%b | Boolean值 | h | 散列码(十六进制) |
%s | String | % | 字符 ”%“ |
%f | 浮点数(十进制) |
13.5.6 String.format()
String.format() 返回一个String对象
public class Test {
public static void main(String[] args) {
String res = String.format("the height of %s is %d", "wangwu",23);
System.out.println(res);
}
}
13.6 正则表达式
System.out.println("-1234".matches("-?\\d+"));
// 返回 true
System.out.println("5978".matches("-?\\d+"));
// 返回 true
System.out.println("+912".matches("-?\\d+"));
// 返回 false
System.out.println("+912".matches("(-|\\+)?\\d+"));
// 返回 true
13.7 扫描输入
将数据从文件中读取后,进行切分
public class Test {
public static void main(String[] args) throws IOException {
StringReader stringReader = new StringReader("Hello how are you i am fine");// 读取字符串
BufferedReader bufferReader = new BufferedReader(stringReader);// 放入BufferedReader中
String res = bufferReader.readLine();
String[] str_list = res.split(" ");
for (String str : str_list) {
System.out.println(str);
}
}
}
13.7.1 Scanner定界符
在默认情况下,Scanner根据空白字符对输入进行分词。但可以用正则表达式指定自己所需的定界符。
import java.util.Scanner;
public class Test {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner("12,42,78,99,42");
sc.useDelimiter("\\s*,\\s*");//进行分割
while(sc.hasNextInt()) {
System.out.println(sc.nextInt());
}
}
}
// 输出结果
// 12
// 42
// 78
// 99
// 42
13.7.2 使用正则表达式扫描
import java.util.Scanner;
import java.util.regex.MatchResult;
public class Test {
public static void main(String[] args) {
Scanner sc = new Scanner("192.168.0.1\n192.168.2.3");
String par = "\\d+[.]\\d+[.]\\d+[.]\\d+";//匹配ip地址
while(sc.hasNext(par)) {//判断是否还有ip地址
sc.next(par);
MatchResult rs = sc.match();//获得匹配结果
System.out.println(rs.group());
}
}
}
13.8 StringTokenizer
在引入正则表达式和Scanner之前,分割字符串的唯一方式就是StringTokenizer
但是正则表达式和Scanner更加的简单。
import java.util.StringTokenizer;
public class Test {
public static void main(String[] args) {
String str = "zhangsan,lisi,wangwu";
StringTokenizer st=new StringTokenizer(str,",");// 使用逗号来分割
while(st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
}
}
13.9 总结
Java截至Java SE5意见相当完善了。
为了提高效率,还可以使用StringBuilder。