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