我去,你竟然还在用 try–catch-fin

探讨了Java中try-with-resources语句的使用,对比try-catch-finally,展示其在资源管理上的优势,包括代码简洁性和异常处理的可靠性。

现在复工复产的行业越来越多了,疫情估计会踩着春天的尾巴离开。同也因为这次疫情,让我们更深刻的认识到,只有新颖的、实用的、高效快捷的东西才会被留下来,随之而改变的就是我们的行业模式。我们开始寻找新的东西,留下那些有思想有技术的工作人员,只有有内涵,能创新,紧跟网络时代的脚步,了解最新的网络资源,认清当前的趋势走向的人,才是公司真正需要的人员。

来说说我们公司的吧,这次 review 的代码仍然是小王的,他编写的大部分代码都很漂亮,严谨的同时注释也很到位,这令我非常满意。但当我看到他没用 try-with-resources 时,还是忍不住破口大骂:“我擦,小王,你丫的竟然还在用 try–catch-finally!”

来看看小王写的代码吧。

public class Trycatchfinally {
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader("/牛逼.txt"));
            String str = null;
            while ((str =br.readLine()) != null) {
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
复制代码

咦,感觉这段代码很完美无缺啊,try–catch-finally 用得中规中矩,尤其是文件名 牛逼.txt 很亮。不用写注释都能明白这段代码是干嘛的:在 try 块中读取文件中的内容,并一行一行地打印到控制台。如果文件找不到或者出现 IO 读写错误,就在 catch 中捕获并打印错误的堆栈信息。最后,在 finally 中关闭缓冲字符读取器对象 BufferedReader,有效杜绝了资源未被关闭的情况下造成的严重性能后果。

在 Java 7 之前,try–catch-finally 的确是确保资源会被及时关闭的最佳方法,无论程序是否会抛出异常。

但是呢,有经验的读者会从上面这段代码中发现 2 个严重的问题:

1)文件名“牛逼.txt”包含了中文,需要通过 java.net.URLDecoder 类的 decode() 方法对其转义,否则这段代码在运行时铁定要抛出文件找不到的异常。

2)如果直接通过 new FileReader("牛逼.txt") 创建 FileReader 对象,“牛逼.txt”需要和项目的 src 在同一级目录下,否则同样会抛出文件找不到的异常。但大多数情况下,(配置)文件会放在 resources 目录下,便于编译后文件出现在 classes 目录下,见下图。

 

为了解决以上 2 个问题,我们需要对代码进行优化:

public class TrycatchfinallyDecoder {
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            String path = TrycatchfinallyDecoder.class.getResource("/牛逼.txt").getFile();
            String decodePath = URLDecoder.decode(path,"utf-8");
            br = new BufferedReader(new FileReader(decodePath));

            String str = null;
            while ((str =br.readLine()) != null) {
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
复制代码

运行这段代码,程序就可以将文件中的内容正确输出到控制台。但如果你对“整洁”这个词心生向往的话,会感觉这段代码非常臃肿,尤其是 finally 中的代码,就好像一个灌了 12 瓶雪花啤酒的大肚腩。

网上看到一幅 Python 程序员调侃 Java 程序员的神图,直接 copy 过来(侵删),逗你一乐:

 

况且,try–catch-finally 至始至终存在一个严重的隐患:try 中的 br.readLine() 有可能会抛出 IOException,finally 中的 br.close() 也有可能会抛出 IOException。假如两处都不幸地抛出了 IOException,那程序的调试任务就变得复杂了起来,到底是哪一处出了错误,就需要花一番功夫,这是我们不愿意看到的结果。

为了模拟上述情况,我们来自定义一个类 MyfinallyReadLineThrow,它有两个方法,分别是 readLine()close(),方法体都是主动抛出异常。

class MyfinallyReadLineThrow {
    public void close() throws Exception {
        throw new Exception("close");
    }

    public void readLine() throws Exception {
        throw new Exception("readLine");
    }
}
复制代码

然后我们在 main() 方法中使用 try-finally 的方式调用 MyfinallyReadLineThrow 的 readLine()close() 方法:

public class TryfinallyCustomReadLineThrow {
    public static void main(String[] args) throws Exception {
        MyfinallyReadLineThrow myThrow = null;
        try {
            myThrow = new MyfinallyReadLineThrow();
            myThrow.readLine();
        } finally {
            myThrow.close();
        }
    }
}
复制代码

运行上述代码后,错误堆栈如下所示:

Exception in thread "main" java.lang.Exception: close
	at com.cmower.dzone.trycatchfinally.MyfinallyOutThrow.close(TryfinallyCustomOutThrow.java:17)
	at com.cmower.dzone.trycatchfinally.TryfinallyCustomOutThrow.main(TryfinallyCustomOutThrow.java:10)
复制代码

readLine() 方法的异常信息竟然被 close() 方法的堆栈信息吃了,这必然会让我们误以为要调查的目标是 close() 方法而不是 readLine()——尽管它也是应该怀疑的对象。

但自从有了 try-with-resources,这些问题就迎刃而解了,只要需要释放的资源(比如 BufferedReader)实现了 AutoCloseable 接口。有了解决方案之后,我们来对之前的 finally 代码块进行瘦身。

try (BufferedReader br = new BufferedReader(new FileReader(decodePath));) {
    String str = null;
    while ((str =br.readLine()) != null) {
        System.out.println(str);
    }
} catch (IOException e) {
    e.printStackTrace();
}
复制代码

你瞧,finally 代码块消失了,取而代之的是把要释放的资源写在 try 后的 () 中。如果有多个资源(BufferedReader 和 PrintWriter)需要释放的话,可以直接在 () 中添加。

try (BufferedReader br = new BufferedReader(new FileReader(decodePath));
     PrintWriter writer = new PrintWriter(new File(writePath))) {
    String str = null;
    while ((str =br.readLine()) != null) {
        writer.print(str);
    }
} catch (IOException e) {
    e.printStackTrace();
}
复制代码

如果你想释放自定义资源的话,只要让它实现 AutoCloseable 接口,并提供 close() 方法即可。

public class TrywithresourcesCustom {
    public static void main(String[] args) {
        try (MyResource resource = new MyResource();) {
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class MyResource implements AutoCloseable {
    @Override
    public void close() throws Exception {
        System.out.println("关闭自定义资源");
    }
}
复制代码

代码运行后输出的结果如下所示:

关闭自定义资源
复制代码

是不是很神奇?我们在 try () 中只是 new 了一个 MyResource 的对象,其他什么也没干,但偏偏 close() 方法中的输出语句执行了。想要知道为什么吗?来看看反编译后的字节码吧。

class MyResource implements AutoCloseable {
    MyResource() {
    }

    public void close() throws Exception {
        System.out.println("关闭自定义资源");
    }
}

public class TrywithresourcesCustom {
    public TrywithresourcesCustom() {
    }

    public static void main(String[] args) {
        try {
            MyResource resource = new MyResource();
            resource.close();
        } catch (Exception var2) {
            var2.printStackTrace();
        }

    }
}
复制代码

咦,编译器竟然主动为 try-with-resources 进行了变身,在 try 中调用了 close() 方法。

接下来,我们在自定义类中再添加一个 out() 方法,

class MyResourceOut implements AutoCloseable {
    @Override
    public void close() throws Exception {
        System.out.println("关闭自定义资源");
    }

    public void out() throws Exception{
        System.out.println("沉默王二,一枚有趣的程序员");
    }
}
复制代码

这次,我们在 try 中调用一下 out() 方法:

public class TrywithresourcesCustomOut {
    public static void main(String[] args) {
        try (MyResourceOut resource = new MyResourceOut();) {
            resource.out();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
复制代码

再来看一下反编译的字节码:

public class TrywithresourcesCustomOut {
    public TrywithresourcesCustomOut() {
    }

    public static void main(String[] args) {
        try {
            MyResourceOut resource = new MyResourceOut();

            try {
                resource.out();
            } catch (Throwable var5) {
                try {
                    resource.close();
                } catch (Throwable var4) {
                    var5.addSuppressed(var4);
                }

                throw var5;
            }

            resource.close();
        } catch (Exception var6) {
            var6.printStackTrace();
        }

    }
}
复制代码

这次,catch 块中主动调用了 resource.close(),并且有一段很关键的代码 var5.addSuppressed(var4)。它有什么用处呢?当一个异常被抛出的时候,可能有其他异常因为该异常而被抑制住,从而无法正常抛出。这时可以通过 addSuppressed() 方法把这些被抑制的方法记录下来。被抑制的异常会出现在抛出的异常的堆栈信息中,也可以通过 getSuppressed() 方法来获取这些异常。这样做的好处是不会丢失任何异常,方便我们开发人员进行调试。

哇,有没有想到我们之前的那个例子——在 try-finally 中,readLine() 方法的异常信息竟然被 close() 方法的堆栈信息吃了。现在有了 try-with-resources,再来看看作用和 readLine() 方法一致的 out() 方法会不会被 close() 吃掉。

close()out() 方法中直接抛出异常:

class MyResourceOutThrow implements AutoCloseable {
    @Override
    public void close() throws Exception {
        throw  new Exception("close()");
    }

    public void out() throws Exception{
        throw new Exception("out()");
    }
}
复制代码

调用这 2 个方法:

public class TrywithresourcesCustomOutThrow {
    public static void main(String[] args) {
        try (MyResourceOutThrow resource = new MyResourceOutThrow();) {
            resource.out();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
复制代码

程序输出的结果如下所示:

java.lang.Exception: out()
	at com.cmower.dzone.trycatchfinally.MyResourceOutThrow.out(TrywithresourcesCustomOutThrow.java:20)
	at com.cmower.dzone.trycatchfinally.TrywithresourcesCustomOutThrow.main(TrywithresourcesCustomOutThrow.java:6)
	Suppressed: java.lang.Exception: close()
		at com.cmower.dzone.trycatchfinally.MyResourceOutThrow.close(TrywithresourcesCustomOutThrow.java:16)
		at com.cmower.dzone.trycatchfinally.TrywithresourcesCustomOutThrow.main(TrywithresourcesCustomOutThrow.java:5)
复制代码

瞧,这次不会了,out() 的异常堆栈信息打印出来了,并且 close() 方法的堆栈信息上加了一个关键字 Suppressed。一目了然,不错不错,我喜欢。

总结一下,在处理必须关闭的资源时,始终有限考虑使用 try-with-resources,而不是 try–catch-finally。前者产生的代码更加简洁、清晰,产生的异常信息也更靠谱。答应我好不好?别再用 try–catch-finally 了。

鸣谢

好了,我亲爱的读者朋友,以上就是本文的全部内容了,是不是感觉又学到了新知识?对以上相关资料或是对架构,面试,算法,大数据等最新网络资源感兴趣的读者朋友们,可以在评论区发表自己的言论,有什么疑问也可以私信小编进行解答。

 

<think>好的,我现在需要帮用户解决如何在Python中使用类似try-catch的机制来保证资源关闭。用户可能刚接触Python,或者对异常处理不太熟悉,所以需要详细解释。首先,我得回忆一下Python中的异常处理结构,try-except-finally的用法。用户提到的是资源关闭,比如文件、数据库连接或者网络资源,这些都需要正确释放,否则可能导致泄漏。 用户的问题中提到了“类似try catch”,但Python中的关键字是try-except,不过用户可能指的是异常处理的结构。核心应该是在确保无论是否发生异常,资源都能被正确关闭。这时候,finally块的作用就很重要,因为无论是否发生异常,finally中的代码都会执行。 接下来,我需要考虑不同的资源类型,比如文件、数据库连接、锁等,分别举例说明如何使用try-finally或者with语句。比如,文件的处理通常使用with语句来自动关闭,但用户可能想了解更底层的方法,或者在使用不支持上下文管理的资源时如何处理。 可能用户遇到的场景是,他们打开了一个文件,进行了一些操作,但如果在操作过程中出现异常,文件没有被正确关闭,导致资源泄漏。这时候,使用try-finally可以在异常发生时仍执行关闭操作。或者他们可能在使用数据库连接时,没有正确关闭,导致连接池耗尽。 另外,可能需要提到with语句的上下文管理器,这是Python中更简洁的资源管理方式,但用户可能想了解手动处理的方法,或者在某些无法使用with的情况下如何操作。 需要分步骤说明,首先介绍基本的try-except-finally结构,然后举例说明文件处理、数据库连接等场景。同时比较手动使用finally和使用with语句的优劣,让用户了解不同的方法。 还要注意可能出现的错误,比如在finally块中再次引发异常,或者资源关闭的顺序问题。比如,在关闭文件之前检查文件对象是否存在,避免在未成功打开的情况下调用close方法导致错误。 用户可能想知道,如果在try块中获取了多个资源,如何确保每个都能正确关闭。这时候可能需要嵌套的try-finally块,或者分别处理每个资源的关闭。 最后,总结一下最佳实践,推荐使用with语句,因为它更简洁,自动处理资源的释放,同时也可以自定义上下文管理器来处理复杂情况。 现在需要组织这些思路,结构清晰,步骤明确,确保用户能够理解并应用。</think>在Python中,可以通过`try...finally`和上下文管理器两种核心机制确保资源正确关闭。以下是通过异常处理保证资源安全释放的完整指南: --- ### **1. 基础模式 - try/finally** ```python resource = None try: # 获取资源 resource = open("data.txt", "r") # 资源操作 data = resource.read() # 可能抛出异常的操作 process(data) except IOError as e: print(f"文件操作异常: {e}") except ValueError as e: print(f"数据处理异常: {e}") finally: # 无论是否异常都会执行 if resource is not None: resource.close() print("资源已强制关闭") ``` #### **关键要点**: - `finally`块始终执行,无论是否发生异常 - 必须检查资源是否为`None`,避免未初始化时调用关闭方法 - 多个资源需要嵌套处理: ```python file1 = file2 = None try: file1 = open("a.txt") file2 = open("b.txt") # ... finally: for f in [file1, file2]: if f is not None: f.close() ``` --- ### **2. 进阶模式 - 上下文管理器** 使用`with`语句自动管理资源: ```python # 标准文件操作 with open("data.csv", "r") as file: csv_data = file.read() # 不需要显式关闭 # with块结束自动调用file.close() # 自定义资源类 class DatabaseConnection: def __enter__(self): self.conn = create_connection() return self.conn def __exit__(self, exc_type, exc_val, exc_tb): self.conn.close() if exc_type: print(f"关闭时捕获异常: {exc_type}") # 返回True表示已处理异常 return True # 使用自定义管理器 with DatabaseConnection() as db: db.execute("SELECT ...") ``` #### **优势对比**: | 方法 | 代码简洁性 | 异常处理 | 多资源支持 | 可读性 | |-----------------|------------|----------|------------|--------| | try/finally | 低 | 显式处理 | 需要嵌套 | 一般 | | 上下文管理器 | 高 | 自动处理 | 多with并行 | 优秀 | --- ### **3. 复合资源处理** #### (1) 多个with并行: ```python with open("input.txt") as fin, \ open("output.txt", "w") as fout: fout.write(fin.read().upper()) ``` #### (2) 上下文管理器嵌套: ```python from contextlib import contextmanager @contextmanager def acquire_lock(lock): lock.acquire() try: yield # 此处为资源使用区 finally: lock.release() with acquire_lock(my_lock): # 临界区操作 modify_shared_data() ``` --- ### **4. 异常安全等级** | 安全等级 | 描述 | 实现方式 | |----------|-------------------------------|---------------------------| | 基本保证 | 资源不泄漏 | 单独finally块 | | 强保证 | 要么完全成功,要么回滚到初始状态 | 事务处理+嵌套管理器 | | 无异常保证 | 可能残留中间状态 | 无保护代码 | **强保证示例(数据库事务)**: ```python class Transaction: def __enter__(self): self.conn = get_connection() self.conn.begin() return self.conn def __exit__(self, exc_type, *_): if exc_type: self.conn.rollback() else: self.conn.commit() self.conn.close() with Transaction() as conn: conn.execute("UPDATE ...") conn.execute("INSERT ...") # 任一失败都会回滚 ``` --- ### **5. 常见陷阱与解决方案** #### **陷阱1:finally中的异常** ```python # 错误示范 try: f = open("data") # ... finally: f.close() # 如果文件未成功打开会抛异常 # 正确写法 f = None try: f = open("data") # ... finally: if f is not None: try: f.close() except Exception as close_err: print(f"关闭异常: {close_err}") ``` #### **陷阱2:返回值覆盖** ```python def read_file(): f = None try: f = open("data") return f.read() # 在finally前执行return finally: if f: f.close() # 仍然会执行 ``` --- ### **6. 标准库工具** #### (1) contextlib模块 ```python from contextlib import closing, suppress # 自动调用close() with closing(urllib.request.urlopen('http://site.com')) as page: html = page.read() # 忽略指定异常 with suppress(FileNotFoundError): os.remove('temp.file') ``` #### (2) 异步上下文管理器 ```python class AsyncResource: async def __aenter__(self): await connect() return self async def __aexit__(self, *args): await disconnect() async with AsyncResource() as res: await res.query() ``` --- ### **7. 最佳实践原则** 1. **资源获取即进入保护**:在`try`块外初始化资源 2. **异常分层处理**:在`except`块处理已知异常,未知异常向上传播 3. **关闭顺序**:按创建的反序关闭多个资源 4. **防御性关闭**:所有close操作都放在单独的try块中 5. **日志记录**:在finally块添加资源关闭确认日志 通过合理运用这些机制,可以确保文件句柄、数据库连接、网络套接字、线程锁等资源的安全释放,即使程序执行过程中发生意外错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值