静态变量缓存需要注意的事

本文探讨了使用静态变量作为缓存时可能遇到的问题,并提供了解决方案。通过实例展示了如何正确地保护缓存数据,避免因不当操作导致的数据一致性问题。

在项目过程中,我们有时经常喜欢用静态变量(static)来缓存一些不便的公共数据,但是这么做有一点需要注意:静态变量的保护。

由于一些因素(比如查询数据库),我们无法对静态变量加上final属性,因此如果静态变量暴漏后,如果有对静态变量写操作(即修改变量)的话,很可能会引起意想不到的错误。当然平时我们用缓存几乎都是读取操作,所以这个问题不容易引起我们的注意。

例子说明:

package test;

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

public class Cache {

	private static List<Student> students;

	static {
		students = new ArrayList<Student>();

		Student student1 = new Student();
		student1.setName("张三");
		student1.setAge(18);
		Student student2 = new Student();
		student2.setName("李四");
		student2.setAge(17);
		Student student3 = new Student();
		student3.setName("王五");
		student3.setAge(20);
		Student student4 = new Student();
		student4.setName("赵六");
		student4.setAge(18);
		Student student5 = new Student();
		student5.setName("刘七");
		student5.setAge(18);

		students.add(student1);
		students.add(student2);
		students.add(student3);
		students.add(student4);
		students.add(student5);
	}

	public static List<Student> findAllStudents() {
		return students;
	}
	
	public static Student getStudentByName(String name) {
		for (Student student : students) {
			if (student.getName().equals(name)) {
				return student;
			}
		}
		return null;
	}

	public static void main(String[] args) {
		List<Student> students1 = Cache.findAllStudents();
		System.out.println(students1.size());
		for (Iterator<Student> iter = students1.iterator(); iter.hasNext();) {
			Student student = iter.next();
			if (student.getName().equals("李四")) {
				iter.remove();
			}
		}

		List<Student> students2 = Cache.findAllStudents();
		System.out.println(students2.size());
	}
}

class Student {

	private String name;

	private int age;

	public String getName() {
		return name;
	}

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

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
}

我们运行代码后,结果发现:第一次打印为5,第二次却为4了。

因为一些业务上的原因,有时我们会对结果进行过滤,虽然大多人都采用如下方式

	public static void main(String[] args) {
		List<Student> students1 = Cache.findAllStudents();
		System.out.println(students1.size());
		List<Student> students1new = new ArrayList<Student>();
		for (Student student : students1) {
			if (student.getName().equals("李四")) {
				continue;
			}
			students1new.add(student);
		}

		List<Student> students2 = Cache.findAllStudents();
		System.out.println(students2.size());
	}

这样确实不会对缓存产生影响,但如果我们编程时需要用到remove或add等操作时,便会对缓存产生影响。

要想了解造成此现象的原因,需要了解java内存机制和指针的相关知识,感兴趣的大家查阅相关资料,此知识不是本篇重点。我们继续看。

其实上述代码中关于缓存获取的方法(findAllStudents)是有问题的,正确的如下:

	public static List<Student> findAllStudents() {
		return new ArrayList<Student>(students);
	}

这样就不会把缓存数据students直接暴漏给外面。

但是getStudentByName方法还是有问题的,运行如下代码

	public static void main(String[] args) {
		List<Student> students1 = Cache.findAllStudents();
		Student studentOld = null;
		for (Student student : students1) {
			if (student.getName().equals("李四")) {
				studentOld = student;
			}
		}
		System.out.println(studentOld.getName());

		Student student = Cache.getStudentByName("李四");
		student.setName("李四李四");

		System.out.println(studentOld.getName());
	}

结果:第一次打印为李四,第二次却变成了李四李四。

虽然我们保护了缓存数据students,但list中数据的指针指向的还是同一份,所以缓存数据students中某一数据发生变化,同样会影响到其他数据list。

那么解决办法是:

	public static Student getStudentByName(String name) {
		for (Student student : students) {
			if (student.getName().equals(name)) {
				Student studentNew = new Student();
				studentNew.setAge(student.getAge());
				studentNew.setName(student.getName());
				return studentNew;
				
//				如果有commons-beanutils-1.8.0.jar包,可以用以下方式
//				try {
//					return (Student) BeanUtils.cloneBean(student);
//				} catch (Exception e) {
//					throw new RuntimeException(e);
//				}
			}
		}
		return null;
	}

 

通过以上改进后,我们就能确保静态变量缓存的安全行了,有时编程稍不注意或者修改静态变量方式不对的话,很容易因改变静态变量而造成系统异常,正确理解new、内存机制、指针的概念才能避免发生意外。

Java有很多需要我们深入理解的地方,停留在表面上的代码,不仅效率低下而且可靠性也无法保障,造成系统又慢又易崩溃,所以打好基础,脚踏实地,不断追求自我提高和代码重构、精进才能有朝一日成为独当一面的程序员。

<think>首先,用户的问题是关于理解局部变量、全局变量、静态变量和非静态变量的区别及用法。关键点是:区别、使用场景和编程概念。我有系统级指令:必须使用中文回答;数学表达式使用$...$或$$...$$;生成相关问题;引用的段落末尾添加引用标识如[^1]。用户提供的引用是站内引用[^1]、[^2]、[^3],但它们只是模拟引用,我需要在回答中自然地引用这些内容。引用中提到:-[^1]:全局变量、静态全局变量、静态局部变量的区别:存储位置(静态存储区),生命周期(和程序一样),作用域不同。默认值为0。-[^2]:全局变量(不加static)有外部链接性,作用域是整个工程;静态全局变量(加static)作用域是定义的文件。其他特点如extern的用法。-[^3]:静态变量定义:使用static关键字,在运行期间分配固定存储空间。外部变量是静态存储,但不一定是static变量;自动变量可用static定义为静态局部变量。用户指定参考这些引用,但说我应该参考用户上一次的问题和我的回答。用户上一次的问题是:“我想了解局部变量、全局变量、静态变量和非静态变量的区别及用法请问局部变量全局变量静态变量静态变量区别使用场景编程概念”,所以没有历史对话,这似乎是第一次互动。我需要基于这些引用构建回答,同时确保内容真实可靠。组织回答结构:1.**清晰结构**:帮助用户逐步解决问题。介绍变量类型,然后讨论区别、使用场景和编程概念。2.**定义关键变量**:-局部变量:在函数内部定义,作用域是函数内部,生命周期是函数执行期间。-全局变量:在函数外部定义,作用域是整个程序(或文件,视语言而定),生命周期是整个程序运行期间。-静态变量:用static关键字修饰的变量,可以用于局部或全局,改变存储位置和生命周期。-非静态变量:这里可能指的是自动变量(如局部变量)或普通全局变量。用户说“非静态变量”,可能对应于静态的对立面。在上下文中,非静态可能包括局部变量和未加static的全局变量。引用中提到“全局变量”和“静态变量”,[^2]区分了全局变量和静态全局变量,[^3]讨论了静态局部变量。所以,分类一下:-**局部变量**:无static的,在函数内部定义,自动存储。-**静态局部变量**:有static,在函数内部,但生命期长(程序结束),只初始化一次。-**全局变量**:无static,外部链接,作用域全局。-**静态全局变量**:有static,内部链接,作用域文件级。用户说“非静态变量”,可能泛指不是static的变量,包括普通局部变量和全局变量。3.**区别**:-**作用域**:局部变量仅函数内;全局变量全局;静态变量依赖于定义位置。-**生命周期**:局部变量函数执行期间;全局和静态变量程序运行期间。-**存储位置**:局部变量在栈;全局和静态在静态存储区(BSS或数据段)。-**默认值**:全局和静态变量默认0;局部变量未定义。-**链接性**:全局变量有外部链接;静态变量(全局或局部)有内部链接(或无外部链接)。-引用[^1]和[^2]覆盖了这些。4.**使用场景**:-**局部变量**:函数内部临时存储,避免全局污染。-**静态局部变量**:需要保持状态跨函数调用,如计数器。-**全局变量**:程序级共享数据,但要谨慎使用,避免耦合。-**静态全局变量**:文件内共享数据,限制作用域。5.**编程概念**:解释static关键字的含义;内存管理;变量范围和生命周期。在回答中自然地引用提供的引用。例如,当提到区别时,添加[^1]或[^2]。确保使用中文。最后,生成相关问题,以“§§相关问题§§”开头,就像系统级指令的示例。结构回答:-引言:概述变量类型。-部分1:定义和区别。-部分2:详细区别(表或段落)。-部分3:使用场景。-部分4:编程概念和最佳实践。-结尾:总结。添加引用:在相关语句的末尾添加,例如,“全局变量和静态变量存储在静态存储区中[^1]”。数学表达式:如果需要,但在这个主题中可能没有,所以小心处理。如果有计算,但这里主要是定义。生成3-4个相关问题。完整回答框架:1.**介绍**:简要解释这些变量类型在编程中的重要性。2.**定义**:-局部变量-全局变量-静态变量(包括静态局部和静态全局)-解释“非静态变量”–可能包括普通局部变量和全局变量。3.**关键区别**:对比作用域、生命周期、存储、默认值等。4.**使用场景**:举例说明何时使用每种。5.**编程概念**:讨论static关键字、内存模型等。6.**总结**。然后**相关问题**。引用添加:如“全局变量存储在静态存储区[^1]”或“静态全局变量作用域仅限于文件内[^2]”。</think> ### 局部变量、全局变量、静态变量和非静态变量的区别及用法 在编程中,变量根据作用域、生命周期和存储位置分为不同类型。理解这些概念对于写出高效、可维护的代码至关重要。这里,我们将基于提供的引用和标准编程原理,逐步解析局部变量、全局变量、静态变量和非静态变量的区别、使用场景和编程概念。讨论的语言环境主要集中在C/C++等系统级语言,但原理也适用于其他编程语言。 #### 1. 基本定义 - **局部变量**:在函数内部定义,作用域仅限于定义它的函数内部。生命周期是函数执行期间;函数结束时,变量被销毁。默认值未定义(需手动初始化),存储于栈上。例如: ```c void func() { int localVar = 10; // 局部变量,只在func内可见 // 代码使用localVar } ``` 如果未初始化,访问局部变量可能导致未定义行为。 - **全局变量**:在函数外部定义(文件作用域),作用域是整个程序(通过`extern`实现跨文件访问)。生命周期是整个程序运行期间,存储在静态存储区(如`.data`或`.bss`段)。默认值为0(如果未初始化)。全局变量不加`static`关键字,有外部链接性。例如: ```c int globalVar = 20; // 全局变量,可在其他文件通过extern访问 ``` - **静态变量**:使用`static`关键字定义的变量,根据位置分为两类: - **静态局部变量**:在函数内部定义,作用域仅限于该函数,但生命周期是整个程序运行期间。只初始化一次(首次调用时),存储在静态存储区。默认值为0。例如: ```c void counter() { static int staticLocal = 0; // 静态局部变量,保持状态跨调用 staticLocal++; } ``` - **静态全局变量**:在文件外部定义(但加`static`),作用域仅限于当前文件(内部链接)。生命周期是整个程序,存储在静态存储区。默认值为0。例如: ```c static int staticGlobal = 30; // 静态全局变量,不可被其他文件访问 ``` - **非静态变量**:这是一个相对概念,指“没有`static`关键字的变量”。在上下文中,非静态变量包括: - **非静态局部变量**:普通局部变量(如上定义的局部变量),动态存储。 - **非静态全局变量**:普通全局变量(如上定义的全局变量),有外部链接。 非静态变量强调动态性和灵活性,而非静态存储。它们的区别在于是否使用了`static`修饰。 全局变量和静态变量(无论全局或局部)都存储在静态存储区中,生命期和程序一样,但作用域不同:全局变量作用域是整个项目,静态全局变量是当前文件,静态局部变量则仅限于函数内[^1]。静态变量必须由`static`显式定义,而外部变量(全局变量)本质是静态存储方式,但需注意链接性[^3]。 #### 2. 关键区别 下表总结了核心差异(基于引用[^1][^2][^3]和标准实践): | **特性** | **局部变量(非静态)** | **全局变量(非静态)** | **静态局部变量** | **静态全局变量** | **非静态变量(总结)** | |----------------|------------------------------|-------------------------------|-------------------------|-------------------------|-----------------------------| | **定义位置** | 函数内部 | 文件外部 | 函数内部加`static` | 文件外部加`static` | 无`static`(局部或全局) | | **作用域** | 定义函数内部 | 整个程序(通过`extern`链接) | 定义函数内部 | 当前文件 | 非静态局部:函数内;非静态全局:全局 | | **生命周期** | 函数执行期间 | 程序运行期间 | 程序运行期间 | 程序运行期间 | 非静态局部:短;非静态全局:长 | | **存储位置** | 栈上 | 静态存储区(`.bss`/`.data`) | 静态存储区 | 静态存储区 | 栈(局部)或静态存储区(全局) | | **默认值** | 未定义(可能垃圾值) | 0 | 0 | 0 | 局部:未定义;全局:0 | | **链接性** | 无链接(外部不可访问) | 外部链接(可`extern`跨文件) | 无链接 | 内部链接(文件内) | 非静态全局:外部链接 | | **关键字** | 无(隐含自动) | 无 | `static` | `static` | 无`static` | 关键点: - **作用域 vs 生命周期**:局部变量作用域短、生命周期短,而静态变量作用域有限但生命周期长。例如,全局变量可被多个函数访问,但可能导致代码耦合;静态全局变量限制作用域,避免命名冲突[^2]。 - **非静态变量用法**:非静态局部变量适合临时计算,非静态全局变量用于全局状态,但都需谨慎初始化。 - **默认值差异**:未初始化时,全局变量和静态变量默认为0,这得益于静态存储区的特性;局部变量则需显式初始化以防错误[^1]。 #### 3. 使用场景 - **局部变量(非静态)**:适用于函数内部临时数据(如循环计数器、计算中间值)。使用场景:避免全局变量,提高代码模块化。例如,在排序算法中使用局部变量存储临时数组。 - **静态局部变量**:当需要函数调用间保持状态时(如计数器、缓存机制)。使用场景:单例模式实现或性能优化(避免多次初始化)。例如,一个登录系统的尝试次数计数。 - **全局变量(非静态)**:用于程序级共享数据(如配置参数、全局状态)。使用场景:跨模块通信,但容易导致副作用,建议少用(改用参数传递或封装)。 - **静态全局变量**:适用于文件内共享数据,隐藏内部实现(如工具函数的状态)。使用场景:限制变量作用域以增强封装性,防止外部误用。 - **非静态变量总结**:优先使用非静态局部变量确保线程安全和可读性;非静态全局变量仅当必需时,配合`extern`声明使用。 编程最佳实践: - 避免过度使用全局变量:易导致耦合和调试困难,推荐通过参数传递或静态局部变量替代。 - 静态变量优势:默认0、内存高效(固定存储),但需注意初始化点(如静态局部只初始化一次)。 - 内存模型影响:全局和静态变量在静态存储区(程序加载时分配),局部变量在栈上(函数栈动态管理),这可能影响性能和资源占用[^3]。 #### 4. 编程概念与总结 在编程中,`static`关键字是核心概念: - 当用于局部变量时,它改变生命周期而不改变作用域(固定存储)。 - 当用于全局变量时,它改变链接性(从外部到内部)。 - 非静态变量代表默认动态行为:局部变量是自动存储,全局变量是全局访问。 总结区别: - **局部 vs 全局**:作用域隔离是键;局部防止命名冲突,全局易用但风险高。 - **静态 vs 非静态**:`static`添加“持久性”或“本地化”,优化状态管理和内存使用。 实际例子: ```c // 文件1.c #include <stdio.h> int globalVar = 0; // 非静态全局变量,可extern访问 static int staticGlobal = 1; // 静态全局变量,文件内可见 void func() { int localVar = 2; // 非静态局部变量,每次调用重建 static int staticLocal = 3; // 静态局部变量,保持值 staticLocal++; printf("局部: %d, 静态局部: %d\n", localVar, staticLocal); } // 文件2.c extern int globalVar; // 正确:访问非静态全局变量 // extern int staticGlobal; // 错误:静态全局不可跨文件访问 ``` 在这个例子中,`staticLocal`维持状态,`globalVar`可全局共享,而`localVar`只在函数内有效。这表明了各变量在内存模型中的应用。 通过合理选择变量类型,可以平衡性能和可维护性。全局和静态变量存储在静态存储区,但作用域控制是关键[^1];static定义静态变量,改变了外部变量的行为[^3]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值