2012-07-02 11 views
16

Để chuỗi trên bộ sưu tập có thể đi vào vòng lặp vô hạn nếu một nơi nào đó trong biểu đồ các mục được thu thập là tham chiếu về chính nó. Xem ví dụ bên dưới.Cách hiệu quả nhất để ngăn chặn đệ quy vô hạn trong toString()?

Có, thực hành mã hóa tốt nên ngăn chặn điều này ngay từ đầu, nhưng dù sao, câu hỏi của tôi là: Cách hiệu quả nhất để phát hiện đệ quy trong tình huống này là gì?

Một cách tiếp cận là sử dụng một bộ trong tiêu đề, nhưng điều đó có vẻ hơi nặng.

public class AntiRecusionList<E> extends ArrayList<E> { 
    @Override 
    public String toString() { 
    if ( /* ???? test if "this" has been seen before */) { 
     return "{skipping recursion}"; 
    } else { 
     return super.toString(); 
    } 
    } 
} 


public class AntiRecusionListTest { 
    @Test 
    public void testToString() throws Exception { 
     AntiRecusionList<AntiRecusionList> list1 = new AntiRecusionList<>(); 
     AntiRecusionList<AntiRecusionList> list2 = new AntiRecusionList<>(); 
     list2.add(list1); 
     list1.add(list2); 
     list1.toString(); //BOOM ! 
    } 
} 
+3

Hãy xem [Lombok] (http://projectlombok.org/). Nó cho phép bạn chú thích các lớp của bạn với '@ToString (exclude = {" fields "," you "," dont "," want "})'. :) – elias

+1

Không bao giờ chạy vào này bản thân mình, nhưng những thứ như thế này có thể là một nỗi đau REAL, bởi vì nó chỉ có thể xảy ra khi bạn chuyển log4j (hoặc tương tự) của bạn gỡ lỗi trên. Vì vậy, bạn đang cố gắng để gỡ lỗi một vấn đề và kết thúc chạy vào một vấn đề với các thông điệp logger mình - rất khó chịu! 1 cho câu hỏi từ tôi – davidfrancis

+0

@davidfrancis - ghi nhật ký và gỡ lỗi chính xác là nơi điều này xuất hiện! Chúc mừng. – marathon

Trả lời

4

Bit ThreadLocal tôi đã đề cập trong câu hỏi:

public class AntiRecusionList<E> extends ArrayList<E> { 


private final ThreadLocal<IdentityHashMap<AntiRecusionList<E>, ?>> fToStringChecker = 
     new ThreadLocal<IdentityHashMap<AntiRecusionList<E>, ?>>() { 
      @Override 
      protected IdentityHashMap<AntiRecusionList<E>, ?> initialValue() { 
       return new IdentityHashMap<>(); 
      } 
     };  

@Override 
public String toString() { 
    boolean entry = fToStringChecker.get().size() == 0; 
    try { 
     if (fToStringChecker.get().containsKey(this)/* test if "this" has been seen before */) { 
      return "{skipping recursion}"; 
     } else { 
      fToStringChecker.get().put(this, null); 
      entry = true; 
     } 
     return super.toString(); 
    } finally { 
     if (entry) 
      fToStringChecker.get().clear(); 
    } 
} 
} 
+1

Đây là cược an toàn với chi phí tối thiểu liên quan. –

+0

Chủ đề địa phương có phải là tĩnh không? – davidfrancis

1

Cách đơn giản nhất: không gọi toString() trên các yếu tố của bộ sưu tập hoặc bản đồ, bao giờ hết. Chỉ cần in [] để cho biết rằng đó là một bộ sưu tập hoặc bản đồ và tránh lặp lại hoàn toàn. Đó là cách duy nhất chống đạn để tránh rơi vào một đệ quy vô hạn. Trong trường hợp chung, bạn không thể dự đoán được những yếu tố nào sẽ ở trong một đối tượng khác, và biểu đồ phụ thuộc có thể khá phức tạp, dẫn đến các tình huống bất ngờ xảy ra trong biểu đồ đối tượng. .

Bạn đang sử dụng IDE nào? vì trong Eclipse có một tùy chọn để xử lý rõ ràng trường hợp này khi tạo phương thức toString() thông qua trình tạo mã - đó là những gì tôi sử dụng, khi thuộc tính xảy ra là một bộ sưu tập không rỗng hoặc in bản đồ [] bất kể có bao nhiêu phần tử.

+0

Tại sao một hashmap không được chứng nhận bằng bullet? – Aidanc

+2

@Aidanc đó là tình huống tương tự, đối với trường hợp chung bạn không thể dự đoán nơi hashmap sẽ được sử dụng, nó có thể dễ dàng kết thúc ở giữa một chu kỳ trong đồ thị đối tượng và bùng nổ! - đệ quy vô hạn. –

11

Khi tôi phải lặp qua biểu đồ nguy hiểm, tôi thường tạo một hàm với bộ đếm giảm dần.

Ví dụ:

public String toString(int dec) { 
    if ( dec<=0) { 
     return "{skipping recursion}"; 
    } else { 
     return super.toString(dec-1); 
    } 
} 

public String toString() { 
    return toString(100); 
} 

tôi sẽ không nhấn mạnh vào nó, như bạn đã biết điều đó, nhưng điều đó không tôn trọng hợp đồng toString() mà phải là ngắn và có thể dự đoán.

+1

+1 cho ý tưởng, mặc dù điều này sẽ chỉ hoạt động trong trường hợp đối tượng tham chiếu một đối tượng thuộc loại riêng của nó, trong phần lớn trường hợp chúng ta có một cái gì đó giống như người thuộc bộ phận có nhiều người (tạo chu kỳ) và trong tình huống này không có giải pháp –

+0

Khi biểu đồ không đồng nhất, bạn thường có thể tạo một hàm đệ quy tĩnh với bộ đếm giảm dần. Hoặc nhiều chuyên môn của cùng một phương pháp. Điểm quan trọng là để vượt qua truy cập và thất bại khi nó xuống đến số không. –

+0

đơn giản và thanh lịch và lưu lại ngày của tôi – Leo

3

Vấn đề không phải là vốn có đối với bộ sưu tập, nó có thể xảy ra với bất kỳ đồ thị nào của các đối tượng có tham chiếu tuần hoàn, ví dụ: danh sách được liên kết kép.

Tôi nghĩ rằng một chính sách lành mạnh là: phương pháp toString() của lớp học của bạn không được gọi toString() con của nó/tham chiếu nếu có khả năng là một phần của biểu đồ đối tượng có chu kỳ. Ở những nơi khác, chúng ta có thể có một phương pháp đặc biệt (có lẽ là tĩnh, có lẽ là một lớp phụ trợ) tạo ra một biểu diễn chuỗi của biểu đồ đầy đủ.

0

có lẽ bạn có thể tạo ra một ngoại lệ trong toString và đòn bẩy của bạn trên stacktrace để biết bạn đang ở đâu trong ngăn xếp, và bạn sẽ thấy nó có các cuộc gọi đệ quy. Một số khung làm theo cách này.

@Override 
public String toString() { 
    // ... 
    Exception exception = new Exception(); 
    StackTraceElement[] stackTrace = exception.getStackTrace(); 
    // now you analyze the array: stack trace elements have 
    // 4 properties: check className, lineNumber and methodName. 
    // if analyzing the array you find recursion you stop propagating the calls 
    // and your stack won't explode  
    //...  

} 
3

Bạn có thể tạo toString có bộ băm nhận dạng.

public String toString() { 
    return toString(Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>())); 
} 

private String toString(Set<Object> seen) { 
    if (seen.add(this)) { 
     // to string this 
    } else { 
     return "{this}"; 
    } 
} 
+0

sẽ không tạo ra một bộ mới mỗi khi một loại AntiRecusionList gặp phải trong đồ thị? Và do đó không bao giờ có hiệu quả mong muốn bởi vì nếu (nhìn thấy) thử nghiệm sẽ luôn luôn được chống lại một bộ mới? – marathon

+2

Đúng. Bạn phải đặt các thiết lập trong một ThreadLocal để tránh điều đó. – Jorn

+3

mặc dù tôi thích cách bạn tạo bộ nhận dạng. Tôi sẽ ăn cắp điều đó :) – marathon

2

Bạn luôn có thể theo dõi các đệ quy như sau (không có vấn đề luồng đưa vào tài khoản):

public static class AntiRecusionList<E> extends ArrayList<E> { 
    private boolean recursion = false; 

    @Override 
    public String toString() { 
     if(recursion){ 
       //Recursion's base case. Just return immediatelly with an empty string 
       return ""; 
     } 
     recursion = true;//start a perhaps recursive call 
     String result = super.toString(); 
     recursion = false;//recursive call ended 
     return result; 
    } 
} 
+0

... Tôi nghĩ anh ấy đúng, phải không? Tôi không thể tìm thấy một lỗ hổng trong ý tưởng, nó chỉ hoạt động. Và việc định vị luồng này rất dễ dàng. –

+0

@Slanec vấn đề là nó không dừng lại khi một đối tượng đã được nhìn thấy trước đó, thay vào đó, nó chỉ dừng lại sau một mức độ đệ quy. Nó sẽ chỉ ngăn chặn đệ quy, không tìm thấy một chu trình trong một bộ sưu tập. – dcow

+0

@DavidCowden: 'toString' của danh sách chỉ cần gọi' toString' trên mỗi mục của danh sách được trình bày để hiển thị. Việc quét đệ quy thực hiện những gì được mong đợi vì đối tượng sẽ đã được in. Đây không thực sự là một thuật toán đồ thị mà OP đang cố gắng hoàn thành, ít nhất là theo cách tôi hiểu vấn đề ở đây – Cratylus

1

Nếu bạn muốn đi quá nhiệt tình, bạn có thể sử dụng một khía cạnh theo dõi bộ sưu tập lồng nhau bất cứ khi nào bạn gọi toString().

public aspect ToStringTracker() { 
    Stack collections = new Stack(); 

    around(java.util.Collection c): call(String java.util.Collection+.toString()) && target(c) { 
    if (collections.contains(c)) { return "recursion"; } 
    else { 
     collections.push(c); 
     String r = c.toString(); 
     collections.pop(); 
     return r; 
    } 
    } 
} 

Tôi không bao giờ 100% về cú pháp mà không ném này vào Eclipse, nhưng tôi nghĩ rằng bạn sẽ có được ý tưởng

2

tôi khuyên bạn nên sử dụng ToStringBuilder từ Apache Commons Lang. Bên trong nó sử dụng một bản đồ ThreadLocal để "phát hiện các tham chiếu đối tượng theo chu kỳ và tránh các vòng lặp vô hạn."