Рекурсивный поток

Я хочу перечислить все файлы на моем компьютере рекурсивно, используя Java 8.

Java 8 предоставляет метод listFiles, который возвращает все файлы и каталоги, но без рекурсии. Как я могу использовать его для получения полного рекурсивного списка файлов (без использования мутировавшей коллекции)?

Я пробовал код ниже, но он идет только на один уровень:

static Function<Path, Stream<Path>> listFiles = p -> {
    if (p.toFile().isDirectory()) {
        try { return Files.list(p); }
        catch (Exception e) { return Stream.empty(); }
    } else {
        return Stream.of(p);
    }
};

public static void main(String[] args) throws IOException {
    Path root = Paths.get("C:/temp/");
    Files.list(root).flatMap(listFiles).forEach(System.out::println);
}

И использование return Files.list(p).flatMap(listFiles); не компилируется (не уверен, почему)...

Примечание. Меня не интересуют решения, связанные с FileVisitors или внешними библиотеками.

Ответ 1

Новый API для генерации потока путей путем рекурсивной обработки файловой системы - Files.walk.

Если вы действительно хотите рекурсивно генерировать поток (не обязательно ходить по дереву файлов, но я продолжу использовать его в качестве примера), может быть немного проще выполнить рекурсию с использованием ссылок на методы:

class RecursiveStream {
    static Stream<Path> listFiles(Path path) {
        if (Files.isDirectory(path)) {
            try { return Files.list(path).flatMap(RecursiveStream::listFiles); }
            catch (Exception e) { return Stream.empty(); }
        } else {
            return Stream.of(path);
        }
    }

    public static void main(String[] args) {
        listFiles(Paths.get(".")).forEach(System.out::println);
    }
}

Ссылки на методы оказываются весьма полезными для адаптации именованного метода, который имеет одинаковую "форму" (аргументы и тип возврата) в качестве функционального интерфейса к этому функциональному интерфейсу. Это также позволяет избежать потенциальной цикличности инициализации, сохраняя лямбда в экземпляре или статической переменной и вызывая себя рекурсивно.

Ответ 2

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

Итак, в функции return Files.list(p).flatMap(listFiles); не компилируется, но return Files.list(p).flatMap(q -> listFiles.apply(q)); делает.

Это выводит все файлы в данной папке рекурсивно:

static final Function<Path, Stream<Path>> listFiles = p -> {
    if (p.toFile().isDirectory()) {
        try { return Files.list(p).flatMap(q -> listFiles.apply(q)); }
        catch (Exception e) { return Stream.empty(); }
    } else {
        return Stream.of(p);
    }
};

public static void main(String[] args) throws IOException {
    Path root = Paths.get("C:/temp/");
    Files.list(root).flatMap(listFiles).forEach(System.out::println);
}

но, как указано, это не нужно:

Files.walk(root).forEach(System.out::println);

делает то же самое...