Самый эффективный способ предотвратить бесконечную рекурсию в toString()? - программирование

Самый эффективный способ предотвратить бесконечную рекурсию в toString()?

В строку в коллекции можно попасть в бесконечный цикл, если где-то на графике собранных элементов будет ссылка на себя. См. Пример ниже.

Да, хорошие методы кодирования должны помешать этому, в первую очередь, но в любом случае, мой вопрос: какой самый эффективный способ обнаружить рекурсию в этой ситуации?

Один из подходов - использовать набор в threadlocal, но это кажется немного тяжелым.

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 !
  }
}
4b9b3361

Ответ 1

Я уже упоминал в вопросе вопрос:

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();
    }
}
}

Ответ 2

Когда мне нужно перебирать рискованные графики, я обычно делаю функцию с декрементирующим счетчиком.

Например:

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

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

Я не буду настаивать на этом, как вы уже знаете, но это не соответствует контракту toString(), который должен быть коротким и предсказуемым.

Ответ 3

Проблема не присуща коллекциям, это может произойти с любым графиком объектов, которые имеют циклические ссылки, например, двусвязный список.

Я думаю, что разумная политика: метод toString() вашего класса не должен вызывать toString() своих дочерних элементов/ссылок, если есть вероятность, что это часть графика объекта с циклами. В другом месте у нас могут быть специальные методы (возможно, статические, возможно, как вспомогательный класс), которые создают строковое представление полного графика.

Ответ 4

Вы можете создать toString, который принимает хэш-идентификатор идентификатора.

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}";
   }
}

Ответ 5

Вы всегда можете отслеживать рекурсию следующим образом (без учета потоков):

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

   @Override
    public String toString() {
         if(recursion){
               //Recursion 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;
   }
}

Ответ 6

Я рекомендую использовать ToStringBuilder из Apache Commons Lang. Внутри он использует карту ThreadLocal для "обнаружения циклических ссылок на объекты и исключения бесконечных циклов".

Ответ 7

Самый простой способ: не вызывать toString() на элементах коллекции или карты, когда-либо. Просто напечатайте [], чтобы указать, что это коллекция или карта, и избегайте повторного ее завершения. Это единственный пуленепробиваемый способ избежать падения в бесконечной рекурсии.

В общем случае вы не можете предвидеть, какие элементы будут находиться в Collection или Map внутри другого объекта, а граф зависимостей может быть довольно сложным, что приводит к непредвиденным ситуациям, в которых происходит цикл графа объектов.

Какую IDE вы используете? потому что в Eclipse есть возможность явно обрабатывать этот случай при генерации метода toString() через генераторы кода - то, что я использую, когда атрибут является ненулевой или картографической печатью [] независимо от того, сколько элементов он содержит.

Ответ 8

Если вы хотите перейти за борт, вы можете использовать аспект, который отслеживает вложенные коллекции всякий раз, когда вы вызываете 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;
    }
  }
}

Я никогда не на 100% синтаксис, не бросая это в Eclipse, но я думаю, что вы поняли идею

Ответ 9

возможно, вы могли бы создать исключение в своей команде toString и использовать рычаг стека, чтобы узнать, где вы находитесь в стеке, и вы обнаружите, что есть рекурсивные вызовы. Некоторые рамки делают этот путь.

@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    
    //...    

}