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