解密 Java Lambda 表达式中的 “effectively final“ 陷阱

news2025/2/7 17:35:40

文章目录

      • 1. 引言 (Introduction)
        • 1.1. 核心问题
        • 1.2. 博客目标
        • 1.3. 目标读者
        • 1.4. 阅读收获
      • 2. 重现错误 (Reproducing the Error)
        • 2.1. 代码示例 (LambdaErrorExampleCorrected.java)
        • 2.2. 逐步演示
          • 2.2.1. 没有错误的代码版本 (list 满足 effectively final)
          • 2.2.2. 导致错误的代码版本 (在 lambda 表达式之后 尝试重新赋值 names 变量的引用)
        • 2.3. 动手实践
      • 3. 理解 Lambda 表达式和变量捕获 (Understanding Lambda Expressions and Variable Capture)
        • 3.1. 什么是 Lambda 表达式?
        • 3.2. 什么是变量捕获?
        • 3.3. Lambda 表达式如何进行变量捕获?
          • 基本类型变量 (Primitive Type Variables)
          • 引用类型变量 (Reference Type Variables)
      • 4. `final` 和 Effectively Final (`final` vs. Effectively Final)
        • 4.1. `final` 关键字
        • 4.2. Effectively Final (Java 8 引入)
        • 4.3. 对比:`final` vs. Effectively Final
      • 5. 深入理解:为什么需要 Effectively Final? (Why Effectively Final?)
        • 5.1. 数据一致性问题
        • 5.2. 线程安全问题
      • 6. 总结 (Conclusion)

1. 引言 (Introduction)

您是否在编写 Java Lambda 表达式时遇到过类似这样的困惑:明明代码看起来只是简单地使用了一个变量,却被编译器报错 "Variable used in lambda expression should be final or effectively final"? 就像下面这段代码,我们希望在一个 Lambda 表达式中使用一个列表,然后在之后修改这个列表的 引用,却遭遇了编译器的阻拦:

import java.util.ArrayList;
import java.util.List;

public class LambdaErrorExampleCorrected {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");

        names.forEach(name -> System.out.println("Hello, " + name));

        // **🔴 编译错误! 尝试重新赋值 names 变量的引用**
        names = new ArrayList<>();
        // 或者
        // names = null;
    }
}

这段代码的初衷是先使用一个名字列表,通过 Lambda 表达式打印问候语,然后再将 names 变量指向一个新的列表(或者 null)。 然而,Java 编译器却明确报错,阻止了我们重新赋值 names 变量的企图。

1.1. 核心问题

为什么 Java 会有如此看似苛刻的限制? 为什么 Lambda 表达式对变量的使用有 finaleffectively final 的要求? 我们经常听到的 finaleffectively final, 它们的真正含义和区别又是什么? 这背后蕴藏着 Java Lambda 表达式设计的核心原则:对数据一致性和行为可预测性的极致追求

1.2. 博客目标

在本篇博客中,我们将拨开迷雾,深入解析 Java Lambda 表达式中 “effectively final” 概念的本质。 我们将从重现这个常见的编译错误出发,深入剖析 Lambda 表达式的变量捕获机制,精确理解 finaleffectively final 的真正含义,最终掌握避开 “effectively final” 陷阱,编写健壮高效 Lambda 表达式的最佳实践方法。

1.3. 目标读者

本文的目标读者: 对 Java Lambda 表达式有基本了解,但对 “effectively final” 概念及其背后的原理感到困惑的 Java 开发者。

1.4. 阅读收获

阅读本文后,您将能够:

  • 准确理解 "Variable used in lambda expression should be final or effectively final" 错误的根本原因。
  • 彻底掌握 Lambda 表达式的变量捕获机制,特别是对于引用类型变量的捕获方式。
  • 精准区分 finaleffectively final 的概念,理解它们在 Lambda 表达式中的作用。
  • 识别并避免 “effectively final” 陷阱,编写更加可靠和易于维护的 Lambda 表达式代码。

让我们一起踏上解密 “effectively final” 之旅,彻底扫清 Java Lambda 表达式使用中的障碍!

2. 重现错误 (Reproducing the Error)

为了更深刻地理解 “effectively final” 错误,实践是最好的老师。 接下来,我们将提供一个完整的、可编译的代码示例,并逐步演示错误是如何被触发的。

2.1. 代码示例 (LambdaErrorExampleCorrected.java)
import java.util.ArrayList;
import java.util.List;

public class LambdaErrorExampleCorrected {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");

        // **✅ 没有错误的代码版本 (list 满足 effectively final)**
        System.out.println("--- 没有错误的版本 ---");
        names.forEach(name -> System.out.println("Hello, " + name));


        // **❌ 导致错误的代码版本 (在 lambda 表达式之后 尝试重新赋值 names 变量的引用)**
        System.out.println("\n--- 导致错误的版本 ---");
        List<String> errorNames = new ArrayList<>();
        errorNames.add("Alice");
        errorNames.add("Bob");

        errorNames.forEach(name -> System.out.println("Hello, " + name));
        // **🔴 编译错误!  尝试重新赋值 errorNames 变量**
        // errorNames = new ArrayList<>();
        errorNames = null;

    }
}
2.2. 逐步演示
2.2.1. 没有错误的代码版本 (list 满足 effectively final)

首先,运行代码中 “没有错误的版本” 部分。 这段代码创建了一个 names 列表,并使用 forEach 方法和一个 Lambda 表达式来遍历打印每个名字的问候语。 关键在于,在这个版本中,我们在 Lambda 表达式之后,没有对 names 变量进行任何重新赋值引用的操作。 这段代码能够成功编译并运行,控制台输出如下:

--- 没有错误的版本 ---
Hello, Alice
Hello, Bob
2.2.2. 导致错误的代码版本 (在 lambda 表达式之后 尝试重新赋值 names 变量的引用)

现在,我们尝试运行代码中 “导致错误的版本” 部分。 这段代码与之前的版本极其相似,核心的区别在于,我们在 errorNames.forEach(...) 之后,尝试对 errorNames 变量进行重新赋值引用的操作, 例如 errorNames = new ArrayList<>(); 或者 errorNames = null;。 当我们尝试编译这段代码时,编译器会毫不犹豫地报错:

Error:(25, 24) java: Local variable errorNames defined in an enclosing scope must be final or effectively final

清晰的错误信息,精准定位问题:

编译器清楚地指出错误发生在尝试重新赋值 errorNames 变量的代码行,并给出明确的错误提示: Local variable errorNames defined in an enclosing scope must be final or effectively final (定义在封闭作用域中的局部变量 errorNames 必须是 final 或 effectively final)。 这有力地证明了,“effectively final” 限制的核心在于,Lambda 表达式引用的外部局部变量,其 引用 在 Lambda 表达式使用后,不能被重新赋值

2.3. 动手实践

动手实践,加深理解:

为了更深入地理解 “effectively final” 错误,我强烈建议您亲自操作。 将上述 LambdaErrorExampleCorrected.java 代码复制到您的 Java 开发环境 (IDE) 中,分别编译并运行 “没有错误的版本” 和 “导致错误的版本”。 观察编译结果和运行输出,尤其是编译报错信息。 亲身体验错误,能够帮助您从根本上理解 “effectively final” 限制的本质

3. 理解 Lambda 表达式和变量捕获 (Understanding Lambda Expressions and Variable Capture)

要彻底理解编译器报错的缘由,我们必须深入理解 Lambda 表达式的工作机制,尤其是 变量捕获 (Variable Capture) 这个至关重要的概念。

3.1. 什么是 Lambda 表达式?

简而言之,Lambda 表达式是 Java 8 引入的一种轻量级、简洁的方式来表示匿名函数。 您可以将其视为一段可传递的代码块,它可以作为参数传递给方法,也可以赋值给变量。 Lambda 表达式的强大之处在于其简洁性灵活性,它使得我们可以以更加函数式编程的风格来编写 Java 代码。

在本篇博客中,我们聚焦于 Lambda 表达式与 变量捕获 紧密相关的概念。 创建 Lambda 表达式不仅仅是定义一段独立的代码,它还可能需要访问和使用在其定义的作用域中声明的变量。 这就引出了变量捕获。

3.2. 什么是变量捕获?

变量捕获 是指 Lambda 表达式在其函数体内部访问和使用其定义所在作用域中声明的变量。 回顾之前的代码示例:

List<String> names = new ArrayList<>();
names.forEach(name -> System.out.println("Hello, " + name));

Lambda 表达式 name -> System.out.println("Hello, " + name) 访问了外部作用域中定义的 names 列表。 这个过程即为变量捕获。 Lambda 表达式 “捕获”names 变量,以便在 Lambda 表达式内部能够使用它。

3.3. Lambda 表达式如何进行变量捕获?

Java Lambda 表达式在处理变量捕获时,采取了以下关键策略:

基本类型变量 (Primitive Type Variables)

对于基本类型变量,Lambda 表达式捕获的是该变量 值的副本 (copy of value)这意味着,即使在 Lambda 表达式外部修改了基本类型变量的值,Lambda 表达式内部使用的仍然是最初捕获的副本值,外部的修改对 Lambda 表达式内部没有任何影响,反之亦然。 两者之间是完全隔离的。

引用类型变量 (Reference Type Variables)

对于引用类型变量,Lambda 表达式捕获的是该变量 引用的副本 (copy of reference)而非对象本身的副本这意味着,Lambda 表达式和外部作用域中的代码,实际上是通过 两个不同的引用副本 指向 堆内存中的同一个对象。因此,如果通过任何一个引用修改了对象 内部的状态 (例如,向 List 中添加元素,修改对象的字段值),另一个引用仍然能够访问到修改后的对象状态。 但是,至关重要的是,如果尝试修改 引用本身 (例如,将引用指向一个新的对象,或者赋值为 null), Lambda 表达式内部捕获的引用副本仍然指向 原来的对象,与外部作用域的新引用就此分离,互不影响。这正是 “effectively final” 限制起作用的关键所在。

4. final 和 Effectively Final (final vs. Effectively Final)

为了彻底弄清楚 “effectively final” 的概念,我们需要先理解 final 关键字,以及它与 effectively final 之间的联系和区别。

4.1. final 关键字

明确定义: final 关键字 在 Java 中是一个修饰符,可以用来修饰类、方法和变量。 当 final 用于修饰变量时,它表示被修饰的变量只能被 赋值一次

作用: final 关键字的主要作用是确保变量的值(或引用)在初始化后不会被改变。 一旦 final 变量被赋值,就不能再对它进行重新赋值。 这提供了一种不可变性的保证。

代码示例:

public class FinalExample {
    public static void main(String[] args) {
        final int number = 10; // 声明 final 变量并赋值

        System.out.println("Number: " + number);

        // **🔴 编译错误! 尝试重新赋值 final 变量**
        // number = 20;
    }
}

在上面的例子中,number 被声明为 final int 类型,并被赋值为 10。 任何尝试在之后重新赋值 number 的操作,都会导致编译错误。

4.2. Effectively Final (Java 8 引入)

定义: Effectively Final 是 Java 8 引入的一个新概念,它用来描述一种变量的状态。 即使一个局部变量没有被显式地声明为 final,但如果在初始化之后,它的值(或引用)事实上没有被修改,那么这个变量就是 effectively final 的

条件: 要成为 effectively final 的变量,需要满足以下条件:

  1. 只被赋值一次:变量必须在声明时或之后只被赋值一次。
  2. 在 Lambda 表达式中使用之前没有被修改:变量在被 Lambda 表达式捕获之前,不能被修改。

代码示例:

import java.util.ArrayList;
import java.util.List;

public class EffectivelyFinalExample {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(); // names 变量没有声明为 final

        names.add("Alice"); // 第一次修改 names 指向的 ArrayList 对象的内容
        names.add("Bob");  // 第二次修改 names 指向的 ArrayList 对象的内容

        // **✅ names 变量是 effectively final 的,因为它的引用没有被重新赋值**
        names.forEach(name -> System.out.println("Hello, " + name));

        // **❌ 如果在这里重新赋值 names 变量的引用,names 就不再是 effectively final 的了 (如果 Lambda 在重新赋值后使用)**
        // names = new ArrayList<>();
    }
}

在上面的例子中,names 变量没有被声明为 final,但是它仍然是 effectively final 的,因为在 Lambda 表达式 names.forEach(...) 使用之前,names 变量的引用本身 没有被重新赋值。 我们只是修改了 names 指向的 ArrayList 对象的内容,但这并不影响 names 变量的 effectively final 状态。

4.3. 对比:final vs. Effectively Final

相同点: final 和 effectively final 的变量都保证了变量的值(或引用)在初始化后不会被改变。 对于 Lambda 表达式而言,它们都满足了 Lambda 表达式对捕获变量不可变性的要求。

不同点:

  1. 声明方式不同: final显式声明,需要使用 final 关键字来修饰变量。 effectively final 是 隐式推断,不需要显式关键字,由编译器根据变量的使用情况自动推断。

  2. 约束力不同: final 具有更强的约束力。 一旦变量被声明为 final,编译器会严格检查,确保在任何地方都不会对 final 变量进行重新赋值。 而 effectively final 的约束力相对较弱,它只在 Lambda 表达式的变量捕获上下文中起作用。 在 Lambda 表达式之外,即使变量是 effectively final 的,我们仍然可以修改它的值(只要不违反 effectively final 的条件)。

举例说明:

public class FinalVsEffectivelyFinal {
    public static void main(String[] args) {
        // **final 变量**
        final int finalNumber = 10;
        // finalNumber = 20; // 🔴 编译错误! final 变量不能重新赋值

        // **effectively final 变量**
        int effectivelyFinalNumber = 10;
        effectivelyFinalNumber = 20; // ✅  在 Lambda 表达式之外,effectively final 变量可以被修改 (只要 Lambda 表达式没有捕获它,或者在捕获之后没有修改)

        // 使用 Lambda 表达式捕获 effectively final 变量
        Runnable lambdaTask = () -> System.out.println("Effectively Final Number in Lambda: " + effectivelyFinalNumber);

        // **❌ 如果在 Lambda 表达式捕获 effectivelyFinalNumber 之后,再修改 effectivelyFinalNumber 变量的引用,就会报错 (如果在 Lambda 表达式中使用)**
        // effectivelyFinalNumber = 30; // 如果 lambdaTask 被执行,这里会导致数据不一致,因此 Java 编译器禁止这样做 (如果在 lambdaTask 中使用了 effectivelyFinalNumber)


        lambdaTask.run();
    }
}

final 和 effectively final 都服务于同一个目的: 确保 Lambda 表达式捕获的变量在 Lambda 表达式执行期间保持不变,从而保证数据一致性和行为的可预测性final 是显式的、强制性的,而 effectively final 是隐式的、更灵活的,它允许开发者在不显式使用 final 关键字的情况下,也能享受到 Lambda 表达式带来的便利。

5. 深入理解:为什么需要 Effectively Final? (Why Effectively Final?)

现在我们理解了什么是 effectively final,但更重要的是要理解 为什么 Java 要强制要求 Lambda 表达式捕获的变量必须是 final 或 effectively final 的? 这背后的原因主要与 数据一致性线程安全 这两个关键问题有关。

5.1. 数据一致性问题

详细解释: 如果允许 Lambda 表达式捕获的变量在其创建后被修改,就会导致 Lambda 表达式内部和外部的数据不一致,从而产生难以预测的行为和逻辑混乱

Lambda 表达式本质上是一个闭包 (Closure)。 闭包的一个重要特性是它可以“记住”创建时的环境,并访问和使用环境中的变量。 如果允许在 Lambda 表达式创建后修改被捕获的变量,那么 Lambda 表达式在不同时刻访问到的变量值可能会发生变化,这会破坏 Lambda 表达式的 “快照”特性,使其行为变得难以理解和调试。

代码示例:Counter 示例

我们来看一个经典的计数器示例,来生动地展示数据不一致可能导致的问题:

public class CounterExample {
    public static void main(String[] args) {
        int counter = 0;

        Runnable incrementCounter = () -> {
            // 假设允许修改外部变量
            counter++; // 🔴 如果允许,这里会修改外部的 counter 变量
        };

        for (int i = 0; i < 5; i++) {
            incrementCounter.run(); // 多次执行 Lambda 表达式
            System.out.println("Counter in loop: " + counter); // 打印循环中的 counter 值
        }

        System.out.println("Final Counter: " + counter); // 打印最终的 counter 值
    }
}

假设 Java 允许 Lambda 表达式修改外部变量 counter (实际上 Java 不允许,这里只是为了演示)。 在这种假设情况下,由于 Lambda 表达式每次执行都会修改外部的 counter 变量,循环内和循环外的 counter 值都会不断变化,程序的行为将变得非常难以预测。 Lambda 表达式的行为不再像一个“快照”,而是会随着外部变量的改变而动态变化,这会给程序的正确性和可维护性带来巨大的挑战。

代码示例:修改 List 示例

再回顾我们之前的 List 示例,虽然修改 List 的 内容 不会导致错误,但如果我们允许修改 List 的 引用,同样会产生数据不一致的问题:

import java.util.ArrayList;
import java.util.List;

public class ListDataInconsistencyExample {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");

        Runnable printNames = () -> {
            System.out.println("Names in Lambda: " + names); // Lambda 表达式捕获 names 列表
        };

        printNames.run(); // 第一次执行 Lambda 表达式,打印 names 列表

        names = new ArrayList<>(); // 🔴  如果允许,在这里重新赋值 names 变量的引用
        names.add("Charlie");
        names.add("David");

        printNames.run(); // 第二次执行 Lambda 表达式,打印 names 列表
    }
}

同样假设 Java 允许 Lambda 表达式捕获的 names 变量在其创建后被重新赋值引用 (实际上 Java 不允许)。 在这种假设情况下,第一次执行 printNames.run() 时,Lambda 表达式会打印最初的 names 列表 (“Alice”, “Bob”)。 但是,由于我们在之后重新赋值了 names 变量的引用,第二次执行 printNames.run() 时,Lambda 表达式打印的 names 列表就会变成新的列表 (“Charlie”, “David”)。 同一个 Lambda 表达式,在不同的时刻执行,由于外部变量的改变,输出了不同的结果,这显然破坏了数据一致性,让程序的行为变得难以理解。

强调 “快照” 特性: 总结来说,Java 强制要求 Lambda 表达式捕获的变量必须是 final 或 effectively final, 核心目的就是为了维护 Lambda 表达式的 “快照” 特性, 保证数据一致性, 让 Lambda 表达式的行为更加可预测和易于理解。 Lambda 表达式应该像一个“函数”,给定输入,输出始终是确定的,而不会受到外部环境变化的影响。

5.2. 线程安全问题

简要提及: 除了数据一致性问题,线程安全 也是 Java 强制要求 effectively final 的一个重要考量因素,尤其是在多线程环境下。

竞态条件和不可预测的结果: 在多线程环境中,如果多个线程同时访问和修改同一个非 effectively final 的变量,就可能导致竞态条件 (Race Condition) 和不可预测的结果。 Lambda 表达式可能会在不同的线程中执行,如果 Lambda 表达式捕获的变量不是 effectively final 的,并且可以被外部线程修改,那么就可能出现线程安全问题。

示例说明:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadSafetyExample {
    public static void main(String[] args) {
        int counter = 0; // 非 effectively final 变量

        ExecutorService executor = Executors.newFixedThreadPool(5);

        for (int i = 0; i < 10; i++) {
            executor.submit(() -> {
                // 假设允许修改外部变量
                for (int j = 0; j < 1000; j++) {
                    counter++; // 🔴  多个线程同时修改非 effectively final 变量,可能导致线程安全问题 (如果允许)
                }
            });
        }

        executor.shutdown();

        // ... 等待所有线程执行完成 ...

        System.out.println("Final Counter (Expected 10000): " + counter); // 最终的 counter 值可能小于 10000,结果不可预测
    }
}

同样假设 Java 允许 Lambda 表达式修改外部变量 counter (实际上 Java 不允许)。 在这种假设情况下,多个线程会同时执行 Lambda 表达式,并尝试递增同一个非 effectively final 的 counter 变量。 由于 缺乏同步机制,不同线程对 counter 变量的修改操作可能会互相干扰,导致最终的 counter 值小于预期的 10000,甚至每次运行结果都可能不同,这显然是线程不安全的。

线程安全保证: 通过强制要求 Lambda 表达式捕获的变量必须是 final 或 effectively final,Java 可以有效地避免 Lambda 表达式在多线程环境下访问和修改共享的可变状态,从而提高 Lambda 表达式的线程安全性。 如果 Lambda 表达式捕获的变量是不可变的,那么多个线程同时访问这个变量是安全的,不会出现竞态条件。

Effectively Final 限制不仅是为了保证数据一致性,也是为了提高 Lambda 表达式在多线程环境下的安全性。它避免了 Lambda 表达式与外部作用域之间不必要的变量共享和修改,使得 Lambda 表达式更加可靠和易于在并发环境中使用。

6. 总结 (Conclusion)

本文深入探讨了 Java Lambda 表达式中 “Variable used in lambda expression should be final or effectively final” 错误的本质,全面解析了 effectively final 概念。 通过实例、变量捕获分析、final 与 effectively final 对比,以及对数据一致性和线程安全的理解,我们揭示了 effectively final 概念的重要性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2294398.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习系列--04.梯度下降以及其他优化器

目录 一.梯度概念 1.一元函数 2.二元函数 3.几何意义上的区别 二.梯度下降 1.原理 2.步骤 3.示例代码&#xff08;Python&#xff09; 4.不同类型的梯度下降 5.优缺点 三.动量优化器&#xff08;Momentum&#xff09; 适用场景 1.复杂地形的优化问题 2.数据具有噪声的问…

【C语言篇】“三子棋”

一、游戏介绍 三子棋&#xff0c;英文名为 Tic - Tac - Toe&#xff0c;是一款简单而经典的棋类游戏。游戏在一个 33 的棋盘上进行&#xff0c;两名玩家轮流在棋盘的空位上放置自己的棋子&#xff08;通常用 * 和 # 表示&#xff09;&#xff0c;率先在横、竖或斜方向上连成三个…

TongSearch3.0.4.0安装和使用指引(by lqw)

文章目录 安装准备手册说明支持的数据类型安装控制台安装单节点(如需集群请跳过这一节)解压和启动开启X-Pack Security和生成p12证书&#xff08;之后配置内置密码和ssl要用到&#xff09;配置内置用户密码配置ssl&#xff08;先配置内置用户密码再配ssl&#xff09;配置控制台…

在本地顺利的部署一个al模型从零开始 windows

引言 &#xff08;踩的坑&#xff0c;省流引言的内容没有有使模型跑起来&#xff09; 最近想在本地部署一个deepseek模型&#xff0c;就在网上搞了3 4天终于是能够部署下来了&#xff0c;在部署的时候也是成功的踩了无数的坑&#xff0c;比如我先问al如何在本地部署一个语言模…

【容器技术01】使用 busybox 构建 Mini Linux FS

使用 busybox 构建 Mini Linux FS 构建目标 在 Linux 文件系统下构建一个 Mini 的文件系统&#xff0c;构建目标如下&#xff1a; minilinux ├── bin │ ├── ls │ ├── top │ ├── ps │ ├── sh │ └── … ├── dev ├── etc │ ├── g…

尝试在Excel里调用硅基流动上的免费大语言模型

我个人觉得通过api而不是直接浏览器客户端聊天调用大语言模型是使用人工智能大模型的一个相对进阶的阶段。 于是就尝试了一下。我用的是老师木 袁进辉博士新创的硅基流动云上的免费的大模型。——虽然自己获赠了不少免费token&#xff0c;但测试阶段用不上。 具体步骤如下&am…

SqlSugar简单使用之Nuget包封装-Easy.SqlSugar.Core

SqlSugar官方文档 Nuget包开源地址 Nuget包是为了简化SqlSugar的使用步骤,增加一些基础的使用封装 引入分为两个版本&#xff0c;一个Ioc模式&#xff0c;另一个是注入模式&#xff0c;如果不想影响原本的仓储代码推荐使用Ioc模式&#xff0c;两者区别不到&#xff0c;方法通…

Linux网络 | 理解NATPT, 数据链路层Done

前言&#xff1a;本节内容结束数据链路层&#xff0c; 本节的重要内容有两个&#xff1a;一个是见一个综合性面试题&#xff0c;另一个就是NAT技术NATPT。 那么废话不多说&#xff0c; 开始我们的学习吧&#xff01;&#xff01;&#xff01; ps&#xff1a;最好先看一下上一篇…

微信小程序~django Petting pets(爱抚宠物)小程序

博主介绍&#xff1a;✌程序猿徐师兄、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

UE (标识符, meta=())笔记

视频连接&#xff1a; [UFSH2024]UE5(标识符, meta(详解, 史上最全)) | 大钊 Epic Games 虚幻社区经理 参考文档&#xff1a; UE5标识符详解 | 史上最全 UnrealSpecifiers | UE5标识符详解 GitHub 老外整理的标识符文档 标识符 CPP_Default_ParamName&#xff08;18:22&a…

并发编程 引用类型 原子类 Stamped和Markable atomicMarkableReference表单Ref和标记Markable 面试题

目录 Stamped 和 Markable 的区别 示例代码 所以这个东西是一次性的 从来没听说过 从来没见过 Stamped 和 Markable 的区别 标记号 boolean 一次性的 版本号 整型数 不建议用 Markable 解决 ABA 问题 AtomicMarkableReference 是一个位于 java.util.concurrent.atomic 包中…

绿联NAS安装cpolar内网穿透工具实现无公网IP远程访问教程

文章目录 前言1. 开启ssh服务2. ssh连接3. 安装cpolar内网穿透4. 配置绿联NAS公网地址 前言 本文主要介绍如何在绿联NAS中使用ssh远程连接后&#xff0c;使用一行代码快速安装cpolar内网穿透工具&#xff0c;轻松实现随时随地远程访问本地内网中的绿联NAS&#xff0c;无需公网…

【Ubuntu】ARM交叉编译开发环境解决“没有那个文件或目录”问题

【Ubuntu】ARM交叉编译开发环境解决“没有那个文件或目录”问题 零、起因 最近在使用Ubuntu虚拟机编译ARM程序&#xff0c;解压ARM的GCC后想要启动&#xff0c;报“没有那个文件或目录”&#xff0c;但是文件确实存在&#xff0c;环境配置也检查过了没问题&#xff0c;本文记…

微信小程序~电器维修系统小程序

博主介绍&#xff1a;✌程序猿徐师兄、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

android 适配 api 35(android 15) 遇到的问题

首先升级 targetSdkVersion 和 compileSdkVersion 到 35&#xff0c;升级后发生的报错 一、 解决方案: 升级 gradle 和 gradle 插件版本 com.android.tools.build:gradle -> 8.3.0-alpha02 gradle-wrapper.properties : distributionUrl -> gradle-8.6-bin.zip htt…

Python Pandas(3):DataFrame

1 介绍 DataFrame 是 Pandas 中的另一个核心数据结构&#xff0c;类似于一个二维的表格或数据库中的数据表。它含有一组有序的列&#xff0c;每列可以是不同的值类型&#xff08;数值、字符串、布尔型值&#xff09;。DataFrame 既有行索引也有列索引&#xff0c;它可以被看做由…

Mac电脑上好用的压缩软件

在Mac电脑上&#xff0c;有许多优秀的压缩软件可供选择&#xff0c;这些软件不仅支持多种压缩格式&#xff0c;还提供了便捷的操作体验和强大的功能。以下是几款被广泛推荐的压缩软件&#xff1a; BetterZip 功能特点&#xff1a;BetterZip 是一款功能强大的压缩和解压缩工具&a…

BUUCTF_XSS-Lab

xss XSS&#xff08;Cross - Site Scripting&#xff09;即跨站脚本攻击&#xff0c;是一种常见的 Web 安全漏洞。攻击者通过在目标网站注入恶意脚本&#xff08;通常是 JavaScript&#xff09;&#xff0c;当其他用户访问该网站时&#xff0c;这些恶意脚本会在用户的浏览器中执…

新能源算力战争:为什么AI大模型需要绿色数据中心?

新能源算力战争:为什么AI大模型需要绿色数据中心? 近年来,人工智能(AI)大模型的爆发式增长正在重塑全球科技产业的格局。以GPT-4、Gemini、Llama等为代表的千亿参数级模型,不仅需要海量数据训练,更依赖庞大的算力支撑。然而,这种算力的背后隐藏着一个日益严峻的挑战——…

go gin配置air

一、依赖下载 安装最新&#xff0c;且在你工作区下进行安装&#xff0c;我的是D:/GO是我的工作区&#xff0c;所有项目都在目录下的src&#xff0c; go install github.com/air-verse/airlatest 如果出现类似报错&#xff1a; 将图中第三行 github.com/air-verse/air 替换最…