I have recently just finished an upgrade of the JavaSE 7 hands on labs that we conduct at Oracle Technical Network Java Developer Days. One of the objectives of the lab is to teach the attendees about the new JavaSE 7 features from Project Coin and NIO2. The other, somewhat subtle objective, is to introduce the NetBeans IDE and it's refactoring capabilities. Additionally, with the recent JavaSE 6 end of updates announcement it is a good time to share some refactoring hints to others that might be thinking of upgrading their code when as they upgrade to JavaSE 7.
Starting Point:
The pre-JavaSE 7 HOL code from the original exercise looked like this:
private static void tryWithResource6c() {
System.out.println("TESTING TRY WITH RESOURCES 6c");
UglyCloser closer1 = new UglyCloser("First closer");
UglyCloser closer2 = new UglyCloser("Second closer");
try {
fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
fakeAssert(!closer2.isClosed(), "Closer 2 should be open");
closer1.close();
closer2.close();
} catch (Exception ex) {
// quietly ignore
}
fakeAssert(closer1.isClosed(), "Closer1 should be closed");
fakeAssert(closer2.isClosed(), "Closer1 should be closed");
}
I would have expected to see a refactoring hint, the little light bulb with the yellow triangle with an "!" icon, next to one of the line numbers indicating that the code was a candidate to "convert to try-with-resources", but oddly no such icon appeared. This prompted me to start trying variations of the code that would encourage the refactoring. I tried several variations of the above code attempting to find what could and couldn't be refactored.
Example 1:
The following code produces a refactoring hint...
private static void tryWithResource2a() {
System.out.println("TESTING TRY WITH RESOURCES");
UglyCloser closer1 = new UglyCloser("First closer");
try {
fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
} catch (Exception ex) {
// quietly ignore
} finally {
closer1.close();
}
}
and gets refactored as you would expect
private static void tryWithResource6a() throws Exception { System.out.println("TESTING TRY WITH RESOURCES 6A"); try (UglyCloser closer1 = new UglyCloser("First closer")) { fakeAssert(!closer1.isClosed(), "Closer 1 should be open"); } catch (Exception ex) { // quietly ignore } }
Example 2:
But if there is some reference to an object outside the try finally block it doesn't get refactored. Which I probably accept as reasonable.
private static void tryWithResource2() {
System.out.println("TESTING TRY WITH RESOURCES");
UglyCloser closer1 = new UglyCloser("First closer");
try {
fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
} catch (Exception ex) {
// quietly ignore
} finally {
closer1.close();
}
fakeAssert(closer1.isClosed(), "Closer1 should be closed");
}
The key take away here is that the current form of try-with-resource uses a fresh resource variable, if there are references to the variable outside of try-finally, those references would be out of scope. It's unlikely this example would exist in real world code, but it's worth pointing out to look for out of scope resource variables when converting code.
Example 3:
Oddly, if you remove the finally clause it doesn't get refactored. This I think is probably bad code because closer1 is never closed but...
private static void tryWithResource2b() {
System.out.println("TESTING TRY WITH RESOURCES");
UglyCloser closer1 = new UglyCloser("First closer");
try {
fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
} catch (Exception ex) {
// quietly ignore
}
}
In this example, without the close in the finally clause there is really a change in code semantics. Remember that prior to JavaSE 7 we didn't have an autoclose feature so either the original programmer was incorrect in not calling close, or intentionally was trying to escape with out the close. In either case further analysis is required.
Example 4:
Similarly, if you close the object in the try statement it doesn't get closed either.
private static void tryWithResource2b() {
System.out.println("TESTING TRY WITH RESOURCES");
UglyCloser closer1 = new UglyCloser("First closer");
try {
fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
closer1.close();
} catch (Exception ex) {
// quietly ignore
}
}
In this example who knows what the programmer is thinking. It could be a failed attempt to call close on the programmer's part, or it might be important to have close inside the try. At this point you have no choice but to try to think what the programmer was trying to accomplish when you're refactoring.
Example 5:
Finally, if you're dealing with more than one object in the try-catch block only one get refactored. This I think is definitely a bug in NetBeans as it should be handled. Of course all the single variants abortions above apply as well.
private static void tryWithResource1a() {
System.out.println("TESTING TRY WITH RESOURCES");
UglyCloser closer1 = new UglyCloser("First closer");
UglyCloser closer2 = new UglyCloser("Second closer");
try {
fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
fakeAssert(!closer2.isClosed(), "Closer 2 should be open");
} catch (Exception ex) {
// quietly ignore
} finally {
closer1.close();
closer2.close();
}
}
refactors to ...
private static void tryWithResource6b() throws Exception {
System.out.println("TESTING TRY WITH RESOURCES 6a");
UglyCloser closer1 = new UglyCloser("First closer");
try (UglyCloser closer2 = new UglyCloser("Second closer")) {
fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
fakeAssert(!closer2.isClosed(), "Closer 2 should be open");
} catch (Exception ex) {
// quietly ignore
} finally {
closer1.close();
}
}
which is just a partial refactoring in that it doesn't refactor closer1. This might be a bug in NetBeans, but if you want to take full advantage of try-with-resources you need to hand refactor.New HOL Exercises:
For the hands on lab I decided to highlight the three types of refactoring experiences you can expect with try-catch refactoring.
1. Complete refactoring
private static void tryWithResource6a() throws Exception {
System.out.println("TESTING TRY WITH RESOURCES 6A");
UglyCloser closer1 = new UglyCloser("First closer");
try {
fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
} catch (Exception ex) {
// quietly ignore
} finally {
closer1.close();
}
}
refactors to
private static void tryWithResource6a() throws Exception {
System.out.println("TESTING TRY WITH RESOURCES 6A");
try (UglyCloser closer1 = new UglyCloser("First closer")) {
fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
} catch (Exception ex) {
// quietly ignore
}
}
2. Partial refactoring
private static void tryWithResource6a() throws Exception { System.out.println("TESTING TRY WITH RESOURCES 6A"); UglyCloser closer1 = new UglyCloser("First closer"); try { fakeAssert(!closer1.isClosed(), "Closer 1 should be open"); } catch (Exception ex) { // quietly ignore } finally { closer1.close(); } }refactors incorrectly to
private static void tryWithResource6a() throws Exception {
System.out.println("TESTING TRY WITH RESOURCES 6A");
try (UglyCloser closer1 = new UglyCloser("First closer")) {
fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
} catch (Exception ex) {
// quietly ignore
}
}
requiring a hand change to
private static void tryWithResource6b() throws Exception {
System.out.println("TESTING TRY WITH RESOURCES 6b");
try (UglyCloser closer1 = new UglyCloser("First closer");
UglyCloser closer2 = new UglyCloser("Second closer")) {
fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
fakeAssert(!closer2.isClosed(), "Closer 2 should be open");
} catch (Exception ex) {
// quietly ignore
}
}
3. No refactoring
private static void tryWithResource6c() {
System.out.println("TESTING TRY WITH RESOURCES 6c");
UglyCloser closer1 = new UglyCloser("First closer");
UglyCloser closer2 = new UglyCloser("Second closer");
try {
fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
fakeAssert(!closer2.isClosed(), "Closer 2 should be open");
closer1.close();
closer2.close();
} catch (Exception ex) {
// quietly ignore
}
fakeAssert(closer1.isClosed(), "Closer1 should be closed");
fakeAssert(closer2.isClosed(), "Closer1 should be closed");
}
Which has to be hand refactored to
private static void tryWithResource6b() throws Exception {
System.out.println("TESTING TRY WITH RESOURCES 6b");
try (UglyCloser closer1 = new UglyCloser("First closer");
UglyCloser closer2 = new UglyCloser("Second closer")) {
fakeAssert(!closer1.isClosed(), "Closer 1 should be open");
fakeAssert(!closer2.isClosed(), "Closer 2 should be open");
} catch (Exception ex) {
// quietly ignore
}
}