Method toString() thuộc class java.lang.String, giá trị trả về mang kiểu dữ liệu String và cũng chính là giá trị của object đang gọi nó,.
Việc sử dụng method toString() một cách bừa bãi sẽ dẫn tới 2 hậu họa:
- Tạo ra các ràng buộc vòng tròn nối đuôi nhau (circular dependencies)
- Nguy cơ lỗi StackOverFlow
#1 Khởi nguồn
Để dễ hình dung, chúng ta cùng xét ví dụ sau, ví dụ của chung sta có 3 class là Account, Account Holder và Main:
Đầu tiên là class AccountHolder.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
<span class="hljs-keyword">package</span> com.example.toString; <span class="hljs-keyword">import</span> java.util.ArrayList; <span class="hljs-keyword">import</span> java.util.List; <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AccountHolder</span> </span>{ <span class="hljs-keyword">private</span> String name; <span class="hljs-keyword">private</span> List<Account> accList=<span class="hljs-keyword">new</span> ArrayList<Account>(); <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addAccount</span><span class="hljs-params">(Account acc)</span></span>{ accList.add(acc); } <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getName</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> name; } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setName</span><span class="hljs-params">(String name)</span> </span>{ <span class="hljs-keyword">this</span>.name = name; } <span class="hljs-annotation">@Override</span> <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">toString</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> <span class="hljs-string">"AccountHolder [name="</span> + name + <span class="hljs-string">", accList="</span> + accList + <span class="hljs-string">"]"</span>; } } |
Tiếp đó là Account.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
<span class="hljs-keyword">package</span> com.example.toString; <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Account</span> </span>{ <span class="hljs-keyword">private</span> String accNumber; <span class="hljs-keyword">private</span> String accType; <span class="hljs-keyword">private</span> AccountHolder holder; <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getAccNumber</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> accNumber; } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setAccNumber</span><span class="hljs-params">(String accNumber)</span> </span>{ <span class="hljs-keyword">this</span>.accNumber = accNumber; } <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getAccType</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> accType; } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setAccType</span><span class="hljs-params">(String accType)</span> </span>{ <span class="hljs-keyword">this</span>.accType = accType; } <span class="hljs-function"><span class="hljs-keyword">public</span> AccountHolder <span class="hljs-title">getHolder</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> holder; } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setHolder</span><span class="hljs-params">(AccountHolder holder)</span> </span>{ <span class="hljs-keyword">this</span>.holder = holder; } <span class="hljs-annotation">@Override</span> <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">toString</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> <span class="hljs-string">"Account [accNumber="</span> + accNumber + <span class="hljs-string">", accType="</span> + accType + <span class="hljs-string">", holder="</span> + holder + <span class="hljs-string">"]"</span>; } } |
Cuối cùng là Main.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<span class="hljs-keyword">package</span> com.example.toString; <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{ <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{ AccountHolder ach = <span class="hljs-keyword">new</span> AccountHolder(); ach.setName(<span class="hljs-string">"Shamik Mitra"</span>); Account acc = <span class="hljs-keyword">new</span> Account(); acc.setAccNumber(<span class="hljs-string">"100sm"</span>); acc.setAccType(<span class="hljs-string">"Savings"</span>); acc.setHolder(ach); ach.addAccount(acc); System.out.println(ach); } } |
Bạn hãy quan sát kĩ 3 class này, nhận diện các vấn đề mà chúng đang gặp phải, ghi ra giấy rồi chuyển tới phần tiếp theo của bài viết.
#2 Vấn đề
Có lẽ bạn đoán đúng, 2 đoạn code trên bị lỗi StackOverFlow. Thực ra chúng chính là code của một cậu Junior được tôi review.
2 class AccountHolder và Account tạo ra 2 object tương ứng. AccountHolder sẽ giữ thông tin của Account, và trong Account object có một tham chiếu tới Account Holder. Đây là một kịch bản phổ biến khi chúng ta làm việc với JPA entity. Tuy nhiên vấn đề phát sinh lỗi StackOverFlow lại nằm ở chỗ cậu Junior này đã vô tình generate method toString() một cách tự động thông qua tính năng auto code generation của IDE. Và hậu quả là các lời gọi hàm nối đuôi nhau liên tục dẫn tới lỗi StackOverFlow.
Bạn hãy để ý kĩ, method toString() trong class AccountHolder sẽ in ra danh sách các Account, do đó method này sẽ gọi đến toString() trong class Account. Thật không may, method toString() trong class Account lại tiếp tục gọi tới AccountHolder. Các lời gọi cứ nối đuôi nhau cho đến khi xảy ra lỗi StackOverFlow.
Tại sao cậu Junior lại không phát hiện ra lỗi này? Bởi các đoạn code tiếp theo của cậu ta không hề gọi tới toString() của một trong 2 class Account, AccountHolder.
#3 Khắc phục và phòng chống
Điều tôi muốn cảnh báo mọi người thông qua trường hợp này đó là hãy luôn cẩn thận khi lập trình. Thế giới bug có rất nhiều trường hợp không thể nhìn thấy bằng mắt thường.
Đối với các trường hợp nghi ngờ tồn tại các ràng buôc nối đuôi nhau, lập trình viên nên sử dụng các tool có khả năng tự động detect các vị trí đó.
Tuyệt đối tránh việc in ra (in ra màn hình console hoặc bất cứ nơi nào khác) các biến không phải là biến của class đó. Cẩn thận hơn nữa, lập trình viên không nên in các biến liên quan tới những mối quan hệ có-một (Has – a) giữa các class.