5

So I've started playing with Java 8 streams/lambda expressions and have encountered and interesting issue that I'm not really sure how to resolve. So here I am, asking you for help.

Sample code in question:

public void insertBlankPages(File inputFile, String outputFile, final int OFFSET) {
    PDDocument newDocument;
    PDDocument oldDocument;
    try {
        newDocument = createNewDocument();
        oldDocument = createOldDocument(inputFile);
        List<PDPage> oldPages = getAllPages(oldDocument);

        oldPages.stream()
                .limit(oldPages.size() - OFFSET)
                .forEach(page -> {
                    newDocument.addPage(page);
                    newDocument.addPage(new PDPage(page.getMediaBox()));
                });

        newDocument.save(outputFile);
    } catch (IOException e) {
        e.printStackTrace();
    } catch (COSVisitorException e) {
        e.printStackTrace();
    } finally {
        newDocument.close();
        oldDocument.close();
    }
}

With the code above, compiler complains about the call to close() in the finally block. The error is: "Variable newDocument might not have been initialized". Same for oldDocument.

Naturally, I go ahead and initialize the variables as follow:

PDDocument newDocument = null;
PDDocument oldDocument = null;

Now I get compiler error "Variable used in lambda expression should be effectively final".

How to go about this?

Methods createNewDocument and createOldDocument throw an exception, so the calls must be within try/catch block. I also need to close the documents within the finally block.

I should be able to work around this issue by using try-with-resources. But, I'm curious to hear if there's any other proper solution for this.

Thanks.

0

3 Answers 3

3

The proper solution is try-with-resources.

Without that, you'd need to manually nest some more try/finally blocks:

try {
    PDDocument newDocument = createNewDocument();
    try{           
       PDDocument oldDocument = createOldDocument(inputFile);
       try{
          // .....
       }
       finally { oldDocument.close() }
    }
    finally{ newDocument.close(); }
}
catch (IOException e) {
    e.printStackTrace();
} 
Sign up to request clarification or add additional context in comments.

Comments

1

Thanks everyone for the ideas. It appears that there isn't a "good" way of doing this without try-with-resources. Here is an example using try-with-resources.

public void insertBlankPages(File inputFile, String outputFile, final int OFFSET) {
    try (PDDocument newDocument = createNewDocument();
         PDDocument oldDocument = createOldDocument(inputFile)) {

        List<PDPage> oldPages = getAllPages(oldDocument);

        oldPages.stream()
                .limit(oldPages.size() - OFFSET)
                .forEach(page -> {
                    newDocument.addPage(page);
                    newDocument.addPage(new PDPage(page.getMediaBox()));
                });

        newDocument.save(outputFile);
    } catch (IOException e) {
        e.printStackTrace();
    } catch (COSVisitorException e) {
        e.printStackTrace();
    }
}

Comments

0

Simple work-around is to use an array, which itself is final so you can reference it in a lambda, but its contents are free to change.

This code is essentially the same as your, but has no compile errors:

public void insertBlankPages(File inputFile, String outputFile, final int OFFSET) {
    PDDocument[] newDocument = new PDDocument[1]; // automatically filled with a null
    PDDocument oldDocument = null;
    try {
        newDocument[0] = createNewDocument();
        oldDocument = createOldDocument(inputFile);
        List<PDPage> oldPages = getAllPages(oldDocument);

        oldPages.stream()
                .limit(oldPages.size() - OFFSET)
                .forEach(page -> {
                    newDocument[0].addPage(page);
                    newDocument[0].addPage(new PDPage(page.getMediaBox()));
                });

        newDocument[0].save(outputFile);
    } catch (IOException e) {
        e.printStackTrace();
    } catch (COSVisitorException e) {
        e.printStackTrace();
    } finally {
        if (newDocument[0] != null)
            newDocument[0].close();
        if (oldDocument != null)
            oldDocument.close();
    }
}

4 Comments

That will result in a NullPointerException if createNewDocument throws an exception.
@Thilo so what? this is not about debugging his code, this answers "how do I deal with effectively final". When he gets the NPE, he'll figure out he needs to test for null. I've edited the code to handle this though
Why not just make it final (and use try-with-resources)? Seems that was made specifically for cases like this.
@thilo you're quite right; try-with-resources is a cool solution in this case. You should post that solution in your answer ( then I'd vote for it :) )

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.