The most significant issues in your code are:
- the handling of inputs.
- the use of helper functions from the standard libraries.
The algorithm you have chose in is fine, and both other answer recommend using streams, but I disagree (hence my answer).
So, your user-inputs are not validated, and your code can throw more than just IOExceptions
, but also NoSuchElementException
and so on if the file name arguments are not given on the commandline. You8 need to validate these and throw appropriate exceptions.
Further, you just print stack traces for IOException
, but the program exists with code 0 - a success condition. If you're just going to print the stack trace it makes more sense to declare that the exception is thrown from the main method (and that will automagically print the trace and return with a non-zero code).
Other answers have pointed to the Files.lines(...)
method, but I think you should consider the Files.readAllLines
instead. Note that Files.Lines(...)
and Files.readAllLines(...)
methods will both trim the whitespace at the end-of-line. This may be a problem.
Regardless, Files is a good class to know about.
Consider this code:
public static void main(String[] args) throws IOException {
if (args.length < 2) {
throw new IllegalArgumentException("Expect 2 file-name command-line arguments");
}
Path source = Paths.get(args[0]);
Path target = Paths.get(args[1]);
List<String> lines = Files.readAllLines(source);
lines.sort(String::compareToIgnoreCase);
Files.write(target, lines);
}
I prefer the read-the-whole-file concept to the stream concept. It makes it clear that there are memory-requirements. Additionally, it makes the logic clear.
I would also consider a mechanism for handling raw lines without messing with the line termination padding and characters. Your code strips new-line/carriage-return characters and replaces them with just newline characters. I would prefer to see the line's end-of-line sequence unaltered in the transform. To do this requires a more careful consideration of what methods to use.... None of the Files
methods, nor the default BufferedReader.readLine()
nor Scanner
methods do. You have to override these things. Consider the code:
private static final Pattern EOL = Pattern.compile("$", Pattern.MULTILINE);
private static final List<String> getLines(Path source) throws IOException {
try (Scanner scanner = new Scanner(Files.newBufferedReader(source))) {
scanner.useDelimiter(EOL);
List<String> lines = new ArrayList<>();
while (scanner.hasNext()) {
lines.add(scanner.next());
}
return lines;
}
}
private static final void writeLines(Path target, List<String> lines) throws IOException {
try (BufferedWriter writer = Files.newBufferedWriter(target)) {
for (String line : lines) {
writer.write(line);
}
}
}
public static void main(String[] args) throws IOException {
if (args.length < 2) {
throw new IllegalArgumentException("Expect 2 file-name command-line arguments");
}
Path source = Paths.get(args[0]);
Path target = Paths.get(args[1]);
List<String> lines = getLines(source);
lines.sort(String::compareToIgnoreCase);
writeLines(target, lines);
}
Note how the IOException is still thrown out the main method, but also note that I am using the try-with-resource options for a try-block to ensure the files/streams are closed. The above code does not strip any line-terminators, and writes the output with the same termination as the input.