Search code examples
java-8functional-java

Functionaljava: sorting a list of arbitrary types


I have a very simple Java bean, WatchedFile, which has a fileName field.

I would like to sort a fj.data.List of WatchedFile objects, but I'm struggling with defining an fj.Ord for the list's sort() method. This is what I came up with:

protected List<WatchedFile> getWatchedFileList(String path) throws IOException {
    List<File> files = List.list(new File(path).listFiles());
    return files
            .map((file) -> new WatchedFile(file.getName(), false, file.length()))
            .sort(Ord.ord(new F<WatchedFile, F<WatchedFile, Ordering>>()
            {
                @Override
                public F<WatchedFile, Ordering> f(final WatchedFile watchedFile1)
                {
                    return new F<WatchedFile, Ordering>()
                    {
                        @Override
                        public Ordering f(final WatchedFile watchedFile2)
                        {
                            int compareResult = watchedFile1.fileName.compareTo(watchedFile2.fileName);
                            return (compareResult < 0 ? Ordering.LT :
                                    (compareResult > 0 ? Ordering.GT : Ordering.EQ));
                        }
                    };
                }
            }));
}

This is ugly! I'm sure there is a better way of instantiating an Ord object... Possibly utilizing some Java 8 magick?


Solution

  • protected List<WatchedFile> getWatchedFileList(String path) throws IOException {
        List<File> files = Arrays.asList(new File(path).listFiles());
        return files.stream()
            .map(file -> new WatchedFile(file.getName(), false, file.length()))
            .sorted((wf1, wf2)->wf1.fileName.compareTo(wf2.fileName))
            .collect(Collectors.toList());
    }
    

    It’s recommended to have a method public String getFileName() in your class WatchedFile. In that case you can simply say:

    protected List<WatchedFile> getWatchedFileList(String path) throws IOException {
        List<File> files = Arrays.asList(new File(path).listFiles());
        return files.stream()
            .map(file -> new WatchedFile(file.getName(), false, file.length()))
            .sorted(Comparator.comparing(WatchedFile::getFileName))
            .collect(Collectors.toList());
    }
    

    And, using NIO2 for getting the directory entries, it may look like:

    protected List<WatchedFile> getWatchedFileList(String path) throws IOException {
        try {
            return Files.list(Paths.get(path))
                .map(p -> new WatchedFile(p.toString(), false, fileSize(p)))
                .sorted(Comparator.comparing(WatchedFile::getFileName))
                .collect(Collectors.toList());
        } catch(UncheckedIOException ex) { throw ex.getCause(); }
    }
    private long fileSize(Path path) {
        try { return Files.size(path); }
        catch (IOException ex) { throw new UncheckedIOException(ex); }
    }
    

    If you want to stay within the “functional-java” API, a solution can look like:

    protected List<WatchedFile> getWatchedFileList(String path) throws IOException {
        List<File> files = List.list(new File(path).listFiles());
        return files
            .map(file -> new WatchedFile(file.getName(), false, file.length()))
            .sort(Ord.stringOrd.comap(wf -> wf.fileName));
    }
    

    The key point is that you don’t need (shouldn’t) re-implement the way, Strings are compared. Instead, specify the function to get the property value to compare. Compare with Java 8 factory method Comparator.comparing used in the second code example.