TLDR
long dirCount = files.peek(System.out::println).count();
The forEach method requires a Consumer as it's parameter:
void forEach(Consumer<? super T> action);
Where the consumer has a contract of:
public interface Consumer<T> {
void accept(T t);
...
}
So in order to satisfy the contract of Consumer you have to provide a method which takes a T t parameter.
In the first example you are using a method reference:
files.forEach(System.out::println);
System.out::println is able to satisfy the contract as it is defined as:
void println(Object obj);
In the second example, you do not provide an expression which can satisfy the parameter, it is just a statement. You might think "Okay, I'll use a lambda that satisfies the contract to do this" but that would also have errors:
files.forEach(file -> dirCount++);
// Fails to compile because dirCount is not final
For an object to be used inside a lambda from the outer scope then it must be final, meaning that you cannot change the reference or primitive value that variable points to. Another problem is that you can only iterate through the stream once. As soon as you iterate through it, then it will be empty.
So given the problem, I would solve this using peek and count. You can use peek to perform an action when each item comes through the stream and sends the value on to the next operator. After that count will evaluate how many items are in the stream (consuming the stream to do so):
long dirCount = files.peek(System.out::println).count();
Answer from flakes on Stack OverflowVideos
Use:
public void listFilesForFolder(final File folder) {
for (final File fileEntry : folder.listFiles()) {
if (fileEntry.isDirectory()) {
listFilesForFolder(fileEntry);
} else {
System.out.println(fileEntry.getName());
}
}
}
final File folder = new File("/home/you/Desktop");
listFilesForFolder(folder);
The Files.walk API is available from Java 8.
try (Stream<Path> paths = Files.walk(Paths.get("/home/you/Desktop"))) {
paths
.filter(Files::isRegularFile)
.forEach(System.out::println);
}
The example uses the try-with-resources pattern recommended in the API guide. It ensures that no matter circumstances, the stream will be closed.
File folder = new File("/Users/you/folder/");
File[] listOfFiles = folder.listFiles();
for (File file : listOfFiles) {
if (file.isFile()) {
System.out.println(file.getName());
}
}
A general note on the use of FileReader: FileReader uses internally a FileInputStream which overrides finalize() and is therefore discouraged to use beacause of the impact it has on garbarge collection especially when dealing with lots of files.
Unless you're using a Java version prior to Java 7 you should use the java.nio.files API instead, creating a BufferedReader with
Path path = Paths.get(filename);
BufferedReader br = Files.newBufferedReader(path);
So the beginning of your stream pipeline should look more like
filenames.map(Paths::get)
.filter(Files::exists)
.map(p -> {
try {
return Optional.of(Files.newBufferedReader(p));
} catch (IOException e) {
return Optional.empty();
}
})
Now to your problem:
Option 1
One way to preserve the original Reader would be to use a Tuple. A tuple (or any n-ary variation of it) is generally a good way to handle multiple results of a function application, as it's done in a stream pipeline:
class ReaderTuple<T> {
final Reader first;
final T second;
ReaderTuple(Reader r, T s){
first = r;
second = s;
}
}
Now you can map the FileReader to a Tuple with the second item being your current stream item:
filenames.map(Paths::get)
.filter(Files::exists)
.map(p -> {
try {
return Optional.of(Files.newBufferedReader(p));
} catch (IOException e) {
return Optional.empty();
}
})
.filter(Optional::isPresent)
.map(Optional::get)
.flatMap(r -> new ReaderTuple(r, yourOtherItem))
....
.peek(rt -> {
try {
rt.first.close() //close the reader or use a try-with-resources
} catch(Exception e){}
})
...
Problem with that approach is, that whenever an unchecked exception occurrs during stream execution betweem the flatMap and the peek, the readers might not be closed.
Option 2
An alternative to use a tuple is to put the code that requires the reader in a try-with-resources block. This approach has the advantage that you're in control to close all readers.
Example 1:
filenames.map(Paths::get)
.filter(Files::exists)
.map(p -> {
try (Reader r = new BufferedReader(new FileReader(p))){
Stream.of(r)
.... //put here your stream code that uses the stream
} catch (IOException e) {
return Optional.empty();
}
}) //reader is implicitly closed here
.... //terminal operation here
Example 2:
filenames.map(Paths::get)
.filter(Files::exists)
.map(p -> {
try {
return Optional.of(Files.newBufferedReader(p));
} catch (IOException e) {
return Optional.empty();
}
})
.filter(Optional::isPresent)
.map(Optional::get)
.flatMap(reader -> {
try(Reader r = reader) {
//read from your reader here and return the items to flatten
} //reader is implicitly closed here
})
Example 1 has the advantage that the reader gets certainly closed. Example 2 is safe unless you put something more between the the creation of the reader and the try-with-resources block that may fail.
I personally would go for Example 1, and put the code that is accessing the reader in a separate function so the code is better readable.
Perhaps a better solution is to use a Consumer<FileReader> to consume each element in the stream.
Another problem you might be running into if there are a lot of files is the files will all be open at the same time. It might be better to close each one as soon as it's done.
Let's say you change the code above into a method that takes a Consumer<BufferedReader>
I probably wouldn't use a stream for this but we can use one anyway to show how one would use it.
public void readAllFiles( Consumer<BufferedReader> consumer){
Objects.requireNonNull(consumer);
filenames.map(File::new)
.filter(File::exists)
.forEach(f->{
try(BufferedReader br = new BufferedReader(new FileReader(f))){
consumer.accept(br);
} catch(Exception e) {
//handle exception
}
});
}
This way we make sure we close each reader and can still support doing whatever the user wants.
For example this would still work
readAllFiles( br -> System.out.println( br.lines().count()));