0

classic imperativ works as expected:

static void updateFile(final File pm, final String replace, 
                                            final String replacement) {
  BufferedReader bri = null;
  BufferedWriter out = null;
  try {
     String fName = pm.getPath() + ".new";
     bri = new BufferedReader(new FileReader(pm));
     out = new BufferedWriter(new FileWriter(fName));
     String line = bri.readLine();
     while (line != null) {
        if (line.contains(replace)) {
           line = line.replace(replace, replacement);
        }
        out.write(line);
        out.newLine();
        line = bri.readLine();
     }
  } catch (IOException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
  } finally {
     try {
        out.flush();
        out.close();
        bri.close();
     } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
     }
  }
}

Now a try of replacement:

  static void updateFileLambda(final File pm, final String replace, 
                          final String replacement) throws Exception {
  String fName = pm.getPath() + ".new";
  BufferedReader bri = new BufferedReader(new FileReader(pm));
  BufferedWriter out = new BufferedWriter(new FileWriter(fName));

  bri.lines().filter(ln -> ln.contains(replace))
             .map(ln -> ln.replace(replace, replacement))
             .forEach(ln -> { 
                               out.write(ln);
                               out.newLine();
          });
}

1.) This raises several problems and doesn't compile because out.write and out.writeLine Eclipse mars is telling me it should be surrounded with try - catch though throws Ecxeption is declared, or the statemens are surrounded with a try-catch as in the classic version.

2.) Each line should be written to the new file. The old and the new should differ only be one modified line. I suspect, the show solution would write the modified line only to the new file.

So if somebody has an idea how to realize correctly, i would very much appreciate!

azurefrog
  • 10,785
  • 7
  • 42
  • 56
juerg
  • 381
  • 4
  • 18

2 Answers2

0

Stream.forEach takes a Consumer argument. So,

         .forEach(ln -> { 
             out.write(ln);
             out.newLine();
         }

is equivalent to

         .forEach( new Consumer<String>() {
             @Override
             public void accept( String ln ) {
                 out.write( ln );
                 out.newLine();
             }
         } );

and, the accept method cannot throw any checked exceptions, which is why a try-catch is needed there.

Also, since you don't close bri, the output is never written, producing an empty file. You can use the try-with-resources statement to have this done automatically:

static void updateFileLambda( final File pm, final String replace, final String replacement )
    throws Exception
{
    String fName = pm.getPath() + ".new";

    try (BufferedReader bri = new BufferedReader( new FileReader( pm ) );
        BufferedWriter out = new BufferedWriter( new FileWriter( fName ) ))
    {

        bri.lines()
        .filter( ln -> ln.contains( replace ) )
        .map( ln -> ln.replace( replace, replacement ) )
        .forEach(
            ln -> {
                try
                {
                    out.write( ln );
                    out.newLine();
                }
                catch ( IOException e )
                {
                    throw new RuntimeException( e );
                }

            } );
    }
}
Kenney
  • 9,003
  • 15
  • 21
  • OK, this makes it clear so far. But this syntax still left some questions open. Within the classic example, I close the streams within the finally block. (with the ugly additional try - catch we are used to.) But where exactly should I achieve the same with the stream syntax. Up to now I don't care if it works or not. The aim is just to find out, how such a case could be written in a declarative manner. Once to made it easier to read and also to open the path for the compiler to optimize it for multihtreading. Adding the try - catch as you show it, kicks us some what back to the classic. – juerg Dec 12 '15 at 09:08
  • That's right, it's a pity. You'd think IO exceptions would be common in processing streams. [This Q/A](http://stackoverflow.com/questions/18198176/java-8-lambda-function-that-throws-exception) has more info on that. As for the closing in the finally, that's essentially what the new try-with-resources does: `try ( AutoCloseable x = ... ) {}` becomes `try { AutoCloseable x = .. } finally { x.close() }` - it is, just as the functional interfaces, merely syntactic sugar (AFAIK). You can of course still do a a try/finally and close the streams yourself. – Kenney Dec 12 '15 at 14:55
0

In fact the key is to use the try-with-resource statement. And the second step required for a clean declarative form in this case is to use PrintWriter which includes the end of line character instead of BufferedWriter wich requires the additional out.newLine(). The outcome of doing so results in the following snippet:

try (
        BufferedReader bri = new BufferedReader(new FileReader(pm));
        PrintWriter out = new PrintWriter(new FileWriter(newFile), true)) {
        bri.lines()
           .map(ln -> ln.replace(replace, replacement))
           .forEach(ln -> out.println(ln));
  }

Not using the try-with resource, sends you to a hell of variables requested to be final for the anonymous inner class and then not being visible within the finally block of the try-catch to close the stream. Conclusion: Nice solutions are possible, but tricky as soon as not fitting into the standard Functional Interfaces! As you show too in the mentioned Q/A.

juerg
  • 381
  • 4
  • 18