学习笔记3:JavaSE & API(异常处理)

发布时间 2023-12-01 11:55:54作者: 执着的怪味豆

1、Java中的错误

(1)所有错误的超类:Throwable

(2)Throwable的子类:

  • 子类:Error,系统级别错误、运行环境错误。比如,虚拟机内存溢出。
  • 子类:Exception,异常情况,通常是逻辑问题导致的程序级别错误,可在运行期间被解决。比如,空指针,下标越界。

(3)通常,程序中处理的异常都是Exception。

(4)错误输出:System.err.println 输出错误信息到控制台,与System.out是并发的。

(5)错误传递机制:JVM发现异常会实例化一个对应的异常实例,并将程序执行过程设置进入实例,然后把异常抛出。抛出后,层层向上去寻找可以处理异常的方法,如果一直无人处理,抛到JVM,JVM直接杀掉程序。 

2、try-catch 语句块

(1)场景:明知道会发生,却无法避免的情况,才会catch异常

(2)语法特点:

  • 语句块:try、catch、final
  • catch可以定义多个
  • catch可以合并多个异常一起处理:比如,catch(NullPointerException|StringIndexOutOfBoundsException e)
  • catch可以捕捉异常的超类来简单处理:比如,catch(Exception e){ System.out.println("就是出了个错"); }
  • printStackTrace():Throwable的方法,向控制台输出当前异常的错误信息,在catch里,打印出错误信息。

(3)finally块:

  • 定义在异常处理机制中的最后一块。可以在try之后,或者最后一个catch之后。
  • 只要程序执行到了try语句块中,无论try语句块中的代码是否出现异常,最终finally都必定执行。
  • 通常用来做释放资源这类操作,比如关闭Socket,关闭文件流。

(4)示例代码

        //IO操作异常处理示例
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("fos.dat");
            fos.write(1);
            fos = null;
        } catch (IOException e) {
            e.printStackTrace();//向控制台输出当前异常的错误信息
        } finally {
            try {
                if (fos != null) {
                    fos.close();//close可能也会有异常出现
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
try catch

(5)自动关闭特性(IO操作):

  • JDK7之后,java提供了一个新的特性,旨在IO操作中可以更简洁的使用异常处理机制完成最后的close操作。
  • 语法:try后增加(),里面可以写上流的定义,编译器会自动补上final里的close方法。只有实现了AutoCloseable的类,才可以在()里面定义。一般的流,都实现了这个接口。 
  • 该新特性,是编译器认可,非JVM认可。编译后,依然会的class文件依然会转换为标准格式。
  • 示例代码
        try(
                FileOutputStream fos = new FileOutputStream("fos.dat");
        ){
            fos.write(1);
        } catch (IOException e) {
            e.printStackTrace();
        }
自动关闭特性

3、throw关键字

(1)用途:对外主动抛出一个异常
(2)抛出的场景:
  • 当程序遇到一个满足语法,但是不满足业务要求时,可以抛出一个异常告知调用者。
  • 程序执行遇到一个异常,但是该异常不应当在当前代码片段被解决时,可以抛出给调用者。

4、throws关键字

(1)说明:用throw抛出一个非RuntimeException的异常时,要在该方法上使用throws声明这个异常的抛出。此时调用该方法的代码就必须处理这个异常。
(2)注意:
  • 除了throw new RuntimeException(),抛出其他异常,必须在方法名里声明throws
  • 如果方法名里有throws,则调用的时候必须写异常处理的逻辑,不然报错,处理的时候有两种方法:try-catch 、使用throws继续将异常抛出

(3)含有throws的方法被子类重写时的规则,可以进行如下:

  • 不抛出任何异常
  • 抛出全部异常
  • 抛出部分异常
  • 抛出超类方法里异常的子类异常。

(4)含有throws的方法被子类重写时的规则,不可以进行如下:

  • 抛出超类方法里额外的异常
  • 抛出超类方法里异常的超类异常

5、异常的分类

(1)Exception分类:
  • 可检测异常:可检测异常经编译器验证,对于声明抛出异常的任何方法,编译器将强制执行处理或声明规则,不捕捉这个异常,编译器就通不过,不允许编译。
  • 非检测异常:非检测异常不遵循处理或者声明规则。在产生此类异常时,不一定非要采取任何适当操作,编译器不会检查是否已经解决了这样一个异

(2)RuntimeException :

  • 非检测异常,因为普通JVM操作引起的运行时异常随时可能发生,此类异常一般是由特定操作引发。但这些操作在java应用程序中会频繁出现。因此它们不受编译器检查与处理或声明规则的限制。一般也是bug导致,可以逻辑上解决。
  • 常见的子类:
    • IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数
    • NullPointerException:当应用程序试图在需要对象的地方使用 null 时,抛出该异常
    • ArrayIndexOutOfBoundsException:当使用的数组下标超出数组允许范围时,抛出该异常
    • ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常
    • NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。

6、异常中常用方法

(1)e.printStackTrace():控制台输出异常

(2)e.getMessage():获取错误消息,提示给用户或者记录日志

7、自定义异常

(1)场景:自定义异常通常用来定义、说明业务上的错误。

(2)注意:

  • 定义的类名要做到见名知义
  • 必须是Exception的子类
  • 必须提供Exception所定义的所有构造方法

8、Java常见异常 

(1)Java.io.NullPointerException

  空指针异常,该异常出现在我们操作某个对象的属性或方法时,如果该对象是null时引发。

String str = null;
str.length();//空指针异常

  上述代码中引用类型变量str的值为null,此时不能通过它调用字符串的方法或引用属性,否则就会引发空指针异常。

(2)java.lang.NumberFormatException: For input string: "xxxxx"

  数字格式异常,该异常通常出现在我们使用包装类将一个字符串解析为对应的基本类型时引发。

String line = "123.123";//小数不能转换为整数!
int d = Integer.parseInt(line);//抛出异常NumberFormatException
System.out.println(d);

  上述代码中由于line的字符串内容是"123.123".而这个数字是不能通过包装类Integer解析为一个整数因此出现该异常。注:非数字的字符出在解析时也会出现该异常。

(3)java.lang.StringIndexOutOfBoundsException

  字符串下标越界异常。该异常通常出现在String对应的方法中,当我们指定的下标小于0或者大于等于字符串的长度时会抛出该异常。

String str = "thinking in java";
char c = str.charAt(20);//出现异常
System.out.println(c);

(4)java.io.InvalidClassException

  无效的类异常,该异常出现在使用java.io.ObjectInputStream在进行对象反序列化时在readObject()方法中抛出。这通常是因为反序列化的对象版本号与该对象所属类现有的版本号不一致导致的。

  可以通过在类上使用常量:

static final long serialVersionUID = 1L;

  来固定版本号,这样序列化的对象就可以进行反序列化了。

  JAVA建议我们实现Serializable接口的类主动定义序列化版本号,若不定义编译器会在编译时 根据当前类结构生成版本号,但弊端是只要这个类内容发生了改变,那么再次编译时版本号就会改变,直接的后果就是之前序列化的对象都无法再进行反序列化.

如果自行定义版本号,那么可以在改变类内容的同时不改变版本号,这样一来,反序列化以前的 对象时对象输入流会采取兼容模式,即:当前类的属性在反序列化的对象中还存在的则直接还原,不存在的就是用该属性的默认值

 出现该异常的解决办法:

  1. 首先使用上述常量固定版本号
  2. 重新序列化对象(将对象通过ObjectOutputStream重新序列化并写出)
  3. 再进行反序列化即可

 需要注意,之前没有定义序列化版本号时序列化后的对象都无法再反序列化回来,所以若写入了文件,可将之前的那些文件都删除,避免读取即可。

(5)java.io.NotSerializableException

  不能序列化异常,该异常通常出现在我们使用java.io.ObjectOutputStream进行对象序列化(调用writeObject)时。原因时序列化的对象所属的类没有实现java.io.Serializable接口导致

(6)java.io.UnsupportedEncodingException

  不支持的字符集异常,该异常通常出现在使用字符串形式指定字符集名字时,由于字符集名字拼写错误导致。例如

PrintWriter pw = new PrintWriter("pw.txt", "UFT-8");

  上述代码中,字符集拼写成"UFT-8"就是拼写错误。

(7)java.io.FileNotFoundException

  文件没有找到异常,该异常通常出现在我们使用文件输入流读取指定路径对应的文件时出现

FileInputStream fis = new FileInputStream("f1os.dat");

  上述代码如果指定的文件f1os.dat文件不在当前目录下,就会引发该异常:

  java.io.FileNotFoundException: f1os.dat (系统找不到指定的文件。)

  还经常出现在文件输出流写出文件时,指定的路径无法将该文件创建出来时出现

FileOutputStream fos = new FileOutputStream("./a/fos.dat");

  上述代码中,如果当前目录下没有a目录,那么就无法在该目录下自动创建文件fos.dat,此时也会引发这个异常。其他API上出现该异常通常也是上述类似的原因导致的。

(8)java.net.ConnectException: Connection refused: connect

  连接异常,连接被拒绝了.这通常是客户端在使用Socket与远端计算机建立连接时由于指定的地址或端口无效导致无法连接服务端引起的.

System.out.println("正在连接服务端...");
Socket socket = new Socket("localhost",8088);//这里可能引发异常
System.out.println("与服务端建立连接!");

  解决办法:

  • 检查客户端实例化Socket时指定的地址和端口是否正常
  • 客户端连接前,服务端是否已经启动了

(9)java.net.BindException: Address already in use

  绑定异常,该异常通常是在创建ServerSocket时指定的服务端口已经被系统其他程序占用导致的.

System.out.println("正在启动服务端...");
ServerSocket serverSocket = new ServerSocket(8088);//这里可能引发异常
System.out.println("服务端启动完毕");

  解决办法:

  • 有可能是重复启动了服务端导致的,先将之前启动的服务端关闭
  • 找到该端口被占用的程序,将其进程结束
  • 重新指定一个新的服务端口在重新启动服务端

(10)java.net.SocketException: Connection reset

  套接字异常,链接重置。这个异常通常出现在Socket进行的TCP链接时,由于远端计算机异常断开(在没有调用socket.close()的之前直接结束了程序)导致的。

解决办法:

  • 无论是客户端还是服务端当希望与另一端断开连接时,应当调用socket.close()方法,此时会进行TCP的挥手断开动作。
  • 这个异常是无法完全避免的,因为无法保证程序在没有调用socket.close()前不被强制杀死。

(11)java.lang.InterruptedException

  中断异常.这个异常通常在一个线程调用了会产生阻塞的方法处于阻塞的过程中,此时该线程的interrupt()方法被调用.那么阻塞方法会立即抛出中断异常并停止线程的阻塞使其继续运行.

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
}

  如果线程t1调用Thread.sleep(1000)处于阻塞的过程中,其他线程调用了t1线程的inerrupt()方法,那么t1调用的sleep()方法就会立即抛出中断异常InterruptedException并停止阻塞.

(12)java.util.NoSuchElementException

  没有这个元素的异常.该异常通常发生在使用迭代器Iterator遍历集合元素时由于没有先通过hasNext()方法判断存在下一个元素而贸然通过next()获取下一个元素时产生(当集合所有元素都经过迭代器遍历一遍后还使用next获取).

while(it.hasNext()){
            String str = (String)it.next();
            //这里就可能产生NoSuchException异常
            System.out.println(it.next());
        }

  上述代码中循环遍历时,每次调用hasNext()确定存在下一个元素时,循环里面连续调用过两次next()方法,这意味着第二次调用next()方法时并没有判断是否还存在.所以在最后会出现异常.

  解决办法:

  保证每次调用next()方法前都确定hasNext()为true才进行即可.

(13)java.util.ConcurrentModificationException

  并发修改异常.这个异常也经常出现在使用迭代器遍历集合时产生.

  当我们使用一个迭代器遍历集合的过程中,通过集合的方法增删元素时,迭代器会抛出该异常.

while(it.hasNext()){
    //出现ConcurrentModificationException
    String str = (String)it.next();
    if("#".equals(str)){
        c.remove(str);//遍历过程中不要通过集合方法增或删元素
    }
    System.out.println(str);
}

  解决办法:

  使用迭代器提供的remove()方法可以删除通过next()获取的元素.

while(it.hasNext()){
            String str = (String)it.next();
            if("#".equals(str)){
//                c.remove(str);
                it.remove();
            }
            System.out.println(str);
        }

(14)java.lang.UnsupportedOperationException

  不支持的操作异常.该异常出现在很多的API中.

  例如:常出现在我们对数组转换的集合进行增删元素操作时抛出.

String[] array = {"one","two","three","four","five"};
System.out.println("array:"+ Arrays.toString(array));
List<String> list = Arrays.asList(array);//将数组转换为一个List集合
System.out.println("list:"+list);

list.set(0,"six");
System.out.println("list:"+list);
//对该集合的操作就是对原数组的操作
System.out.println("array:"+ Arrays.toString(array));

//由于数组是定长的,因此任何会改变数组长度的操作都是不支持的!
list.add("seven");//UnsupportedOperationException