使用flatMap列出子目錄
前面已經(jīng)看到如何列出指定目錄下的文件了。我們再來看下如何遍歷指定目錄的直接子目錄(深度為1),先實現(xiàn)一個簡單的版本,然后再用更方便的flatMap()方法來實現(xiàn)。
我們先用傳統(tǒng)的for循環(huán)來遍歷一個指定的目錄。如果子目錄中有文件,就添加到列表里;否則就把子目錄添加到列表里。最后,打印出所有文件的總數(shù)。代碼在下面——這個是困難模式的。
public static void listTheHardWay() {
List<File> files = new ArrayList<>();
File[] filesInCurrentDir = new File(".").listFiles();
for(File file : filesInCurrentDir) {
File[] filesInSubDir = file.listFiles();
if(filesInSubDir != null) {
files.addAll(Arrays.asList(filesInSubDir));
} else {
files.add(file);
}
}
System.out.println("Count: " + files.size())
}
我們先獲取當前目錄下的文件列表,然后進行遍歷。對于每個文件,如果它有子文件,就把它們添加到列表中。這樣做是沒問題的,不過它有一些常見的問題:可變性,基本類型偏執(zhí),命令式,代碼冗長,等等。一個叫flatMap()的小方法就可以解決掉這些問題。
正如這個名字所說的,這個方法在映射后會進行扁平化。它會像map()一樣對集合中的元素進行映射。但是和map()方法不同的是,map()方法里面的lambda表達式只是返回一個元素,而這里返回的是一個Stream對象。于是這個方法將多個流壓平,將里面的每個元素映射到一個扁平化的流中。
我們可以用flatMap()來執(zhí)行各種操作,不過現(xiàn)在手頭的這個問題就正好詮釋了它的價值。每個子目錄都有一個文件的列表或者說流,而我們希望獲取當前目錄下的所有子目錄中的文件列表。
有一些目錄可能是空的,或者說沒有子元素。這種情況下,我們將這個空目錄或者文件包裝成一個流對象。如果我們想忽略某個文件,JDK中的flatMap()方法也可以很好的處理空文件;它會把一個空引用作為一個空集合合并到流里。來看下flatMap()方法的使用。
public static void betterWay() {
List<File> files =
Stream.of(new File(".").listFiles())
.flatMap(file -> file.listFiles() == null ?
Stream.of(file) : Stream.of(file.listFiles()))
.collect(toList());
System.out.println("Count: " + files.size());
}
我們先是獲取了當前目錄的子文件流,然后調(diào)用了它的flatMap()方法。然后將一個lambda表達式傳給這個方法,這個表達式會返回指定文件的子文件的流。flatMap()方法返回的的是當前目錄所有子目錄下的文件的集合。我們使用collect()方法以及Collectors里面的toList()(方法把它們收集到一個列表中。
我們傳給flatMap()的這個lambda表達式,它返回的是一個文件的子文件。 如果沒有的話,則返回這個文件的流。flatMap()方法優(yōu)雅地將這個流映射到一個流的集合中,然后將這個集合扁平化,最終合并到一個流中。
flatMap()方法減少了許多開發(fā)的工作——它將兩個連續(xù)的操作很好的結(jié)合到了一起,這通常稱為元組 ——用一個優(yōu)雅的操作就完成了。
我們已經(jīng)知道如何使用flatMap()方法來將直接子目錄中的所有文件列出來。下面我們來監(jiān)控一下文件的修改操作。
監(jiān)控文件修改
我們已經(jīng)知道如何查找文件及目錄,不過如果我們希望在文件創(chuàng)建,修改或刪除的時候,能夠接收到提示消息的話,這個也非常簡單。這樣的機制對于監(jiān)視一些特殊文件比如配置文件,系統(tǒng)資源的改動非常有用。下面我們來探索下Java 7中引入的這個工具,WatchService,它可以用來監(jiān)控文件的修改。下面我們看到的許多特性都來自JDK 7,而這里最大的改進就是內(nèi)部迭代器帶來的便利性。
我們先來寫個監(jiān)控當前目錄中的文件修改的例子。JDK中的Path類會對應(yīng)文件系統(tǒng)中的一個實例,它是一個觀察者服務(wù)的工廠。我們可以給這個服務(wù)注冊通知事件,就像這樣:
inal Path path = Paths.get(".");
final WatchService watchService =
path.getFileSystem()
.newWatchService();
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
System.out.println("Report any file changed within next 1 minute...");
我們注冊了一個WatchService來觀察當前目錄的修改。你可以輪詢這個WatchService來獲取目錄下文件的修改操作,它會通過一個WatchKey將這些改動返回給我們。一旦我們拿到了這個key,可以遍歷它的所有事件來獲取文件更新的詳細信息。因為可能會有多個文件被同時修改,poll操作可能會返回多個事件。來看下輪詢以及遍歷的代碼。
final WatchKey watchKey = watchService.poll(1, TimeUnit.MINUTES);
if(watchKey != null) {
watchKey.pollEvents()
.stream()
.forEach(event ->
System.out.println(event.context()));
}
這里可以看到,Java 7和Java 8的特性同時出場了。我們把pollEvents()返回的集合轉(zhuǎn)化成了一個Java 8的Stream,然后使用它的內(nèi)部迭代器來打印出每個文件的詳細的更新信息。
我們來運行下這段代碼,然后將當前目錄下的sample.txt文件修改一下,看下這個程序是否能察覺這個更新。
Report any file changed within next 1 minute...
sample.txt
當我們修改了這個文件的時候,程序會提示說文件被修改了。我們可以用這個功能來監(jiān)視不同文件的更新,然后執(zhí)行相應(yīng)的任務(wù)。當然我們也可以只注冊文件新建或者刪除的操作。
總結(jié)
有了lambda表達式和方法引用后,像字符串及文件的操作,創(chuàng)建自定義比較器這些常見的任務(wù)都變得更簡單也更簡潔了。匿名內(nèi)部類也變得優(yōu)雅起來了,而可變性就像日出后的晨霧一樣,也消失得無影無蹤了。使用這種新風(fēng)格進行編碼還有一個福利,就是你可以使用JDK的新設(shè)施來高效地遍歷龐大的目錄。
現(xiàn)在你已經(jīng)知道如何創(chuàng)建lambda表達式并把它傳遞給方法了。下一章我們會介紹如何使用函數(shù)式接口及l(fā)ambda表達式進行軟件的設(shè)計。