Каждый вызов метода сохраняет свое собственное состояние. Как только вы доберетесь до узла 6, в стеке будет 5 вызовов, ожидающих окончания reversed(n.next, b)
до sh. Каждый метод может продолжаться только после завершения вызова reversed
в стеке над ним.
В этом примере вы выполняете последним в качестве первого, т.е. у вас есть нехвостовая рекурсивная функция (рекурсивная вызов происходит до того, как вы выполните действие этого метода).
Представьте себе, что каждый раз, когда вы получаете reversed
, вы заменяете вызов этого метода кодом, который он будет выполнять. Помните, что все вызовы происходят последовательно (есть только один поток, ничего не может происходить параллельно). Вы получите что-то похожее на это:
if (n0 != null) {
Node<E> n1 = n0.next;
if (n1 != null) {
Node<E> n2 = n1.next;
if (n2 != null) {
Node<E> n3 = n2.next;
if (n3 != null) {
Node<E> n4 = n3.next;
if (n4 != null) {
Node<E> n5 = n4.next;
if (n5 != null) {
// would be null so nothing happens
}
b.append(n4.element);
b.append('\n');
}
b.append(n3.element);
b.append('\n');
}
b.append(n2.element);
b.append('\n');
}
b.append(n1.element);
b.append('\n');
}
b.append(n0.element);
b.append('\n');
}
Вы можете увидеть, как этот код становится довольно трудно читать, когда число элементов увеличивается. Когда мы не знаем точно, сколько будет длиться список, этот подход ломается ... вы не захотите делать это 10, 100 или, возможно, тысячи раз!
Именно поэтому рекурсия так полезно для таких приложений, как это. Мы можем повторно использовать интересную логику c, не зная длины списка, и можем значительно сократить дублирующийся код.
Просто имейте в виду, что рекурсия происходит с затратами памяти. Каждый раз, когда вы вводите рекурсивный метод, вы добавляете состояние в стек. После того, как вы загрузите память на своем компьютере, вам придется начать искать нерекурсивные способы выполнения этой работы.