Java异常机制:从混乱到控制的错误管理艺术

在这里插入图片描述

  • 👑专栏内容:Java
  • ⛪个人主页:子夜的星的主页
  • 💕座右铭:前路未远,步履不停

目录

  • 一、异常的体系结构
    • 1、异常的体系结构
    • 2、异常的分类
  • 二、异常的处理
    • 1、异常的抛出
    • 2、异常的捕获
      • 2.1、异常声明`throws`
      • 2.2、`try-catch`捕获并处理
      • 2.3、`finally`
    • 3、异常的处理流程
      • 3.1、什么是 "调用栈"
      • 3.2、异常处理流程总结
  • 三、自定义异常类


在程序运行过程中,会遇见一些奇奇怪怪的问题,有时候通过代码是很难控制的。在 Java 中,将程序执行过程中发生的不正常行为称之为异常。

一、异常的体系结构

异常种类有很多,为了对不同异常或者错误进行很好的分类管理。

Java内部维护了一个异常的体系结构:
在这里插入图片描述

1、异常的体系结构

Throwable 是 Java异常体系的顶层类,是Java语言中所有错误和异常的超类。它派生出两个重要的子类:ErrorException

Error 指的是Java虚拟机(JVM)无法解决的严重问题,通常不应由合理的应用程序尝试捕获。大多数此类错误是JVM遇到的异常情况。此类异常一旦发生,一般没有办法通过修改代码解决。

Exception指的是程序员可以通过代码进行处理,使程序继续执行的异常。我们平时所说的“异常”通常是指Exception

2、异常的分类

异常可能在编译时发生,也可能在程序运行时发生,根据发生的时机不同,可以将异常分为:运行时异常和编译时异常(受检查异常)。

  • 编译时异常

在程序编译期间发生的异常,称为编译时异常,也称为受检查异常(Checked Exception)。这些异常在编译期间会被检查,也就是说,如果一个方法可能抛出某个受检查异常,那么在调用这个方法的地方必须对这个异常进行处理。

这种处理可以是捕获异常并对其进行处理,或者是将异常声明在方法的throws子句中,告诉方法的调用者需要处理这个异常。

  • 运行时异常

运行时异常,也称为非受检查异常(Unchecked Exceptions),与编译时异常不同,运行时异常在编译期间不需要显式处理。这意味着如果你的代码抛出了一个运行时异常,你不需要用try-catch块捕获它,也不需要在方法声明中用throws子句声明它。

二、异常的处理

异常处理主要的5个关键字:throwtrycatchfinalthrows

1、异常的抛出

throw关键字是用来显式地抛出一个异常的。使用throw可以抛出一个现有的异常实例或创建一个新的异常实例并抛出。具体的语法如下:

throw new XXXException("需要显示的错误信息");

我们一般通过throw抛出一些自己自定义的异常。

public class ArrayElementRetriever {

    // getElement方法尝试从一个整数数组中获取指定索引处的元素
    public static int getElement(int[] array, int index) {
        // 检查传入的数组是否为null。如果是null,抛出NullPointerException。
        if (null == array) {
            throw new NullPointerException("传递的数组为null");
        }

        // 检查索引是否越界。如果索引小于0或大于等于数组长度,抛出ArrayIndexOutOfBoundsException。
        if (index < 0 || index >= array.length) {
            throw new ArrayIndexOutOfBoundsException("传递的数组下标越界");
        }

        // 如果没有异常发生,则返回数组中指定索引处的元素。
        return array[index];
    }

    public static void main(String[] args) {
        int[] array = {1, 2, 3};
        // 尝试获取数组中的元素。这里故意传递一个越界的索引。
        getElement(array, 3);
    }
}

【注意事项】

  1. throw必须写在方法体内部。
  2. 抛出的对象必须是Exception 或者 Exception 的子类对象。
  3. 如果抛出的是 RunTimeException 或者 RunTimeException 的子类,则可以不用处理,直接交给JVM来处理(JVM会立即终止你的程序)。
  4. 如果抛出的是编译时异常,用户必须处理,否则无法通过编译。
  5. 异常一旦抛出,其后的代码就不会执行。

2、异常的捕获

异常的捕获,也就是异常的具体处理方式。异常的捕获主要有两种方式:异常声明throws 以及 try-catch捕获处理。

2.1、异常声明throws

处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常。具体语法如下:

修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{
}
public class ExceptionThrower {
    // 定义一个方法,声明它可能抛出一个自定义的编译时异常
    public void doSomethingRisky() throws CustomException {
        // 假设这里有一些逻辑,最后确定需要抛出异常
        throw new CustomException("描述错误情况");
    }

    public static void main(String[] args) {
        ExceptionThrower thrower = new ExceptionThrower();
        try {
            // 尝试调用可能抛出异常的方法
            thrower.doSomethingRisky();
        } catch (CustomException e) {
            // 处理异常
            System.out.println("捕获到异常: " + e.getMessage());
        }
    }
}

// 自定义编译时异常
class CustomException extends Exception {
    public CustomException(String message) {
        super(message);
    }
}

【注意事项】

  1. throws关键字用于方法声明中,紧跟在方法的括号之后。
  2. throws后面可以跟一个或多个异常类型,使用逗号分隔。
  3. 如果一个方法声明抛出一个异常,那么调用这个方法的代码必须处理这个异常,要么是通过try-catch捕获它,要么是通过在其自身声明中使用throws继续向上抛出。
  4. 使用throws声明的异常类型应该是具体的异常类,而不是抽象类或接口。
  5. 对于运行时异常(RuntimeException及其子类),通常不在方法声明中使用throws进行声明,因为它们是非受检查的异常,但你也可以这样做以提供更好的程序文档。

2.2、try-catch捕获并处理

throws对异常并没有真正处理,而是将异常报告给抛出异常方法的调用者,由调用者处理。如果真正要对异常进行处理,就需要try-catch。具体语法格式如下:

try{
	// 将可能出现异常的代码放在这里
}catch(要捕获的异常类型 e){
	// 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基类时,就会被捕获到
	// 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码
}[catch(异常类型 e){
	// 对异常进行处理
}finally{
	// 此处代码一定会被执行到
}]
	// 后序代码
	// 当异常被捕获到时,异常就被处理了,这里的后序代码一定会执行
	// 如果捕获了,由于捕获时类型不对,那就没有捕获到,这里的代码就不会被执行

注意:

  1. []中表示可选项,可以添加,也可以不用添加
  2. try中的代码可能会抛出异常,也可能不会
public class demo{
    public static void main(String[] args) {
        System.out.println("before");
        try{
            System.out.println(10/0);
        }catch (ArithmeticException e){
            System.out.println("捕获到了 ArithmeticException 这个异常");
        }
        System.out.println("affer");
    }
}

在这里插入图片描述 try块内抛出异常位置之后的代码将不会被执行,如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到JVM收到后中断程序。

public class demo1 {
    public static void main(String[] args) {
        System.out.println("before");
        try{
            System.out.println(10/0);
        }catch (NullPointerException e){
            System.out.println("捕获到了 ArithmeticException 这个异常");
        }
        System.out.println("affer");
    }
}

在这里插入图片描述try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获,即多种异常,多次捕获

public class demo1 {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try {
            System.out.println("before");
            // arr = null;
            System.out.println(arr[100]);
            System.out.println("after");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("这是个数组下标越界异常");
            e.printStackTrace();
        } catch (NullPointerException e) {
            System.out.println("这是个空指针异常");
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }
}

在这里插入图片描述如果多个异常的处理方式是完全相同, 也可用写成下面这个样子:

catch (ArrayIndexOutOfBoundsException | NullPointerException e) {

	}

虽然可用像上面这样写,但是不建议。因为没办法知道到底是因为那个异常导致的。衡量一个代码的好与坏,除了时间复杂度和空间复杂度之外,真正要看的是代码的可读性。

如果异常之间具有父子关系,一定是子类异常在前catch,父类异常在后catch,否则语法错误:

public class demo1 {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try {
            System.out.println("before");
            arr = null;
            System.out.println(arr[100]);
            System.out.println("after");
        } catch (Exception e) { // Exception可以捕获到所有异常
            e.printStackTrace();
        }catch (NullPointerException e){ // 永远都捕获执行到
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }
}

在这里插入图片描述 可以通过一个catch捕获所有的异常,即多个异常,一次捕获(不推荐)

public class demo1 {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try {
            System.out.println("before");
            arr = null;
            System.out.println(arr[100]);
            System.out.println("after");
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }
}

在这里插入图片描述由于 Exception 类是所有异常类的父类,因此可以用这个类型表示捕捉所有异常。

catch 进行类型匹配的时候,不光会匹配相同类型的异常对象, 也会捕捉目标异常类型的子类对象。如刚才的代码,NullPointerExceptionArrayIndexOutOfBoundsException 都是 Exception 的子类,因此都能被捕获到。

2.3、finally

在写程序时,有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库连接、IO流等,在程序正常或者异常退出时,必须要对资源进进行回收。

另外,因为异常会引发程序的跳转,可能导致有些语句执行不到,finally就是用来解决这个问题的。finally的语法格式如下:

try{
	// 可能会发生异常的代码
}catch(异常类型 e){
	// 对捕获到的异常进行处理
}finally{
	// 此处的语句无论是否发生异常,都会被执行到
}
	// 如果没有抛出异常,或者异常被捕获处理了,这里的代码也会执行
public class demo1 {
    public static void main(String[] args) {
        try{
            int[] arr = {1,2,3};
            arr[100] = 10;
            arr[0] = 10;
        }catch (ArrayIndexOutOfBoundsException e){
            System.out.println(e);
        }finally {
            System.out.println("finally中的代码一定会执行");
        }
        System.out.println("如果没有抛出异常,或者异常被处理了,try-catch后的代码也会执行");
    }
}

在这里插入图片描述
【问题】 既然 finallytry-catch-finally 后的代码都会执行,那为什么还要有finally呢?

虽然finally 块和try-catch 结构后的代码在很多情况下都会执行,finally 仍然非常重要,原因主要包括:

  1. 确保资源释放: 最常见的finally用途是进行资源清理,比如关闭文件流或数据库连接。这些操作即使在发生异常时也必须执行,以防止资源泄漏。如果只将清理代码放在try-catch之后,那么在异常未被当前的catch捕获时,这些清理代码将不会执行。

  2. 处理未捕获的异常: 如果try块中抛出了一个未被任何catch捕获的异常,try-catch之后的代码不会执行,但finally块仍然会执行。这为处理所有情况提供了一个统一的地方,确保即使在出现意外异常时也能进行必要的清理。

  3. 覆盖返回值:trycatch块中有返回语句时,finally块仍会在方法返回之前执行,甚至有能力修改返回值(但是并不推荐这样,因为会使代码难以理解)。

  4. 明确的意图: 使用finally可以明确表示某段代码无论发生何种情况都必须执行,这对于代码的可读性和维护性是有好处的。它让其他开发者清楚地知道,无论try块中发生了什么,finally块中的代码都是必须执行的。

3、异常的处理流程

3.1、什么是 “调用栈”

方法之间是存在相互调用关系的,这种调用关系我们可以用 “调用栈” 来描述。 在 JVM 中有一块内存空间称为"虚拟机栈" 专门存储方法之间的调用关系。当代码中出现异常的时候,我们就可以使用 e.printStackTrace(); 的方式查看出现异常代码的调用栈。这个调用栈追踪显示了异常发生时的方法调用顺序,从最近的方法调用开始,一直追溯到异常被抛出的源头。调用栈追踪提供的信息通常包括:

  1. 异常类型和描述信息。
  2. 异常发生的代码位置,包括类名、文件名和行号。
  3. 方法调用序列,从发生异常的方法开始,一直到程序入口。
public class demo1 {
    public static void main(String[] args) {
        try {
            func();
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }
    public static void func() {
        int[] arr = {1, 2, 3};
        System.out.println(arr[100]);
    }
}

在这里插入图片描述
如果向上一直传递都没有合适的方法处理异常, 最终就会交给 JVM 处理,程序就会异常终止(和我们最开始未使用 try catch 时是一样的)。

3.2、异常处理流程总结

  1. 程序先执行 try 中的代码。
  2. 如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配。
  3. 如果找到匹配的异常类型, 就会执行 catch 中的代码。
  4. 如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者。
  5. 无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行)。
  6. 如果上层调用者也没有处理的了异常, 就继续向上传递一直到 main 方法也没有合适的代码处理异常, 。就会交给 JVM 来进行处理, 此时程序就会异常终止。

三、自定义异常类

自定义异常是一种用户定义的异常,它可以帮助处理程序特定的错误情况。创建自定义异常通常有助于更清晰地表达程序的意图和处理程序的特定错误。

自定义异常通常通过继承Java的Exception类或其子类来创建。如果希望自定义异常是受检异常(即必须显式处理的异常),则应继承Exception类。如果希望它是非受检异常,则应继承RuntimeException。自定义异常类应该至少提供一个构造方法,它通常会调用父类的构造方法来初始化异常消息。可以根据需要提供多个构造方法,以便在抛出异常时可以传递不同类型的信息。

public class LogIn {
	private String userName = "admin";
	private String password = "123456";
	public static void loginInfo(String userName, String password) {
		if (!userName.equals(userName)) {
		}
		if (!password.equals(password)) {
		}
			System.out.println("登陆成功");
	}
	public static void main(String[] args) {
		loginInfo("admin", "123456");
	}
}

此时我们在处理用户名密码错误的时候可能就需要抛出两种异常。我们可以基于已有的异常类进行扩展(继承),创建和我们业务相关的异常类。

具体方式:

  1. 自定义异常类,然后继承自Exception 或者 RunTimeException
  2. 实现一个带有String类型参数的构造方法,参数含义:出现异常的原因
class UserNameException extends Exception {
	public UserNameException(String message) {
	super(message);
	}
}
class PasswordException extends Exception {
	public PasswordException(String message) {
	super(message);
	}
}

此时我们的 login 代码可以改成:

public class LogIn {
    private String userName = "admin";
    private String password = "123456";
    public static void loginInfo(String userName, String password)
            throws UserNameException,PasswordException{
        if (!userName.equals(userName)) {
            throw new UserNameException("用户名错误!");
        }
        if (!password.equals(password)) {
            throw new PasswordException("用户名错误!");
        }
        System.out.println("登陆成功");
    }
    public static void main(String[] args) {
        try {
            loginInfo("admin", "123456");
        } catch (UserNameException e) {
            e.printStackTrace();
        } catch (PasswordException e) {
            e.printStackTrace();
        }
    }
}

注意:

  1. 自定义异常通常会继承自 Exception 或者 RuntimeException
  2. 继承自 Exception 的异常默认是受查异常
  3. 继承自RuntimeException 的异常默认是非受查异常.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/301157.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

加工制造EUV极紫外光刻机的钼/硅反射镜的方法与技术

EUV光刻机使用的反射镜材质是具有极高精度的钼/硅反射镜。这是因为几乎所有材料对13.5nm的EUV都强烈吸收&#xff0c;故EUV光刻机不能采用DUV那样的透镜&#xff0c;只能采用反射式光学系统。又因为EUV波长与晶格参数接近&#xff0c;很容易发生衍射&#xff0c;反射率也很低&a…

Wpf 使用 Prism 实战开发Day09

设置模块设计 1.效果图 一.系统设置模块&#xff0c;主要有个性化(用于更改主题颜色)&#xff0c;系统设置&#xff0c;关于更多&#xff0c;3个功能点。 个性化的颜色内容样式&#xff0c;主要是从 Material Design Themes UI简称md、提供的demo里复制代码过来使用的。 1.设置…

SpringCloud系列篇:核心组件之负载均衡组件

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于SpringCloud的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.负载均衡组件是什么 二.负载均衡…

【ArcGIS微课1000例】0087:经纬度格式转换(度分秒转度、度转度分秒)

ArcGIS软件可以很方便的直接实现度分秒转度、度转度分秒。 文章目录 一、转换预览二、工具介绍三、案例解析一、转换预览 借助ArcGIS快速实现度分秒与度及其他格式的坐标转换。例如:度分秒→度 度分秒: 度: 二、工具介绍 转换坐标记法:将一个或两个字段包含的坐标记法从一…

OpenNL线性系统求解库

OpenNL 是一个用于求解大型稀疏线性系统的C开发库。 它包括一个易于使用的用于组装矩阵的 API&#xff0c;以及用于对称和非对称系统的各种迭代求解器。 OpenNL API 在 geogram/NL/nl.h 中声明。 NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/…

linux反汇编工具: ida pro、rizinorg/cutter; ubuntu 22 flameshot延迟截图 以应对下拉菜单

rizinorg/cutter rizinorg/cutter 是 命令行反汇编工具 rizinorg/rizin 的图形化界面, 这比 ida pro跑在kvm虚拟机中方便多了, ubuntu22.04下直接下载Cutter-v2.3.2-Linux-x86_64.AppImage后即可运行,如下图: 注意 有个同名的报废品: radare2/Cutter 即 radare2的图形化界…

TypeScript 从入门到进阶之基础篇(八)函数篇

系列文章目录 TypeScript 从入门到进阶系列 TypeScript 从入门到进阶之基础篇(一) ts基础类型篇TypeScript 从入门到进阶之基础篇(二) ts进阶类型篇TypeScript 从入门到进阶之基础篇(三) 元组类型篇TypeScript 从入门到进阶之基础篇(四) symbol类型篇TypeScript 从入门到进阶…

docker 安装elasticsearch、kibana、cerebro、logstash

安装步骤 第一步安装 docker 第二步 拉取elasticsearch、kibana、cerebro、logstash 镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.10.2 docker pull docker.elastic.co/kibana/kibana:7.10.2 docker pull lmenezes/cerebro:latest docker pull l…

ClickHouse基础知识(七):ClickHouse的分片集群

副本虽然能够提高数据的可用性&#xff0c;降低丢失风险&#xff0c;但是每台服务器实际上必须容纳全量数据&#xff0c;对数据的横向扩容没有解决。 要解决数据水平切分的问题&#xff0c;需要引入分片的概念。通过分片把一份完整的数据进行切 分&#xff0c;不同的分片分布到…

Vue知识总结-中

VUE-生命周期 生命周期概述 生命周期也常常被称为生命周期回调函数/生命周期函数/生命周期钩子生命周期是Vue在关键时刻帮我们调用的一些特殊名称的函数生命周期函数的名字不能更改,但函数的具体内容是由我们程序员自己编写的生命周期函数中的this指向是vm或组件实例对象 生命周…

【Flutter 开发实战】Dart 基础篇:从了解背景开始

想要学会用 Flutter 开发 App&#xff0c;就不可避免的要学习另一门很有意思的编程语言 —— Dart。很多小伙伴可能在学习 Flutter 之前可能都没听说过这门编程语言&#xff0c;我也是一样&#xff0c;还以为 Dart 是为了 Flutter 而诞生的&#xff1b;然而&#xff0c;当我们去…

【嵌入式】Makefile 学习笔记记录 | 嵌入式Linux

文章目录 前言一、Makefile的引入——最简单的gcc编译过程二、Makefile的规则三、Makefile的语法3.1、通配符3.2、假想目标 .phony3.3、即时变量 延时变量 四、Makefile的函数4.1、foreach4.2、filter4.3、wildcard4.4、patsubst 五、Makefile升级5.1、包含头文件在内的依赖关系…

Force Yc 第六引导公告网页源码

Force Yc 第六引导公告网页源码,HTML源码&#xff0c;本地双击index.html即可运行,内容体积小&#xff0c;美观大气&#xff0c;二次元风格,喜欢的朋友可以拿去研究 蓝奏云&#xff1a;https://wfr.lanzout.com/iZsjv1kexkod

西电期末1035.可构造三角形个数

一.题目 二.分析与思路 依旧是遍历判断&#xff0c;三角形任意两边之和大于第三边&#xff0c;读题&#xff01;&#xff01;&#xff01;&#xff1a;连续的三个数&#xff01;&#xff01;&#xff01; 三.代码实现 #include<bits/stdc.h>//万能头 int main() {int …

机器视觉篇

1&#xff1a;实现LCD显示文字 实验名称&#xff1a;LCD 版本&#xff1a; v1.0 日期&#xff1a; 2022.12 作者&#xff1a; 01Studio 说明&#xff1a;编程实现LCD显示信息。需要将01Studio.bmp文件发送到开发板 import lcd,image,utimelcd.init() #初始化LCD lcd.clear(lcd…

嵌入式培训机构四个月实训课程笔记(完整版)-Linux系统编程第五天-Linux消息共享内存练习题(物联技术666)

更多配套资料CSDN地址:点赞+关注,功德无量。更多配套资料,欢迎私信。 物联技术666_嵌入式C语言开发,嵌入式硬件,嵌入式培训笔记-CSDN博客物联技术666擅长嵌入式C语言开发,嵌入式硬件,嵌入式培训笔记,等方面的知识,物联技术666关注机器学习,arm开发,物联网,嵌入式硬件,单片机…

案例介绍|钡铼助力2023年全国职业院校技能大赛工业网络智能控制与维护赛项

如今&#xff0c;越来越多的企业开始意识到数字制造和工业物联网已经成为工业自动化中大规模生产的核心驱动力。这其中&#xff0c;工业网络作为基础设施&#xff0c;是实现工厂设备联网与数据采集&#xff0c;建设数字工厂的基础和前提&#xff0c;甚至成为关乎数字工厂能否真…

【动态规划】C++算法312 戳气球

作者推荐 【动态规划】【字符串】扰乱字符串 本文涉及的基础知识点 动态规划 LeetCode312 戳气球 有 n 个气球&#xff0c;编号为0 到 n - 1&#xff0c;每个气球上都标有一个数字&#xff0c;这些数字存在数组 nums 中。 现在要求你戳破所有的气球。戳破第 i 个气球&…

PIG框架学习2——资源服务器的配置详解

一、前言 1、pig资源服务器的配置 Spring Security oauth2相关的依赖是在pigx-common-security模块中引入的&#xff0c;其他模块需要进行token鉴权的&#xff0c;需要在微服务中引入pigx-common-security模块的依赖&#xff0c;从而间接引入相关的Spring security oauth2依赖…

环境中碳循环

含碳的物质有CO2、CO、CH4、糖类、脂肪和蛋白质等&#xff0c;碳循环以CO2为中心&#xff0c;CO2被植物、藻类利用进行光合作用&#xff0c;合成植物性碳&#xff1b;动物摄食植物就将植物性碳转化为动物性碳&#xff1b;动物和人呼吸放出CO2&#xff0c;有机碳化合物被厌氧微生…