Listar subdiretórios usando flatMap
Vimos antes como listar arquivos em um diretório especificado. Vamos dar uma olhada em como percorrer os subdiretórios diretos do diretório especificado (a profundidade é 1), primeiro implementar uma versão simples e, em seguida, usar o método flatMap() mais conveniente para implementá-lo.
Primeiro usamos um loop for tradicional para percorrer um diretório especificado. Se houver arquivos no subdiretório, adicione-os à lista; caso contrário, adicione o subdiretório à lista. Por fim, imprima o número total de todos os arquivos. O código está abaixo – este é para o modo difícil.
Copie o código do código da seguinte forma:
public static void listTheHardWay() {
Lista<Arquivo> arquivos = new ArrayList<>();
Arquivo[] arquivosInCurrentDir = new Arquivo(".").listFiles();
for (Arquivo arquivo: arquivosInCurrentDir) {
Arquivo[] arquivosInSubDir = arquivo.listFiles();
if(arquivosInSubDir! = nulo) {
arquivos.addAll(Arrays.asList(filesInSubDir));
} outro {
arquivos.add(arquivo);
}
}
System.out.println("Contagem: " + arquivos.size())
}
Primeiro obtemos a lista de arquivos no diretório atual e depois a percorremos. Para cada arquivo, se houver subarquivos, adicione-os à lista. Não há problema com isso, mas há alguns problemas comuns: mutabilidade, paranóia de tipo básico, imperatividade, verbosidade do código, etc. Um pequeno método chamado flatMap() pode resolver esses problemas.
Como o nome diz, esse método fica nivelado após o mapeamento. Ele mapeia elementos em uma coleção assim como map(). Mas, diferentemente do método map(), a expressão lambda no método map() retorna apenas um elemento, e o que é retornado aqui é um objeto Stream. Portanto, este método nivela vários fluxos e mapeia cada elemento interno para um fluxo nivelado.
Podemos usar flatMap() para realizar várias operações, mas o problema em questão ilustra seu valor. Cada subdiretório possui uma lista ou fluxo de arquivos e queremos obter a lista de arquivos em todos os subdiretórios do diretório atual.
Alguns diretórios podem estar vazios ou não ter elementos filhos. Nesse caso, agrupamos o diretório ou arquivo vazio em um objeto de fluxo. Se quisermos ignorar um arquivo, o método flatMap() no JDK também pode lidar muito bem com arquivos vazios; ele mesclará uma referência nula no fluxo como uma coleção vazia; Vejamos o uso do método flatMap().
Copie o código do código da seguinte forma:
public static void melhorWay() {
Lista<Arquivo> arquivos =
Stream.of(novo arquivo(".").listFiles())
.flatMap(arquivo -> arquivo.listFiles() == null ?
Stream.of(arquivo): Stream.of(arquivo.listArquivos()))
.collect(toList());
System.out.println("Contagem: " + arquivos.size());
}
Primeiro obtemos o fluxo de subarquivos do diretório atual e, em seguida, chamamos seu método flatMap(). Em seguida, passe uma expressão lambda para este método, que retornará um fluxo de subarquivos do arquivo especificado. O método flatMap() retorna uma coleção de arquivos em todos os subdiretórios do diretório atual. Usamos o método collect() e o método toList()( em Coletores para coletá-los em uma lista.
A expressão lambda que passamos para flatMap() retorna um subarquivo de um arquivo. Caso contrário, o fluxo do arquivo será retornado. O método flatMap() mapeia elegantemente esse fluxo em uma coleção de fluxos, depois nivela a coleção e finalmente a mescla em um único fluxo.
O método flatMap() reduz muito trabalho de desenvolvimento – ele combina duas operações consecutivas, geralmente chamadas de tuplas – em uma operação elegante.
Já sabemos como usar o método flatMap() para listar todos os arquivos em um subdiretório imediato. Vamos monitorar as operações de modificação de arquivos.
Monitore modificações de arquivos
Já sabemos como encontrar arquivos e diretórios, mas se quisermos receber mensagens avisadas quando arquivos forem criados, modificados ou excluídos, isso também é muito simples. Tal mecanismo é muito útil para monitorar alterações em arquivos especiais, como arquivos de configuração e recursos do sistema. Vamos explorar esta ferramenta introduzida no Java 7, WatchService, que pode ser usada para monitorar modificações de arquivos. Muitos dos recursos que vemos abaixo vêm do JDK 7, mas a maior melhoria aqui é a conveniência trazida pelos iteradores internos.
Vamos primeiro escrever um exemplo de monitoramento de modificações de arquivos no diretório atual. A classe Path no JDK corresponde a uma instância no sistema de arquivos, que é uma fábrica para serviços de observação. Podemos registrar eventos de notificação para este serviço, assim:
Copie o código do código da seguinte forma:
caminho final caminho = Paths.get(".");
final WatchService watchService =
caminho.getFileSystem()
.newWatchService();
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
System.out.println("Relate qualquer arquivo alterado no próximo 1 minuto...");
Registramos um WatchService para observar modificações no diretório atual. Você pode consultar este WatchService para obter as operações de modificação dos arquivos do diretório, e ele nos retornará essas alterações através de um WatchKey. Assim que tivermos a chave, podemos percorrer todos os seus eventos para obter os detalhes da atualização do arquivo. Como vários arquivos podem ser modificados ao mesmo tempo, a operação de pesquisa pode retornar vários eventos. Vamos dar uma olhada no código de pesquisa e travessia.
Copie o código do código da seguinte forma:
final WatchKey watchKey = watchService.poll(1, TimeUnit.MINUTES);
if(watchKey! = nulo) {
watchKey.pollEvents()
.fluxo()
.forEach(evento ->
System.out.println(event.context()));
}
Como você pode ver aqui, os recursos do Java 7 e do Java 8 aparecem ao mesmo tempo. Convertemos a coleção retornada por pollEvents() em um Java 8 Stream e, em seguida, usamos seu iterador interno para imprimir informações detalhadas de atualização para cada arquivo.
Vamos executar este código e, em seguida, modificar o arquivo sample.txt no diretório atual para ver se o programa consegue detectar esta atualização.
Copie o código do código da seguinte forma:
Relate qualquer arquivo alterado no próximo 1 minuto...
amostra.txt
Quando modificamos este arquivo, o programa irá avisar que o arquivo foi modificado. Podemos usar esse recurso para monitorar atualizações em diferentes arquivos e então executar as tarefas correspondentes. Claro, também podemos registrar apenas operações de criação ou exclusão de arquivos.
Resumir
Com expressões lambda e referências de método, tarefas comuns como manipulação de strings e arquivos e criação de comparadores personalizados tornam-se mais fáceis e concisas. As classes internas anônimas tornam-se elegantes e a variabilidade desaparece como a névoa matinal após o nascer do sol. Outro benefício da codificação nesse novo estilo é que você pode usar os novos recursos do JDK para percorrer diretórios grandes com eficiência.
Agora você sabe como criar uma expressão lambda e passá-la para um método. No próximo capítulo, apresentaremos como usar interfaces funcionais e expressões lambda para projetar software.