1. 程式人生 > >靜態變數快取需要注意的事

靜態變數快取需要注意的事

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