靜態變量緩存需要注意的事

在項目過程中,我們有時經常喜歡用靜態變量(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有很多需要我們深入理解的地方,停留在表面上的代碼,不僅效率低下而且可靠性也無法保障,造成系統又慢又易崩潰,所以打好基礎,腳踏實地,不斷追求自我提高和代碼重構、精進纔能有朝一日成爲獨當一面的程序員。

發佈了110 篇原創文章 · 獲贊 197 · 訪問量 320萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章