阿里面试:面试官问java变量声明在循环体内还是循环体外?

简介: 你平时都是怎么定义的

引言

最近刷知乎的时候看到一个比较有意思的问题,变量声明在循环体内还是循环体外?这个问题有人认为应该定义循环体外,不应该定义在循环体内。很多java代码优化建议都有这么一条建议:
循环内不要不断创建对象引用
例如:

for (int i = 1; i <= count; i++){

Object obj = new Object();

}

这种做法会导致内存中有countObject对象引用存在,count很大的话,就耗费内存了,建议为改为:

Object obj = null;
for (int i = 0; i <= count; i++) {

obj = new Object();

}

这样的话,内存中只有一份Object对象引用,每次new Object()的时候,Object对象引用指向不同的Object罢了,但是内存中只有一份,这样就大大节省了内存空间了。这条建议应该也出现过在很多公司的代码规范上了吧。下面我们就来分析下变量声明在循环体内和变量声明循环体外的情况。

效率对比

首先我们先来看看写在循环体内和询环体外的效率比对,测试代码如下:

/**

  • @author: 公众号【java金融】
  • @Date:
  • @Description:
    */

@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2) // 预热 2 轮,每次 1s
@Measurement(iterations = 5) // 测试 5 轮,每次 1s
@Fork(1) // fork 1 个线程
@State(Scope.Thread)
public class ForEachBenchMark {

public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder()
            .include(ForEachBenchMark.class.getSimpleName())
            .result("result.json")
            .resultFormat(ResultFormatType.JSON).build();
    new Runner(opt).run();
}

@Param(value = {"10", "50", "100"})
private int length;
/**
 * 循环体外创建对象
 * @param blackhole
 */
@Benchmark
public void outsideLoop(Blackhole blackhole) {
    Object object = null;
    for (int i = 0; i < length; i++) {
        object = new Object();
        blackhole.consume(object);
    }
}

/**
 * 循环体内创建对象
 * @param blackhole
 */
@Benchmark
public void insideLoop(Blackhole blackhole) {
    for (int i = 0; i < length; i++) {
        Object object = new Object();
        blackhole.consume(object);

    }

}

}

测试结果如下:

Benchmark (length) Mode Cnt Score Error Units
ForEachBenchMark.insideLoop 10 avgt 5 58.629 ± 8.857 ns/op
ForEachBenchMark.insideLoop 50 avgt 5 293.726 ± 1.856 ns/op
ForEachBenchMark.insideLoop 100 avgt 5 587.185 ± 40.424 ns/op
ForEachBenchMark.outsideLoop 10 avgt 5 59.563 ± 5.057 ns/op
ForEachBenchMark.outsideLoop 50 avgt 5 305.829 ± 27.476 ns/op
ForEachBenchMark.outsideLoop 100 avgt 5 584.853 ± 20.289 ns/op

在这里插入图片描述
我们可以发现不管在循环外创建对象和循环内创建对象时间几乎都是一样的。

字节码对比

下面我们准备两个测试类

public class InsideTest {

public static int count = 100;
public List<Object> insideLoop() {
    List<Object> list = new ArrayList<>();
    int n = 0;
    for (; ; ) {
        if (n > count) {
          break;
        }
        Object o = new Object();
        list.add(o);
    }
    Object b = 2;
    return list;
}

}

public class OutsideTest {

public static int count = 100;

public List

    List<Object> list = new ArrayList<>();
   Object o = null;
   int n = 0;
   for (; ; ) {
       if (n > count) {
           break;
       }
        o = new Object();
        list.add(o);
    }
    Object b = 2;
    return list;
}

这两个编译后字节码几乎一模一样,除了循环体外(OutsideTest )常量池多了一个Object o = null变量还有的话就是LocalVariableTable有点区别,变量在循环体内的话公用了一个变量槽(o和b变量)
outsideLoop在stack frame中定义了4个slot, 而intsideLoop只定义了3个slot 在outsideLoop中,变量o和b分别占用了不同的slot,在intsideLoop中,变量o和b复用一个slot。所以outsideLoop的stack frame比intsideLoop多占用1个solt内存。
执行以下命令就可以找到字节码中的LocalVariableTable。

javac -g OutsideTest.java
javap -v OutsideTest.class

LocalVariableTable:

    Start  Length  Slot  Name   Signature
       28       8     3     o   Ljava/lang/Object;
        0      46     0  this   Lcom/workit/autoconfigure/autoconfigure/controller/InsideTest;
        8      38     1  list   Ljava/util/List;
       10      36     2     n   I
       44       2     3     b   Ljava/lang/Object;

LocalVariableTable:

    Start  Length  Slot  Name   Signature
        0      49     0  this   Lcom/workit/autoconfigure/autoconfigure/controller/OutsideTest;
        8      41     1  list   Ljava/util/List;
       10      39     2     o   Ljava/lang/Object;
       12      37     3     n   I
       47       2     4     b   Ljava/lang/Object;

这是比较极端的情况下有1个solt的差距,如果把上述的代码 Object b = 2;就不会存在solt复用了。

总结

整体看下来貌似内存和效率都差不多。从“局部变量作用域最小化”原则上来说,变量声明在循环体内更合适一点,这样代码的阅读性更好。

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
  • 感谢您的阅读,十分欢迎并感谢您的关注。
    在这里插入图片描述
目录
相关文章
|
3月前
|
人工智能 Java 开发者
阿里出手!Java 开发者狂喜!开源 AI Agent 框架 JManus 来了,初次见面就心动~
JManus是阿里开源的Java版OpenManus,基于Spring AI Alibaba框架,助力Java开发者便捷应用AI技术。支持多Agent框架、网页配置、MCP协议及PLAN-ACT模式,可集成多模型,适配阿里云百炼平台与本地ollama。提供Docker与源码部署方式,具备无限上下文处理能力,适用于复杂AI场景。当前仍在完善模型配置等功能,欢迎参与开源共建。
1739 58
阿里出手!Java 开发者狂喜!开源 AI Agent 框架 JManus 来了,初次见面就心动~
|
4月前
|
Java
Java编程:理解while循环的使用
总结而言, 使用 while 迴圈可以有效解决需要多次重复操作直至特定條件被触发才停止執行任务场景下问题; 它简单、灵活、易于实现各种逻辑控制需求但同时也要注意防止因邏各错误导致無限迁璇発生及及時處理可能発生异常以确保程序稳定运作。
421 0
|
5月前
|
负载均衡 架构师 Cloud Native
阿里面试:服务与发现 ,该选 CP 还是 AP?为什么?
阿里面试:服务与发现 ,该选 CP 还是 AP?为什么?
阿里面试:服务与发现 ,该选  CP 还是 AP?为什么?
|
6月前
|
监控 Java 数据安全/隐私保护
阿里面试:SpringBoot启动时, 如何执行扩展代码?你们项目 SpringBoot 进行过 哪些 扩展?
阿里面试:SpringBoot启动时, 如何执行扩展代码?你们项目 SpringBoot 进行过 哪些 扩展?
|
6月前
|
SQL Java 数据库连接
阿里腾讯互联网公司校招 Java 面试题总结及答案解析
本文总结了阿里巴巴和腾讯等互联网大厂的Java校招面试题及答案,涵盖Java基础、多线程、集合框架、数据库、Spring与MyBatis框架等内容。从数据类型、面向对象特性到异常处理,从线程安全到SQL优化,再到IOC原理与MyBatis结果封装,全面梳理常见考点。通过详细解析,帮助求职者系统掌握Java核心知识,为校招做好充分准备。资源链接:[点击下载](https://pan.quark.cn/s/14fcf913bae6)。
213 2
|
2月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
199 1
|
2月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
225 1

热门文章

最新文章