JDK源码中为什么在方法中创建一个临时变量替代成员变量、类变量

本文通过实验对比了Java中局部变量、实例变量及静态变量的访问效率,结果显示局部变量的访问速度最快。文章还提供了通过使用局部变量来优化实例变量和静态变量访问的方法。

JDK源码中为什么在方法中创建一个临时变量替代成员变量、类变量 又名 临时变量提高Java代码性能

一个Java类中可以存在多种形式的变量,可以是最一般的成员变量;或静态变量;或临时变量。那么这三种变量的存取效率如何?

1、实战出真知

package com.subject.variable;

/**
 * 使用临时变量提高Java代码性能 (转)
 */
public class VariableDemo {

    private long instVar;// 成员变量
    private static long staticVar; // 静态变量

    // 存取类方法中的临时变量
    void tempAccess(long val) {
        int j = 0;// 临时变量
        long startTime = System.currentTimeMillis();
        for (long i = 0; i < val; i++) {
            j += 1;
        }
        long endTime = System.currentTimeMillis();
        System.out.println("temp var: " + (endTime - startTime) + " milli seconds");
    }

    // 存取类的成员变量
    void instanceAccess(long val) {
        long startTime = System.currentTimeMillis();
        for (long i = 0; i < val; i++) {
            instVar += 1;
        }
        long endTime = System.currentTimeMillis();
        System.out.println("instance var: " + (endTime - startTime) + " milli seconds");
    }

    // 存取类的 static 变量
    void staticAccess(long val) {
        long startTime = System.currentTimeMillis();
        for (long i = 0; i < val; i++) {
            staticVar += 1;
        }
        long endTime = System.currentTimeMillis();
        System.out.println("static var: " + (endTime - startTime) + " milli seconds");
    }

    public static void main(String[] args) {
        VariableDemo test = new VariableDemo();
        long count=10*1000*1000*1000L;
        test.tempAccess(count);
        test.instanceAccess(count);
        test.staticAccess(count);
    }
}

计算结果:

temp var: 2987 milli seconds
instance var: 4039 milli seconds
static var: 3996 milli seconds

这段代码中的每个方法都执行相同的循环,并反复相同的次数。唯一的不同是每个循环使一个不同类型的变量递增。方法 tempAccess 使一个局部堆栈变量递增,instanceAccess 使类的一个成员实例变量递增,而 staticAccess 使类的一个 static 变量递增。
从结果中可以发现,instanceAccess 和 staticAccess 的执行时间基本相同。但是,tempAccess 要快两到三倍。

2、JVM存取变量机制
存取堆栈变量如此快是因为JVM 存取堆栈变量比它存取 成员变量 或 静态变量执行的操作少。
JVM 是一种基于堆栈的虚拟机,因此优化了对堆栈数据的存取和处理。所有局部变量都存储在一个局部变量表中,在 Java 操作数堆栈中进行处理,并可被高效地存取。
存取 成员变量 和 静态变量 成本更高,因为 JVM 必须使用代价更高的操作码,并从常数存储池中存取它们。(常数存储池保存一个类型所使用的所有类型、字段和方法的符号引用。)通常,在第一次从常数存储池中访问 static 变量或实例变量以后,JVM 将动态更改字节码以使用效率更高的操作码。尽管有这种优化,堆栈变量的存取仍然更快。

3、那么如何优化代码
考虑到这些事实,就可以重新构建前面的代码,以便通过存取堆栈变量而不是实例变量或 static 变量使操作更高效。

package com.subject.variable;

/**
 * 使用临时变量提高Java代码性能 (转)
 */
public class VariableDemo2 {

    private long instVar;// 成员变量
    private static long staticVar; // 静态变量

    // 存取类方法中的临时变量
    void tempAccess(long val) {
        int j = 0;// 临时变量
        long startTime = System.currentTimeMillis();
        for (long i = 0; i < val; i++) {
            j += 1;
        }
        long endTime = System.currentTimeMillis();
        System.out.println("temp var: " + (endTime - startTime) + " milli seconds");
    }

    // 存取类的成员变量
    void instanceAccess(long val) {
        long startTime = System.currentTimeMillis();
        long tmp=instVar;
        for (long i = 0; i < val; i++) {
            tmp += 1;
        }
        instVar=tmp;
        long endTime = System.currentTimeMillis();
        System.out.println("instance var: " + (endTime - startTime) + " milli seconds");
    }

    // 存取类的 static 变量
    void staticAccess(long val) {
        long startTime = System.currentTimeMillis();
        long tmp=staticVar;
        for (long i = 0; i < val; i++) {
            tmp += 1;
        }
        staticVar=tmp;
        long endTime = System.currentTimeMillis();
        System.out.println("static var: " + (endTime - startTime) + " milli seconds");
    }

    public static void main(String[] args) {
        VariableDemo2 test = new VariableDemo2();
        long count=10*1000*1000*1000L;
        test.tempAccess(count);
        test.instanceAccess(count);
        test.staticAccess(count);
    }
}

计算结果:

temp var: 2973 milli seconds
instance var: 3074 milli seconds
static var: 3018 milli seconds

转载来源
http://blog.youkuaiyun.com/yejin191258966/article/details/40195933
原文样式已经凌乱了,特重新进行了整理,并统一了术语,比如实例变量/成员变量,静态变量/static变量

<think>我们正在讨论JDK1.8中匿名内部访问局部变量的改进。根据引用[1]、[2]、[3]、[4]的内容,我们可以总结如下: 在JDK1.8之前(如JDK1.6、1.7),匿名内部访问局部变量时,该局部变量必须显式声明为final。而到了JDK1.8,引入了一个重要的改进:对于局部内部和匿名内部访问的局部变量,如果该变量在初始化后没有被重新赋值(即满足“有效final”,也称为“事实final”),那么可以不用显式声明为final,编译器会自动将其视为final变量(即编译器会隐式地添加final修饰)。但是,如果尝试在内部中修改这个变量,或者在其所属的方法中修改它,都会导致编译错误。 引用[1]指出:在JDK8之前,匿名内部访问局部变量必须用final修饰。引用[2]提到:JDK1.8之后,内部访问局部变量时,编译器会隐式地为使用到的局部变量添加final修饰,因此可以不用显式写final,但不能修改。引用[3]也说明:在Java8及以后,被局部内部、匿名内部访问的局部变量会自动使用final修饰(语法糖)。引用[4]通过代码示例展示了在JDK1.8中,匿名内部可以访问方法中的局部变量而不用加final修饰,但尝试修改变量会导致错误(如示例中注释了`a=2`)。 因此,JDK1.8的改进可以总结为: 1. 不再强制要求显式使用final关键字修饰被匿名内部访问的局部变量。 2. 但要求该局部变量实际上是final的(即一旦赋值后就不再改变),编译器会自动进行final处理。 3. 如果变量不是有效final的(即在后续被重新赋值),则不能在匿名内部中使用。 这种改进使得代码更加简洁,同时保持了线程安全和数据一致性(因为变量实际上仍然是不可变的)。 下面我们给出一个示例代码,展示JDK1.8中的行为: ```java public class Test { public static void main(String[] args) { int a = 10; // 这是一个有效final变量,因为赋值后没有改变 // 匿名内部 Runnable r = new Runnable() { @Override public void run() { // 可以读取a,因为a是有效final的 System.out.println(a); // 但不能修改a,下面的语句如果取消注释会编译错误 // a = 20; } }; new Thread(r).start(); // 如果在这修改a的值,那么上面的匿名内部中使用a就会编译失败 // a = 30; // 如果取消注释,会导致上面的匿名内部中访问a出错 } } ``` 为什么JDK1.8要做这样的改进?主要是为了简化代码,同时保持与之前版本相同的语义(变量不可变性)。这样,程序员可以少写一些final关键字,使代码更简洁,但又不破坏原有的规则(即内部中访问的局部变量必须是final的,以保证数据一致性)。 值得注意的是,lambda表达式同样遵循有效final规则。例如: ```java public class TestLambda { public static void main(String[] args) { int a = 10; Runnable r = () -> { System.out.println(a); // 正确,a是有效final // a = 20; // 错误,不能修改 }; new Thread(r).start(); } } ``` 总结JDK1.8的改进: - 匿名内部(以及局部内部)访问的局部变量可以是有效final的(即不用显式写final,但实际效果等同于final)。 - 编译器在编译时会自动为这些变量加上final修饰(在生成的字节码中体现)。 - 这样既保持了原有语义(变量不可变),又简化了代码。 相关问题: 1. 为什么JDK1.8允许匿名内部访问有效final的局部变量而不强制写final关键字? 2. 在JDK1.8中,如果尝试在匿名内部中修改一个有效final的局部变量会发生什么? 3. 有效final(effectively final)在lambda表达式中的应用规则是什么? 4. 为什么匿名内部和lambda表达式要求访问的外部局部变量必须是final或有效final的? 5. 在JDK1.8中,匿名内部和lambda表达式在变量捕获方面有何异同? 以上问题都可以从变量生命周期、线程安全和编译器的实现机制等方面进行探讨。 </think>### JDK1.8匿名内部访问局部变量的改进解析 在JDK1.8中,Java对匿名内部访问局部变量的机制进行了重要改进,主要解决了早期版本必须显式声明`final`的限制问题: #### 1. **有效final(Effectively Final)机制** JDK1.8引入**有效final**概念:只要局部变量在初始化后未被修改(即满足事实上的不可变性),编译器就会自动将其视为`final`变量处理,无需显式声明: ```java // JDK1.8有效final示例 void demo() { int count = 5; // 未被修改 → 有效final Runnable task = new Runnable() { @Override public void run() { System.out.println(count); // ✔️ 编译通过 } }; } ``` 此时`count`虽然没有`final`修饰,但因其未被重新赋值,编译器自动将其识别为final变量[^2][^3]。 #### 2. **隐式final处理(语法糖)** 编译器在字节码层面会自动为满足条件的局部变量添加`final`修饰符: ```java // 源码(开发者视角) String message = "Hello"; // 编译后的字节码(等效) final String message = "Hello"; ``` 这种处理对开发者透明,称为**语法糖**[^3]。 #### 3. **修改限制保持不变** 虽然不再强制显式`final`修饰,但**变量仍不可被修改**: ```java void invalidDemo() { int value = 10; Runnable task = new Runnable() { @Override public void run() { // value = 20; // ❌ 编译错误:有效final变量不可修改 System.out.println(value); } }; } ``` 尝试修改会触发`Local variable value defined in an enclosing scope must be final or effectively final`错误[^2][^4]。 #### 4. **Lambda表达式的统一规则** 该改进同时适用于Lambda表达式: ```java void lambdaDemo() { String name = "JDK8"; Runnable r = () -> System.out.println(name); // ✔️ 有效final捕获 // name = "Modified"; // ❌ 修改会破坏有效final } ``` Lambda表达式与匿名内部采用相同的变量捕获规则[^4][^5]。 --- ### 改进原理与必要性 1. **线程安全保证** 匿名内部可能在其他线程执行,而局部变量存储在栈帧中。通过强制不可变性($$ \text{变量} V \xrightarrow{\text{初始化}} \text{不变} $$),防止多线程下的数据不一致问题[^1][^3]。 2. **生命周期解耦** 局部变量生命周期随方法结束而终结,而内部对象可能持续存在。通过值拷贝($$ V_{\text{外部}} \rightarrow V_{\text{内部副本}} $$)解决此问题,final/有效final确保拷贝值的一致性[^1]。 3. **字节码实现** 编译器自动生成合成字段`val$varName`存储变量副本: ```java // 源码 Runnable r = new Runnable() { void run() { use(var); } }; // 等效编译结果 final outerClass$1 implements Runnable { private final Type val$var; // 自动生成的final字段 outerClass$1(Type var) { this.val$var = var; } void run() { use(val$var); } } ``` --- ### 新旧版本对比 | 特性 | JDK ≤ 1.7 | JDK ≥ 1.8 | |---------------------|------------------------|------------------------| | **final声明** | 强制显式声明 | 可选(支持隐式final) | | **变量修改** | 完全禁止 | 完全禁止 | | **Lambda支持** | 不适用 | 遵循相同规则 | | **编译器处理** | 要求严格final | 自动检测有效final | > **最佳实践**:尽管JDK1.8允许省略`final`,显式声明`final`仍能提高代码可读性和意图清晰度[^3][^5]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明月(Alioo)

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值