1. 程式人生 > >Lombok 工具中@Data註解生成hashCode()可能導致StackOverflowError情況

Lombok 工具中@Data註解生成hashCode()可能導致StackOverflowError情況

         看了大學一位同學部落格,寫了內容大致就是對於一個類中出現了該類的集合,通過Lombok的@Data註解生成class檔案,當建立兩個這個類的物件並且互相之間引用的時候,就出現了StackOverflowError異常,即棧溢位,或者叫超出棧深度.                在Java虛擬機器記憶體區域分為兩種一種是執行緒共享區域,另一種是執行緒私有區域,而虛擬機器棧就處線上程私有區域中,虛擬機器棧描述的是Java方法執行的記憶體模型:每個方法被執行的時候都會同時建立一個棧幀,棧幀用於儲存區域性變量表,運算元棧,動態連結,方法返回地址等等。  

Java 虛擬機器規範中,對這個區域規定了兩種異常情況:
  • 如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError異常。
  • 如果虛擬機器在動態擴充套件棧時無法申請到足夠的記憶體空間,則丟擲OutOfMemoryError異常。

下面給出個記憶體區域記憶體溢位的簡單測試方法,


     回到文章討論的問題,@Data註解在什麼情況下可能導致StackOverflowError情況呢?先看下同學給出的示例程式碼,

@Data
public class Project {
    private 
Long id; private String projectName; private List<Project> projects; public static void main(String[] args) { Project project1 = new Project(); Project project2 = new Project(); project1.setProjects(Arrays.asList(project2)); project2.setProjects(Arrays.asList
(project1)); System.out.println(project1.hashCode()); } }
從程式碼可以看出project1引用了project2,project2引用了project1,同學 給出的解釋如下:“ @Data 註解不僅幫我們實現了生成了getter/setter同時還重寫了equals(Object other) 和 hashCode()方法, Lombok 會將 Project 類中的 List projects 當做是 hashCode 計算的一部分(同理,equals,toString 也會存在同樣的問題),而如果我的專案中出現迴圈引用,這就會導致死迴圈,最終就會丟擲 StackOverFlowError。”,

        難道相互引用就一定出現死迴圈嗎?好吧,來看看反編譯下@Data到底幹啥了,程式碼反編譯後確實發現hashCode()方法被Lombok自己生成了,程式碼如下,

public int hashCode() {
    int PRIME = true;
    int result = 1;
    Object $id = this.getId();
    int result = result * 59 + ($id == null ? 43 : $id.hashCode());
    Object $projectName = this.getProjectName();
    result = result * 59 + ($projectName == null ? 43 : $projectName.hashCode());
    Object $projects = this.getProjects();
    result = result * 59 + ($projects == null ? 43 : $projects.hashCode());
    return result;
}

那怎麼就死迴圈了呢,其實問題在於projects是一個ArrayList集合,而ArrayList對hashCode() 的計算會把每一個元素拿出來呼叫元素的hashCode()求和,但是projects裡面的元素是 project,project裡面又有projects,因此出現無限遞迴呼叫,又是單執行緒中呼叫方法,所以就丟擲StackOverflowError,在大多數場景中我們使用Lombok的@Data註解目的是為了生成getter/setter,不需要生成hashCode()和equals()方法,即使業務需要判斷兩個物件是否相等,邏輯基本也不會是lombok生成的那種,所以建議使用 @Getter 和 @Setter 替換 @Data註解

ArrayList的hashCode()程式碼如下:

public static int hashCode(Object a[]) {
    if (a == null)
        return 0;
    int result = 1;
    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());
    return result;
}