← Назад к вопросам

Приведи пример где выбрасывается StackOverflowError

1.0 Junior🔥 161 комментариев
#Soft Skills и карьера

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Приведи пример где выбрасывается StackOverflowError

StackOverflowError — это ошибка, которая выбрасывается, когда стек вызовов переполняется. Это происходит почти всегда из-за бесконечной рекурсии. Давайте разберёмся глубоко.

Что такое Stack (стек вызовов)

Каждый вызов метода занимает место в стеке:

public static void methodA() {
    methodB();  // Вызов методаB
}

public static void methodB() {
    methodC();  // Вызов методаC
}

public static void methodC() {
    System.out.println("Привет");
}

public static void main(String[] args) {
    methodA();
}

Стек вызовов:

Время выполнения:
main() вызывает methodA()
  ↓ Стек: [main]
methodA() вызывает methodB()
  ↓ Стек: [main, methodA]
methodB() вызывает methodC()
  ↓ Стек: [main, methodA, methodB]
methodC() выполняется
  ↓ Стек: [main, methodA, methodB, methodC]
methodC() завершается
  ↓ Стек: [main, methodA, methodB]
methodB() завершается
  ↓ Стек: [main, methodA]
methodA() завершается
  ↓ Стек: [main]
main() завершается
  ↓ Стек: []

Вся память, которую может занимать стек, ограничена (обычно 1-8 MB в Java). Когда стек переполняется → StackOverflowError.

Пример 1: Простая бесконечная рекурсия

public class StackOverflowExample {
    
    public static void recursiveMethod(int n) {
        System.out.println("Вызов: " + n);
        recursiveMethod(n + 1);  // Вызываем сами себя БЕЗ условия выхода!
    }
    
    public static void main(String[] args) {
        recursiveMethod(1);  // StackOverflowError!
    }
}

Что происходит:

Вызов 1: recursiveMethod(1) → вызывает recursiveMethod(2)
Вызов 2: recursiveMethod(2) → вызывает recursiveMethod(3)
Вызов 3: recursiveMethod(3) → вызывает recursiveMethod(4)
...
Вызов 100000: Стек переполнен! → StackOverflowError

Вывод ошибки:

Exception in thread "main" java.lang.StackOverflowError
    at StackOverflowExample.recursiveMethod(StackOverflowExample.java:3)
    at StackOverflowExample.recursiveMethod(StackOverflowExample.java:4)
    at StackOverflowExample.recursiveMethod(StackOverflowExample.java:4)
    at StackOverflowExample.recursiveMethod(StackOverflowExample.java:4)
    ... (повторяется тысячи раз)

Пример 2: Забыли базовый случай

Это более тонкая ошибка — когда рекурсия теоретически имеет выход, но он никогда не достигается:

public class Factorial {
    
    // Вычисляем факториал
    public static int factorial(int n) {
        System.out.println("factorial(" + n + ")");
        // ЗАБЫЛИ базовый случай!
        return n * factorial(n - 1);  // Всегда рекурсируем
    }
    
    public static void main(String[] args) {
        System.out.println(factorial(5));
    }
}

Правильная версия:

public class Factorial {
    
    public static int factorial(int n) {
        if (n <= 1) {  // Базовый случай!
            return 1;
        }
        return n * factorial(n - 1);
    }
    
    public static void main(String[] args) {
        System.out.println(factorial(5));  // 120 ✓
    }
}

Пример 3: Циклическая рекурсия между методами

иногда ошибка не очевидна — два метода вызывают друг друга:

public class CircularRecursion {
    
    public static void methodA(int n) {
        System.out.println("A: " + n);
        if (n > 0) {
            methodB(n - 1);  // Вызываем B
        }
    }
    
    public static void methodB(int n) {
        System.out.println("B: " + n);
        methodA(n);  // Вызываем A обратно!
    }
    
    public static void main(String[] args) {
        methodA(5);
        // A: 5 → B: 4 → A: 4 → B: 3 → A: 3 → B: 2 → A: 2 → ...
        // StackOverflowError!
    }
}

Эта ошибка сложнее заметить при code review.

Пример 4: Рекурсия в Object.toString()

Есле неправильно переопределить toString(), может быть StackOverflowError:

public class BadToString {
    private String name;
    
    public BadToString(String name) {
        this.name = name;
    }
    
    @Override
    public String toString() {
        // ОШИБКА: вызываем toString сами на себя!
        return "BadToString(" + this.toString() + ")";
    }
    
    public static void main(String[] args) {
        BadToString obj = new BadToString("test");
        System.out.println(obj);  // StackOverflowError!
    }
}

Процесс:

System.out.println(obj)
  → obj.toString()
    → "BadToString(" + this.toString() + ")"
      → this.toString() еще раз!
        → "BadToString(" + this.toString() + ")"
          → this.toString() еще раз!
            → ... бесконечность

Правильная версия:

@Override
public String toString() {
    return "BadToString(" + name + ")";
}

Пример 5: Рекурсия в equals() и hashCode()

public class BadEquals {
    private String value;
    
    @Override
    public boolean equals(Object obj) {
        if (obj == null) return false;
        if (obj instanceof BadEquals) {
            // ОШИБКА: сравниваем equals с equals!
            return this.equals(obj);
        }
        return false;
    }
    
    public static void main(String[] args) {
        BadEquals a = new BadEquals();
        BadEquals b = new BadEquals();
        System.out.println(a.equals(b));  // StackOverflowError!
    }
}

Пример 6: TreeMap с Comparable, который вызывает compareTo рекурсивно

public class BadComparable implements Comparable<BadComparable> {
    private int value;
    
    @Override
    public int compareTo(BadComparable other) {
        // ОШИБКА: compareTo вызывает compareTo!
        return this.compareTo(other);
    }
    
    public static void main(String[] args) {
        TreeMap<BadComparable, String> map = new TreeMap<>();
        BadComparable obj = new BadComparable();
        map.put(obj, "test");  // StackOverflowError в процессе сравнения!
    }
}

Пример 7: JSON десериализация с циклическими ссылками

// Используя Jackson
public class User {
    private String name;
    private List<Post> posts;  // User → List[Post]
}

public class Post {
    private String title;
    private User author;  // Post → User (циклическая ссылка!)
}

public static void main(String[] args) throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    
    User user = new User();
    user.name = "Ivan";
    
    Post post = new Post();
    post.title = "My Post";
    post.author = user;  // Циклическая ссылка
    
    user.posts = List.of(post);
    
    String json = mapper.writeValueAsString(user);
    // Бесконечная сериализация:
    // User → posts[0] (Post) → author (User) → posts[0] (Post) → ...
    // StackOverflowError!
}

Решение:

public class Post {
    private String title;
    
    @JsonBackReference
    private User author;  // Jackson знает о циклических ссылках
}

Пример 8: DOM парсинг с неправильной рекурсией

public class XMLParser {
    
    public static int countNodes(Element element) {
        int count = 1;
        for (Element child : element.getChildren()) {
            count += countNodes(child);  // Рекурсия по дереву
        }
        return count;
    }
    
    public static void main(String[] args) {
        // Если XML глубокий (1000+ уровней), может быть StackOverflowError
        Element root = parseXML("very_deep.xml");
        countNodes(root);  // StackOverflowError на глубоких файлах
    }
}

Как исправить: Tail Recursion или Iteration

Вариант 1: Итерация вместо рекурсии

public static int factorial(int n) {
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

Вариант 2: Tail Recursion (компилятор может оптимизировать)

public static int factorial(int n) {
    return factorialHelper(n, 1);
}

private static int factorialHelper(int n, int accumulator) {
    if (n <= 1) {
        return accumulator;
    }
    return factorialHelper(n - 1, accumulator * n);  // Tail call
}

Вариант 3: Увеличить размер стека (последний вариант)

# JVM опция для увеличения стека
java -Xss10m MyApp  # 10 MB стека вместо дефолтного 1 MB

Как поймать StackOverflowError

Технически его можно поймать, но это очень плохая практика:

try {
    recursiveMethod(1);
} catch (StackOverflowError e) {
    System.out.println("Стек переполнен!");
}

Почему это плохо:

  • StackOverflowError — это критическая ошибка, приложение находится в нестабильном состоянии
  • После переполнения стека много функционала просто не будет работать
  • Нужно пересмотреть алгоритм, а не ловить ошибку

Инструменты для отладки

  1. Профилер памяти
java -Xprof MyApp
# Показывает, какие методы занимают больше всего времени в стеке
  1. Stack Trace Analysis
StackTraceElement[] stackTrace = e.getStackTrace();
// Какой метод вызывает себя?
  1. IDE Debugger
Поставьте breakpoint в рекурсивном методе
Смотрите Call Stack каждого шага

Резюме

StackOverflowError выбрасывается когда:

  1. Бесконечная рекурсия без базового случая
  2. Циклические вызовы между методами
  3. Ошибки в переопределении toString(), equals(), hashCode()
  4. Глубокие деревья данных (DOM, JSON с циклами)
  5. Очень большие данные обработанные рекурсивно

Решение:

  • Всегда проверяйте базовый случай в рекурсии
  • Используйте итерацию вместо рекурсии где возможно
  • Будьте осторожны с циклическими ссылками в сериализации
  • Увеличивайте стек только как последняя мера