0

I want to make the comparator for file object as parameterized. (Ex: sort by name, length, size, last modified, file extension). One method with parameterized as sortType.

I don't want different methods as stated in Sort Array by Date and Size (Files)

private void sortByName(File[] files){
  Arrays.sort(files, new Comparator<File>() {
    @Override
    public int compare(File t, File t1) {
        return t.getName().compareTo(t1.getName());
    }
  });
}

private void sortByDate(File[] files){
 Arrays.sort(files, new Comparator<File>() {
    @Override
    public int compare(File t, File t1) {
        return (int) (t.lastModified() - t1.lastModified());
    }
  });
}

private void sortBySize(File[] files){
  Arrays.sort(files, new Comparator<File>() {
    @Override
    public int compare(File t, File t1) {
        return (int) (t.length() - t1.length());
    }
  });
}

How to create a method something like sort(String sortType) or sort(Enum sortType) and have only one sorting comparator to do the needful.

Having multiple methods is having maintenance issue and unnecessary boilerplate code.

Thanks.

Java Deve
  • 3
  • 1
  • 3
    I note that each of your methods could have a single (simple) line method body if you use lambda expressions. Is that really so much of a maintenance issue? But yes, you could create an enum where each enum value has a `Comparator` provided in its constructor... have you tried that? What happened? – Jon Skeet Apr 19 '22 at 06:18
  • @JonSkeet Can you please share a code snippet. – Java Deve Apr 19 '22 at 06:29
  • 2
    Note: using subtraction in `comparetTo` is already *dangerous* when comparing `int`, even worse if comparing `long` - due to overflow or underflow, the result sign can be wrong - better use `Long.compare` (or `Integer.compare`) ||| BTW the `Comparator` already has factory methods to create `Comparator` for a given `Supplier` (e.g. `comparingLong`) – user16320675 Apr 19 '22 at 06:32

5 Answers5

1

You can create an enum that implements Comparator<File>, and have each enum type to implement the compare method

enum FileComparator implements Comparator<File> {

    ByName {
        @Override
        public int compare(File f1, File f2) {
            return f1.getName().compareTo(f2.getName());
        }
    },
    ByDate {
        @Override
        public int compare(File f1, File f2) {
            return (int) (f1.lastModified() - f2.lastModified());
        }
    },
    BySize {
        @Override
        public int compare(File f1, File f2) {
            return (int) (f1.length() - f2.length());
        }
    };

}

Below code is the implementation to test

public class Lab {
    public static void main(String[] args) {
        File folder = new File("/Users/**/Documents");
        File[] listOfFiles = folder.listFiles();
        Arrays.sort(listOfFiles,FileComparator.ByDate);
        System.out.println(Arrays.toString(listOfFiles));
    }
}
HariHaravelan
  • 1,041
  • 1
  • 10
  • 19
  • I just wanted to sort any one of them and not all. Your example has for loop and confusing. Can you please share a code snippet which does one sort by passing the parameter, so that will change it accordingly – Java Deve Apr 19 '22 at 06:28
  • @JavaDeve updated, let me know if it is understandable – HariHaravelan Apr 19 '22 at 06:34
1

With lambdas, method references here, and the comparing methods of Comparator, one can write:

    Arrays.sort(listOfFiles, Comparator.comparing(File::getName));
    Arrays.sort(listOfFiles, Comparator.comparingLong(File::lastModified));
    Arrays.sort(listOfFiles, Comparator.comparingLong(File::length));

This even allows composition of comparators, like reverse order, or first by last modified, then length or such.

    Arrays.sort(listOfFiles, Comparator.comparingLong(File::length).reversed());
    Arrays.sort(listOfFiles, Comparator.comparingLong(File::lastModified)
                                       .thenComparingLong(File::length));

For an internal usage this suffices. For an external library API maybe not.


Sorting by file extension:

static final Comparator<File> BY_EXTENSION =
    Comparator.comparing(f -> f.getName().replaceFirst("^(.*?)((\\.[^\\.]*)?)$", "$2"));
Arrays.sort(listOfFiles, BY_EXTENSION);

The lambda passed to sort does not need to be a method reference like File::getName but can be some function on File.

The replaceFirst should only keep the extension, yielding ".txt" or "" (no dot in name).

Or if you need the file extension as function:

class Foo { // Need some class name.
    public static String getFileExtension(File file) {
        return file.getName()
            .replaceFirst("^(.*?)((\\.[^\\.]*)?)$", "$2");
    }
}

Arrays.sort(Foo::getFileExtension);

Again using a method reference, though of a static method, File as parameter instead of as this.

Should one use a Comparator<Path> instead of Comparator<File> - as Path is more general than File -, then be aware that Path.getName() returns a Path:

static final Comparator<Path> BY_EXTENSION =
    Comparator.comparing(p -> p.getName().toString()
        .replaceFirst("^(.*?)((\\.[^\\.]*)?)$", "$2"));

In one method:

public static final Comparator<Path> BY_NAME =
    Comparator.comparing(File::getName);
public static final Comparator<Path> BY_MODIFIED =
    Comparator.comparingLong(File::lastModified);
public static final Comparator<Path> BY_LENGTH =
    Comparator.comparingLong(File::length);
public static final Comparator<Path> BY_EXTENSION =
    Comparator.comparing(p -> p.getName().toString()
        .replaceFirst("^(.*?)((\\.[^\\.]*)?)$", "$2"));

Either

Arrays.sort(listOfFiles, BY_NAME);

or:

void sortFiles(File[] files, Comparator<File> comparator) {
    Arrays.sort(files, comparator);
}

sortFiles(listOfFiles, BY_NAME);

With Stream:

List<File> sortFiles(Stream<File> fileStream, Comparator<File> comparator) {
    return fileStream.sorted(comparator).collect(Collectors.toList());
}

File[] fileArray = ...
List<File> fileList = ...
List<File> sorted = sorted(Arrays.stream(fileArray), BY_NAME);
List<File> sorted = sorted(fileList.stream(), BY_NAME);
Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
0

If I understand you correctly, you want the code look more concise with just one Comparator. Now with Lambda and Java 13's enhanced switch, you can write sortBy as follows:

enum SortType {
    Name, Date, Size
}

void sortBy(SortType type, File[] files) {
    Arrays.sort(files, (t, t1) -> switch (type) {
        case Name -> t.getName().compareTo(t1.getName());
        case Date -> Long.compare(t.lastModified(), t1.lastModified());
        case Size -> Long.compare(t.length(), t1.length());
    });
}

But from the view of performance, it would be better to use three independent comparators.

Without lambda and enhanced switch, sortBy is written as:

void sortBy(SortType type, File[] files) {
    Arrays.sort(files, new Comparator<File>() {
        @Override
        public int compare(File t, File t1) {
            switch (type) {
                case Name:
                    return t.getName().compareTo(t1.getName());
                case Date:
                    return Long.compare(t.lastModified(), t1.lastModified());
                case Size:
                    return Long.compare(t.length(), t1.length());
            };
            throw new IllegalStateException("Unreachable!");
        }
    });
}
zhh
  • 2,346
  • 1
  • 11
  • 22
0

It's easiest to use a Stream:

File[] files = {};

Arrays
    .stream(files)
    .sorted(
        Comparator
            .comparing(File::getName)
            .thenComparing(File::lastModified)
            .thenComparing(File::length)
    )
    .collect(Collectors.toList());
0

You can try with this method, you don't need to create enum
Doing it with strings would be the simplest.

public static List shortFiles(String shortType, List files){
    files.sort(new Comparator<File>() {
    @Override
    public int compare(File t, File t1) {
        int x=0;
        switch (shortType) {
            case "name" ->{x = t.getName().compareTo(t1.getName());}
            case "date" ->{x = Long.compare(t.lastModified(), t1.lastModified());}
            case "size" ->{x = Long.compare(t.length(), t1.length());}
        }
        return x;
    }
});
    return files;
}

Additional to that i have created a filter for the file List

/* I create a FileFilter for files with a extension diferent of *.tmp or *.TMP */
FileFilter logFilefilter = new FileFilter() {
        @Override
        public boolean accept(File file) {                
            return !file.getName().toLowerCase().endsWith(".tmp") && file.getName().contains(".")&& file.isFile();
        }
};

Then create the file List with the File Filter (optional) specifying the path for the files

    /* Specify the Directory which contains the files to short */
    File f = new File("C:\\Users\\brdn\\AppData\\Local\\Temp");
    
    if(f.exists()){//execute if the Directory exists
        /* create the files with the FileFilter (optionally) */
        List<File> files = Arrays.asList(f.listFiles(logFilefilter));
    }else
        System.out.println("The directory: " + f.getAbsolutePath() + " does not exist");

Finally here you a complete example

package brdn_company.mavenproject2;

import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.text.SimpleDateFormat;

public class Mavenproject2 {

public static void main(String[] args) {
    /* I create a FileFilter for files with a extension diferent of *.tmp or *.TMP */
    FileFilter logFilefilter = new FileFilter() {
        @Override
        public boolean accept(File file) {                
            return !file.getName().toLowerCase().endsWith(".tmp") && file.getName().contains(".")&& file.isFile();
        }
    };
    
    /* Specify the Directory which contains the files to short */
    File f = new File("C:\\Users\\brdn\\AppData\\Local\\Temp");
    
    if(f.exists()){//execute if the Directory exists
        /* create the files with the FileFilter (optionally) */
        List<File> files = Arrays.asList(f.listFiles(logFilefilter));
        
        System.out.println("----------------SHORTING BY NAME----------------");
        files = shortFiles("name", files);
        for(File file : files)
                System.out.println(file.getName());

        System.out.println("\n----------------SHORTING BY SIZE----------------");
        files = shortFiles("size", files);
        for(File file : files)
                System.out.format("%-70s size= %d bytes \n",file.getName(), file.length());
        
        System.out.println("\n----------------SHORTING BY DATE----------------");
        files = shortFiles("date", files);
        for(File file : files)
                System.out.format("%-70s Date= %s \n", file.getName(), new SimpleDateFormat("dd'/'MMMM'/'YYYY").format(new Date(file.lastModified())) );
    }else
        System.out.println("The directory: " + f.getAbsolutePath() + " does not exist");
}

public static List shortFiles(String shortType, List files){
    files.sort(new Comparator<File>() {
    @Override
    public int compare(File t, File t1) {
        int x=0;
        switch (shortType) {
            case "name" ->{x = t.getName().compareTo(t1.getName());}
            case "date" ->{x = Long.compare(t.lastModified(), t1.lastModified());}
            case "size" ->{x = Long.compare(t.length(), t1.length());
            case "extension" ->{
                    x = t.getName().contains(".")==t1.getName().contains(".")? t.getName().split("[.]")[1].compareTo(t.getName().split("[.]")[1])
                            : !t.getName().contains(".")?1:
                            -1;}}
        }
        return x;
    }
});
    return files;
}
}

Which outputs:

----------------SHORTING BY NAME----------------
.ses
DTL-5996-162487191930034982.fxml
DTL-5996-3330347463011941505.fxml
DTL-5996-3504539881087568310.fxml
JavaDeployReg.log
JavaLauncher.log
fallback4142601253054055938.netbeans.pom
fallback8710927843535716668.netbeans.pom
jusched.log
pcsx2-1.6.0-include_standard.exe
sqlite-3.34.0-ffd2f809-64f1-445e-85ce-61dea205401e-sqlitejdbc.dll

----------------SHORTING BY SIZE----------------
.ses                                                                   size= 53 bytes 
fallback4142601253054055938.netbeans.pom                               size= 203 bytes 
fallback8710927843535716668.netbeans.pom                               size= 207 bytes 
JavaDeployReg.log                                                      size= 1943 bytes 
DTL-5996-162487191930034982.fxml                                       size= 4248 bytes 
DTL-5996-3504539881087568310.fxml                                      size= 4248 bytes 
DTL-5996-3330347463011941505.fxml                                      size= 4298 bytes 
JavaLauncher.log                                                       size= 12978 bytes 
pcsx2-1.6.0-include_standard.exe                                       size= 137428 bytes 
jusched.log                                                            size= 623658 bytes 
sqlite-3.34.0-ffd2f809-64f1-445e-85ce-61dea205401e-sqlitejdbc.dll      size= 852992 bytes 

----------------SHORTING BY DATE----------------
pcsx2-1.6.0-include_standard.exe                                       Date= 06/mayo/2020 
sqlite-3.34.0-ffd2f809-64f1-445e-85ce-61dea205401e-sqlitejdbc.dll      Date= 13/abril/2022 
JavaDeployReg.log                                                      Date= 15/abril/2022 
jusched.log                                                            Date= 15/abril/2022 
JavaLauncher.log                                                       Date= 17/abril/2022 
fallback8710927843535716668.netbeans.pom                               Date= 17/abril/2022 
fallback4142601253054055938.netbeans.pom                               Date= 17/abril/2022 
DTL-5996-162487191930034982.fxml                                       Date= 17/abril/2022 
DTL-5996-3504539881087568310.fxml                                      Date= 17/abril/2022 
DTL-5996-3330347463011941505.fxml                                      Date= 17/abril/2022 
.ses                                                                   Date= 19/abril/2022 

Good luck :) and sorry for the grammar, I'm a Spanish speaker.

brandon v
  • 36
  • 5