前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >希望你不要经历的那些坑:你确定资源正确释放了?

希望你不要经历的那些坑:你确定资源正确释放了?

作者头像
明明如月学长
发布2023-09-21 08:43:03
1820
发布2023-09-21 08:43:03
举报
你确定正确释放了资源??.png
你确定正确释放了资源??.png

一、背景

最近对某段代码进行代码审查,无意间发现一个哭笑不得的“神操作”! 该同学代码中用最标准的释放资源的方法,可是并没有正确释放资源。 本文将模拟该问题,讲述背后的原因,希望大家编码时要特别注意该问题。

二、场景复现

2.1 示例1

代码语言:javascript
复制
package com.demo.demo;

import okhttp3.*;

import java.io.IOException;

public class OkHttpExample {

    public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");

    OkHttpClient client = new OkHttpClient();

    public SomeResult  post(String url, String json) throws IOException {
        RequestBody body = RequestBody.create(JSON, json);
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();
        Response response = null;
        try{
            return someMethod(response,request);
        }finally {
            if(response != null){
                response.close();
            }
        }

    }

    private SomeResult someMethod(Response response, Request request) throws IOException {
         response = client.newCall(request).execute();

         // 其他逻辑
         return   SomeUtils.toSomeResult(response);
    }


    public static void main(String[] args) {
        OkHttpExample example = new OkHttpExample();
        String json = "{\"name\":\"mkyong\"}";
        String response;
        try {
            response = example.post("https://api.yourdomain.com/v1/api", json);
            System.out.println(response);
        } catch (IOException e) {
            // 打印错误日志
        }
    }
}

上述代码看似很正确,非常专业地在 finally 中释放资源,然而你仔细读读代码,可能会发现问题。

下面给你 2 分钟的时间思考一下,存在什么问题。

[2 分钟]

2.2 示例2

如果你还看不出来原因,那么请你说出下面代码运行的结果:

代码语言:javascript
复制
public class CatDemo {
    public static void main(String[] args) {

        Cat cat = new Cat();
        cat.setName("tom");
        System.out.println(cat);

    }

    private void test(Cat cat){
        cat = new Cat();
        cat.setName("cat");
    }
}



public class Cat {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}

答案是: Cat{name='tom'}

如果到这里还搞不明白,说明基础有问题,需要加强了。

三、背景知识

在Java中,所有的方法参数都是按值传递的。这意味着当你传递一个变量给一个方法时,你实际上是传递了一个这个变量的复制品。 但我们需要区分两种情况:传递原始类型的变量传递对象引用变量

传递原始类型变量

对于原始类型(如int, double, char等),按值传递很直观。你创建了一个原始类型的变量,当你将其传递给一个方法时,方法接收到的是一个新的变量,它包含的是原始变量的一个副本。 如果方法修改了这个新变量,它不会影响原始变量。

示例:

代码语言:javascript
复制
public class Main {
    public static void main(String[] args) {
        int a = 5;
        modify(a);
        System.out.println(a);  // 输出:5
    }

    public static void modify(int number) {
        number = 10;
    }
}

在上述代码中,虽然modify方法修改了number变量的值,但这不影响main方法中a变量的值。

image.png
image.png

注:图只是为了说明问题,细节上可能未必合理,如果有出入请勿较真。

传递对象引用变量

对于对象引用变量,情况略有不同。虽然是按值传递,但传递的是对象引用的值,而不是对象本身。这意味着方法接收到的是原始对象引用的一个副本。因此,该方法可以通过这个引用来修改原始对象的状态。

但是,如果该方法试图将新的对象赋值给它的对象引用变量,这不会影响原始对象引用变量,因为它只修改了副本的指向,而不是原始引用的指向。

示例:

代码语言:javascript
复制
public class Main {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.setName("Tom");
        modifyReference(cat);
        System.out.println(cat.getName());  // 输出:Tom

        modifyObject(cat);
        System.out.println(cat.getName());  // 输出:Jerry
    }

    public static void modifyReference(Cat cat) {
        cat = new Cat();
        cat.setName("Spike");
    }

    public static void modifyObject(Cat cat) {
        cat.setName("Jerry");
    }

    static class Cat {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

在上述代码中:

  • modifyReference方法试图修改cat引用变量的指向,但这不影响main方法中的cat变量。
  • modifyObject方法通过cat引用变量来修改Cat对象的状态,这实际上影响了main方法中的cat对象。
image.png
image.png

四、揭晓答案

4.1 示例1

因此,在示例一中:

代码语言:javascript
复制
    private SomeResult someMethod(Response response, Request request) throws IOException {
         response = client.newCall(request).execute();

         // 其他逻辑
         return   SomeUtils.toSomeResult(response);
    }

client.newCall(request).execute();创建了新的实例赋值给对象引用变量 response,只是修改了副本指向了新的对象而已。 因此 :

代码语言:javascript
复制
       Response response = null;
        try{
            return someMethod(response,request);
        }finally {
            // 永远为 null,从来没有调用到 close 方法
            if(response != null){
                response.close();
            }
        }

4.1 示例2

代码语言:javascript
复制
    public static void modifyObject(Cat cat) {
        cat.setName("Jerry");
    }

该方法中 cat 引用副本也指向 cat 对象,可以修改其名称。

代码语言:javascript
复制
  public static void modifyReference(Cat cat) {
        cat = new Cat();
        cat.setName("Spike");
    }

该方法则和示例1 的情况类似,引用副本指向了新的对象,并不会影响到原始的引用。

五、解决办法

知道原因,修改起来就很容易了。

推荐的做法:

代码语言:javascript
复制
try (Response response = client.newCall(request).execute()) {

    if(response.success() && response.body() != null){
        return  SomeUtils.toSomeResult(response.body().string()) ;
    }

    return null;
}

也可以在 someMethod 内部使用 finally 释放资源。

六、启示

6.1 关注 IDEA 警告

其实只要不对 IDEA 的警告视而不见,这种问题基本都可以避免。

image.png
image.png

有两个非常明显的提示,一个是 if 这里警告说条件 always flase ,就需要分析为什么。

调用时也提示 responds always null。

image.png
image.png

还有 someMethod 方法中 responds 是灰色,意味着传入引用没有意义,可以定义成局部变量。

image.png
image.png

6.2 基础不牢,地动山摇

很多人讨厌面试中问一些“八股文”,认为这是在浪费时间。 然而,实际工作中,很多问题都是因为所谓的八股文并没有掌握好才出的问题。

6.3 遵循最佳实践

一个是对于实现了 Closeable 接口的类,推荐使用 try-with-resource 的方式使用和自动释放资源。 写代码时,如果没有必要,请不要定义一个空对象作为参数传到下游,最后再取出对象中的值来使用。如果确实需要这么做,建议使用 Holder 类 或者上下文类。

七、总结

虽然这个问题并不难,但工作中还是会见到很多类似的错误。 希望加大能够养成良好的编码习惯,希望能够真正做到知行合一、学以致用

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、背景
  • 二、场景复现
    • 2.1 示例1
      • 2.2 示例2
        • 传递原始类型变量
        • 传递对象引用变量
    • 三、背景知识
    • 四、揭晓答案
      • 4.1 示例1
        • 4.1 示例2
        • 五、解决办法
        • 六、启示
          • 6.1 关注 IDEA 警告
            • 6.2 基础不牢,地动山摇
              • 6.3 遵循最佳实践
              • 七、总结
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档