啪啪,打脸了!领导说:try-catch必须放在循环体外!

今天给大家带来的是关于try-catch 应该放在循环体外,还是放在循环体内的文章,我们将从性能业务场景分析这两个方面来回答此问题。

很多人对 try-catch 有一定的误解,比如我们经常会把它(try-catch)和“低性能”直接画上等号,但对 try-catch 的本质(是什么)却缺少着最基础的了解,因此我们也会在本篇中对 try-catch 的本质进行相关的探索

性能评测

话不多说,我们直接来开始今天的测试,本文我们依旧使用 Oracle 官方提供的 JMH(Java Microbenchmark Harness,JAVA 微基准测试套件)来进行测试。

首先在 pom.xml 文件中添加 JMH 框架,配置如下:

 <!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->

<dependency>

  <groupId>org.openjdk.jmh</groupId>

  <artifactId>jmh-core</artifactId>

  <version>{version}</version>

</dependency>

完整测试代码如下:

import org.openjdk.jmh.annotations.*;

import org.openjdk.jmh.runner.Runner;

import org.openjdk.jmh.runner.RunnerException;

import org.openjdk.jmh.runner.options.Options;

import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

/**

* try - catch 性能测试

*/

@BenchmarkMode(Mode.AverageTime) // 测试完成时间

@OutputTimeUnit(TimeUnit.NANOSECONDS)

@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 1 轮,每次 1s

@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s

@Fork(1) // fork 1 个线程

@State(Scope.Benchmark)

@Threads(100)

public class TryCatchPerformanceTest {

    private static final int forSize = 1000; // 循环次数

    public static void main(String[] args) throws RunnerException {

        // 启动基准测试

        Options opt = new OptionsBuilder()

                .include(TryCatchPerformanceTest.class.getSimpleName()) // 要导入的测试类

                .build();

        new Runner(opt).run(); // 执行测试

    }

    @Benchmark

    public int innerForeach() {

        int count = 0;

        for (int i = 0; i < forSize; i++) {

            try {

                if (i == forSize) {

                    throw new Exception("new Exception");

                }

                count++;

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

        return count;

    }

    @Benchmark

    public int outerForeach() {

        int count = 0;

        try {

            for (int i = 0; i < forSize; i++) {

                if (i == forSize) {

                    throw new Exception("new Exception");

                }

                count++;

            }

        } catch (Exception e) {

            e.printStackTrace();

        }

        return count;

    }

}


以上代码的测试结果为:

从以上结果可以看出,程序在循环 1000 次的情况下,单次平均执行时间为:循环内包含 try-catch 的平均执行时间是 635 纳秒 ±75 纳秒,也就是 635 纳秒上下误差是 75 纳秒;

循环外包含 try-catch 的平均执行时间是 630 纳秒,上下误差 38 纳秒。

也就是说,在没有发生异常的情况下,除去误差值,我们得到的结论是:try-catch 无论是在for循环内还是for循环外,它们的性能相同,几乎没有任何差别

try-catch的本质

要理解 try-catch 的性能问题,必须从它的字节码开始分析,只有这样我能才能知道 try-catch 的本质到底是什么,以及它是如何执行的。

此时我们写一个最简单的 try-catch 代码:

public class AppTest {

    public static void main(String[] args) {

        try {

            int count = 0;

            throw new Exception("new Exception");

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

}

然后使用javac生成字节码之后,再使用javap -c AppTest的命令来查看字节码文件:

➜ javap -c AppTest

警告: 二进制文件AppTest包含com.example.AppTest

Compiled from "AppTest.java"

public class com.example.AppTest {

  public com.example.AppTest();

    Code:

      0: aload_0

      1: invokespecial #1                  // Method java/lang/Object."<init>":()V

      4: return

  public static void main(java.lang.String[]);

    Code:

      0: iconst_0

      1: istore_1

      2: new          #2                  // class java/lang/Exception

      5: dup

      6: ldc          #3                  // String new Exception

      8: invokespecial #4                  // Method java/lang/Exception."<init>":(Ljava/lang/String;)V

      11: athrow

      12: astore_1

      13: aload_1

      14: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V

      17: return

    Exception table:

      from    to  target type

          0    12    12  Class java/lang/Exception

}

从以上字节码中可以看到有一个异常表:

Exception table:

      from    to  target type

          0    12    12  Class java/lang/Exception

参数说明:

from:表示 try-catch 的开始地址;

to:表示 try-catch 的结束地址;

target:表示异常的处理起始位;

type:表示异常类名称。

从字节码指令可以看出,当代码运行时出错时,会先判断出错数据是否在from到to的范围内,如果是则从target标志位往下执行,如果没有出错,直接goto到return。也就是说,如果代码不出错的话,性能几乎是不受影响的,和正常的代码的执行逻辑是一样的。

业务情况分析

虽然 try-catch 在循环体内还是循环体外的性能是类似的,但是它们所代码的业务含义却完全不同,例如以下代码:

public class AppTest {

    public static void main(String[] args) {

        System.out.println("循环内的执行结果:" + innerForeach());

        System.out.println("循环外的执行结果:" + outerForeach());

    }


    // 方法一

    public static int innerForeach() {

        int count = 0;

        for (int i = 0; i < 6; i++) {

            try {

                if (i == 3) {

                    throw new Exception("new Exception");

                }

                count++;

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

        return count;

    }

    // 方法二

    public static int outerForeach() {

        int count = 0;

        try {

            for (int i = 0; i < 6; i++) {

                if (i == 3) {

                    throw new Exception("new Exception");

                }

                count++;

            }

        } catch (Exception e) {

            e.printStackTrace();

        }

        return count;

    }

}

以上程序的执行结果为:

java.lang.Exception: new Exception

at com.example.AppTest.innerForeach(AppTest.java:15)

at com.example.AppTest.main(AppTest.java:5)

java.lang.Exception: new Exception

at com.example.AppTest.outerForeach(AppTest.java:31)

at com.example.AppTest.main(AppTest.java:6)

循环内的执行结果:5

循环外的执行结果:3

可以看出在循环体内的 try-catch 在发生异常之后,可以继续执行循环;而循环外的 try-catch 在发生异常之后会终止循环。

因此我们在决定 try-catch 究竟是应该放在循环内还是循环外,不取决于性能(因为性能几乎相同),而是应该取决于具体的业务场景

例如我们需要处理一批数据,而无论这组数据中有哪一个数据有问题,都不能影响其他组的正常执行,此时我们可以把 try-catch 放置在循环体内;而当我们需要计算一组数据的合计值时,只要有一组数据有误,我们就需要终止执行,并抛出异常,此时我们需要将 try-catch 放置在循环体外来执行。

总结

本文我们测试了 try-catch 放在循环体内和循环体外的性能,发现二者在循环很多次的情况下性能几乎是一致的。然后我们通过字节码分析,发现只有当发生异常时,才会对比异常表进行异常处理,而正常情况下则可以忽略 try-catch 的执行。但在循环体内还是循环体外使用 try-catch,对于程序的执行结果来说是完全不同的,因此我们应该从实际的业务出发,来决定到 try-catch 应该存放的位置,而非性能考虑

作者:Java中文社群

链接:https://juejin.im/post/5ed5b998f265da76bd1ad012

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350