java异常

1.异常体系

Java一切都是类或对象,异常也是。

所有异常类的基类:Throwable类,两大子类:Error、Exception

体系结构图解:
java异常_第1张图片

  • Error: 系统错误,由JVM抛出,描述内部系统错误。例如JVM崩溃,仅凭程序自身无法处理,在程序中也无法对Error异常进行捕捉和抛出。比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。

  • Exception:异常。分为 RuntimeException(运行时异常)和CheckedException(检查时异常),区别如下:

    • RuntimeException:程序运行过程中才可能发生的异常。一般为代码的逻辑错误。例如:类型错误转换,数组下标访问越界,空指针异常、找不到指定类等等。
    • CheckedException:编译期间可以检查到的异常,必须显式的进行处理(捕获或者抛出到上一层)。例如:IOException, FileNotFoundException等等。
      java异常_第2张图片

说明:java中的Exception类的子类不仅只包含IOException和RuntimeException这两大类,事实上Exception的子类很多很多,主要可概括为:运行时异常与非运行时异常,同时也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。

  • 运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
  • 非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

已检查异常:错误可被提前预知。(输入输出、文件损坏、网络连接失败)
例如:FileNotFoundException。一定会抛出。机器兜底。
未检查异常:程序员自己造成的逻辑错误,不是不可避免的外部风险。
例如:NPE。自己兜底。

两者区别有时候不够明显:
Integer.parseInt(str),当str没有包含一个有效整数,会抛出未检查异常NumberFormatException
Class.forName(str),当str没有包含一个有效类名,抛出已检查异常 ClassNotFoundException,一定会抛出。

事实上,在调用Integer.parseInt之前,检查字符串是可能的。而在加载一个类之前,不可能知道该类是否能被加载。

public static int parseInt(String s) throws NumberFormatException {
    return parseInt(s,10);
}

@CallerSensitive
public static Class<?> forName(String className)
    throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

public static void main(String[] args) throws ClassNotFoundException {
    Class<?> abc = Class.forName("abc");
}

除非没有一个标准的异常满足业务需求,再去继承Exception类、RuntimeException类等来构建,最好提供两个构造函数,一个不带参数,一个待消息字符串参数。

public class FileFormatException extends IOException {
    public FileFormatException() {
    }

    public FileFormatException(String message) {
        super(message);
    }
}

2.异常处理

关键字:try、catch、throw(抛出一个异常、动词)、throws(声明一个方法可能抛出的异常)、finally

2.1 throws声明异常

若方法中存在检查时异常,如果不对其捕获,那必须在方法头中显式声明该异常,以便于告知方法调用者此方法有异常,需要进行处理。

在方法中声明一个异常,方法头中使用关键字throws,后面接上要声明的异常。若声明多个异常,则使用逗号分割。

若是父类的方法没有声明异常,则子类继承方法后,也不能声明异常,子类不能抛出比父类更多,但是可以抛出更少。

2.2 try-catch 捕获异常

​ 1)若执行try块的过程中没有发生异常,则跳过catch子句。

​ 2)若是出现异常,try块中剩余语句不再执行。开始逐步检查catch块,判断catch块的异常类实例是否是捕获的异常类型,匹配后执行相应的catch块中的代码。

​ 3)如果异常没有在当前的方法中被捕获,就会被传递给该方法的调用者。这个过程一直重复,直到异常被捕获或被传给main方法(交给JVM来捕获)

​ 4)对于try…catch捕获异常的形式来说,对于异常的捕获,可以有多个catch。对于try里面发生的异常,他会根据发生的异常和catch里面的进行匹配(按照catch块从上往下匹配),如果有匹配的catch,它就会忽略掉这个catch后面所有的catch。

​ 5)如果有finally的话进入到finally里面继续执行。

​ 6)try ctach fianally 中有return 时,会先执行return ,但是不会返回。在执行完 finally 后 进行返回。

​ 7)return 的是基本类型数据时, fianlly 里面的语句不会影响 return 的值,

​ 8)return 的是引用类型数据时,此时已经确定了要返回对象的地址(地址一),后面 fianlly 里面的可以通过修改前面地址一中的内容修改返回的内容,

​ 9)但是如果将对象指向另一个地址(地址二),则不会影响返回的内容。因为返回的对象地址已经确定为地址一,只能通过修改地址一对象的内容修改返回的信息。

异常的捕获和处理

1、异常处理的步骤:

throw try catch finally throws下面是在网络通信中运用socket的一段代码:

try {  
    mSocket=new Socket(ip,port);  
    if(mSocket!=null)  
    {  
        Log.i("Client","socket is create");  
        clientInput=new ClientInputThread(mSocket);  
        clientOutput=new ClientOutputThread(mSocket);  
        clientInput.setStart(true);  
        clientOutput.setStart(true);  

        clientInput.start();  
        clientOutput.start();  

    }  
    else {  
        Log.i("Client","socket is not create");  
        //  Toast.makeText(, "亲,服务器端连接出错",0).show();  
    }  
} catch (UnknownHostException e) {  
    // TODO Auto-generated catch block  
    e.printStackTrace();  
} catch (IOException e) {  
    // TODO Auto-generated catch block  
    e.printStackTrace();  

}finally{}   

处理流程如下:
java异常_第3张图片

2、 try、catch、finally三个语句块应注意的问题
1)try、catch、finally三个语句块均不能单独使用,三者可以组成 try…catch…finally、try…catch、try…finally三种结构,catch语句可以有一个或多个,finally语句最多一个。
2)try、catch、finally三个代码块中变量的作用域为代码块内部,分别独立而不能相互访问。如果要在三个块中都可以访问,则需要将变量定义到这些块的外面。
3)多个catch块时候,最多只会匹配其中一个异常类且只会执行该catch块代码,而不会再执行其它的catch块,且匹配catch语句的顺序为从上到下,也可能所有的catch都没执行。
4)先Catch子类异常再Catch父类异常。

示意图:
java异常_第4张图片
java异常_第5张图片

3、throw、throws关键字
throw关键字是用于方法体内部,用来抛出一个Throwable类型的异常。如果抛出了检查异常,则还应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也必须检查处理抛出的异常。如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。throw关键字用法如下:

public static void test() throws Exception  
{  
   throw new Exception("方法test中的Exception");  
}  

throws关键字用于方法体外部的方法声明部分,用来声明方法可能会抛出某些异常。仅当抛出了检查异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出.
java异常_第6张图片
java异常_第7张图片

注意一个方法throws出某个异常但是该方法内部可以不throw出该异常,代码如下:

class ER extends RuntimeException  
{  
  
}  
class SomeClass  
{  
    public void fun()throws ER  
    {  
        System.out.println("AAAA");  
          
    }  
}  
  
public class ExceptionTest {  
  
    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
        SomeClass A=new SomeClass();  
        A.fun();  
    }  
}  

try-with-resources语句

资源管理

要求:每一个资源必须实现AutoCloseable接口

public interface AutoCloseable {
    void close() throws Exception;
}
AutoCloseable的子接口Closeable
public interface Closeable {
    void close() throws IOException;
}
static void testTryWithResources() throws IOException {
    ArrayList<String> lines = new ArrayList<>();
    try (Scanner in = new Scanner(Paths.get("/usr/share/dict/words"));
         PrintWriter out = new PrintWriter("output.text")) {
        while (in.hasNext()) {
            out.println(in.next().toLowerCase());
        }
    }
}

先关闭out.close(),再关闭in.close()

如果PrintWriter的构造函数抛出一个异常,此时in已经初始化了,out没有,
try语句会先关闭已经初始化的in.close(),然后传播一个异常。

当你捕获了主要异常,可以通过调用 getSuppressed()方法检索得到第二个异常:

try {
    ...
} catchIOException ex) {
    Throwable[] secondaryExceptions = ex.getSuppressed();
}

堆栈踪迹

如果没有在任何地方捕获异常,会显示stack trace,列出异常抛出时,所有未决方法的调用信息。
堆栈踪迹信息会被推送到错误信息的流System.err

保存异常,设置默认的未捕获异常处理器

Thread.setDefaultUncaughtExceptionHandler((thread,ex)->{
    
});

未捕获异常会终止其所在的线程。
如果你的应用程序只有一个线程,则在未捕获异常处理器之后,程序会退出。

不知道如何处理异常,至少也打印堆栈踪迹信息,而不是忽略。

e.printStackTrace();
StackTraceElement[] frames = ex.getStackTrace();

你可能感兴趣的:(java,java,jvm,开发语言)