Java 7, codenamed Dolphin, was a major update that debuted on July 7, 2011 and became available to developers on July 28, 2011. The development cycle was divided into thirteen major milestones, with the last one completed on June 6, 2011. On average, each milestone had 8 builds, primarily consisting of feature enhancements and bug fixes.

String in switch Statement

A feature long supported in other languages finally arrived in Java 7. Using String in switch-case makes code much cleaner. Under the hood, this is implemented via String.hashCode(). Consider this code:

1
2
3
4
5
6
7
8
9
10
11
12
public static int getDayOfWeek(String dayOfWeek) {
switch (dayOfWeek) {
case "Monday": return 1;
case "Tuesday": return 2;
case "Wednesday": return 3;
case "Thursday": return 4;
case "Friday": return 5;
case "Saturday": return 6;
case "Sunday": return 0;
default: throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeekArg);
}
}

The generated bytecode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
public static int getDayOfWeek(java.lang.String);
descriptor: (Ljava/lang/String;)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=3, args_size=1
0: aload_0
1: astore_1
2: iconst_m1
3: istore_2
4: aload_1
5: invokevirtual #2 // Method java/lang/String.hashCode:()I
8: lookupswitch { // 7
-2049557543: 146
-1984635600: 76
-1807319568: 160
-897468618: 104
687309357: 90
1636699642: 118
2112549247: 132
default: 172
}
76: aload_1
77: ldc #3 // String Monday
79: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
82: ifeq 172
85: iconst_0
86: istore_2
87: goto 172
90: aload_1
91: ldc #5 // String Tuesday
93: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
96: ifeq 172
99: iconst_1
100: istore_2
101: goto 172
104: aload_1
105: ldc #6 // String Wednesday
107: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
110: ifeq 172
113: iconst_2
114: istore_2
115: goto 172
118: aload_1
119: ldc #7 // String Thursday
121: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
124: ifeq 172
127: iconst_3
128: istore_2
129: goto 172
132: aload_1
133: ldc #8 // String Friday
135: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
138: ifeq 172
141: iconst_4
142: istore_2
143: goto 172
146: aload_1
147: ldc #9 // String Saturday
149: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
152: ifeq 172
155: iconst_5
156: istore_2
157: goto 172
160: aload_1
161: ldc #10 // String Sunday
163: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
166: ifeq 172
169: bipush 6
171: istore_2
172: iload_2
173: tableswitch { // 0 to 6
0: 216
1: 218
2: 220
3: 222
4: 224
5: 226
6: 229
default: 231
}
216: iconst_1
217: ireturn
218: iconst_2
219: ireturn
220: iconst_3
221: ireturn
222: iconst_4
223: ireturn
224: iconst_5
225: ireturn
226: bipush 6
228: ireturn
229: iconst_0
230: ireturn
231: new #11 // class java/lang/IllegalArgumentException
234: dup
235: new #12 // class java/lang/StringBuilder
238: dup
239: invokespecial #13 // Method java/lang/StringBuilder."<init>":()V
242: ldc #14 // String Invalid day of the week:
244: invokevirtual #15 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
247: aload_0
248: invokevirtual #15 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
251: invokevirtual #16 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
254: invokespecial #17 // Method java/lang/IllegalArgumentException."<init>":(Ljava/lang/String;)V
257: athrow

The implementation is straightforward: lookuptable + String.equals(Object) + tableswitch.

Type Inference for Generic Instance Creation

Before Java 7, generic classes had to include type parameters upon instantiation, or the compiler would issue an unchecked conversion warning. In Java 7 and earlier:

1
ArrayList<String> list = new ArrayList<String>();

Starting from Java 7, the type parameter can be omitted:

1
ArrayList<String> list = new ArrayList<>();

Java 7 gave the <> symbol a catchy name – Diamond.

Multiple Exception Handling

Java 7 introduced another syntax simplification: multi-catch. Before Java 7, each catch block could only catch one exception type. APIs like java.lang.reflect.** throw a series of exceptions. With this feature, a single catch block can handle them all:

1
2
3
4
5
6
7
try {
Class<?> clazz = Class.forName("com.example.Main");
Method method = clazz.getMethod("main", String[].class);
method.invoke(clazz, new String[] {});
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}

Beyond handling multiple exceptions, Java 7 also added more inclusive type checking for re-thrown exceptions. Before Java 7:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static class FirstException extends Exception { }
static class SecondException extends Exception { }

public void rethrowException(String exceptionName) throws Exception {
try {
if (exceptionName.equals("First")) {
throw new FirstException();
} else {
throw new SecondException();
}
} catch (Exception e) {
throw e;
}
}

In Java 7, you can specify the thrown exception types as FirstException and SecondException, even though the catch block catches Exception. The compiler can determine which specific exception is being re-thrown:

1
2
3
4
5
6
7
public void rethrowException(String exceptionName) throws FirstException, SecondException {
try {
// ...
} catch (Exception e) {
throw e;
}
}

Support for Dynamic Language

Java is a statically-typed language – once a program is compiled, all type information is fixed. Static typing has obvious advantages for execution speed, but before Java 7, supporting dynamic languages like JavaScript and Ruby running efficiently on the JVM was difficult. Java 7 introduced the invokedynamic instruction, which links invokedynamic call sites to methods through a bootstrap method. Consider this example:

1
2
3
def addtwo(a, b)
a + b;
end

+ is the invokedynamic call site. When the compiler emits an invokedynamic instruction to call +, the runtime system knows there is an adder(Integer, Integer) method and links the invokedynamic call site to it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class IntegerOps {
public static Integer adder(Integer x, Integer y) {
return x + y;
}
}

class Example {
public static CallSite mybsm(MethodHandles.Lookup callerClass, String dynMethodName, MethodType dynMethodType) throws Throwable {
MethodHandle mh = callerClass.findStatic(Example.class, "IntegerOps.adder", MethodType.methodType(Integer.class, Integer.class, Integer.class));

if (!dynMethodType.equals(mh.type())) {
mh = mh.asType(dynMethodType);
}

return new ConstantCallSite(mh);
}
}

In the example above, IntegerOps is a library that ships with the dynamic language runtime.

Try with Resources

try-with-resources is an extremely practical feature, especially for I/O operations. Before Java 7, you had to close streams manually:

1
2
3
4
5
6
7
8
9
10
static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
if (br != null) {
br.close();
}
}
}

In Java 7, this becomes much cleaner:

1
2
3
4
5
static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}

The only limitation is that try does not support expressions or variables.

New I/O

The New I/O API introduced in Java 7, also known as NIO.2 or AIO (Asynchronous I/O), includes the following updates:

  • File System API
    • java.nio.file.*
    • java.nio.file.attribute.*
  • Channels API
    • Socket Channel API
      • Multicast
    • Asynchronous I/O
      • Future style API
      • Callback style API
  • Miscellaneous
    • Infinibind (IB) Sockets Direct Protocol (SDP)
    • Stream Control Transport Protocol (SCTP)

For a deeper dive, see: Java Tutorials: Java NIO.2

Binary Literals

Binary Literals is a practical feature. Before Java 7, representing binary values required hexadecimal notation. In Java 7, you can directly use binary (prefixed with 0b or 0B):

1
2
3
4
5
6
7
8
9
10
11
12
13
// An 8-bit 'byte' value:
byte aByte = (byte)0b00100001;

// A 16-bit 'short' value:
short aShort = (short)0b1010000101000101;

// Some 32-bit 'int' values:
int anInt1 = 0b10100001010001011010000101000101;
int anInt2 = 0b101;
int anInt3 = 0B101; // The B can be upper or lower case.

// A 64-bit 'long' value. Note the "L" suffix:
long aLong = 0b1010000101000101101000010100010110100001010001011010000101000101L;

Underscore in Number Literals

This feature may seem somewhat trivial – it’s probably meant to improve the readability of long numbers:

1
2
3
4
5
6
7
8
long creditCardNumber = 1234_5678_9012_3456L;
long socialSecurityNumber = 999_99_9999L;
float pi = 3.14_15F;
long hexBytes = 0xFF_EC_DE_5E;
long hexWords = 0xCAFE_BABE;
long maxLong = 0x7fff_ffff_ffff_ffffL;
byte nybbles = 0b0010_0101;
long bytes = 0b11010010_01101001_10010100_10010010;

Note that underscores can only appear between digits, not at the beginning or end of a number, nor adjacent to a decimal point:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
float pi1 = 3_.1415F;      // Invalid; cannot put underscores adjacent to a decimal point
float pi2 = 3._1415F; // Invalid; cannot put underscores adjacent to a decimal point
long socialSecurityNumber1
= 999_99_9999_L; // Invalid; cannot put underscores prior to an L suffix

int x1 = _52; // This is an identifier, not a numeric literal
int x2 = 5_2; // OK (decimal literal)
int x3 = 52_; // Invalid; cannot put underscores at the end of a literal
int x4 = 5_______2; // OK (decimal literal)

int x5 = 0_x52; // Invalid; cannot put underscores in the 0x radix prefix
int x6 = 0x_52; // Invalid; cannot put underscores at the beginning of a number
int x7 = 0x5_2; // OK (hexadecimal literal)
int x8 = 0x52_; // Invalid; cannot put underscores at the end of a number

int x9 = 0_52; // OK (octal literal)
int x10 = 05_2; // OK (octal literal)
int x11 = 052_; // Invalid; cannot put underscores at the end of a number

Improved Compiler Warnings and Errors

Java 7 improvements to compiler warnings and errors cover the following areas:

  • Heap Pollution
  • Variable Arguments Methods and Non-Reifiable Formal Parameters
  • Potential Vulnerabilities of Varargs Methods with Non-Reifiable Formal Parameters
  • Suppressing Warnings from Varargs Methods with Non-Reifiable Formal Parameters

Fork/Join Framework

The fork/join framework introduced in Java 7 is an implementation of the ExecutorService interface that takes full advantage of multi-core processors. It is designed for tasks that can be recursively split into smaller pieces, leveraging all available processing power to improve application performance. The core of the fork/join framework is ForkJoinPool, which extends AbstractExecutorService and implements the work-stealing algorithm. Here is an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class ForkBlur extends RecursiveAction {
protected static int sThreshold = 100000;

private int[] mSource;
private int mStart;
private int mLength;
private int[] mDestination;

// Processing window size; should be odd.
private int mBlurWidth = 15;

public ForkBlur(int[] src, int start, int length, int[] dst) {
mSource = src;
mStart = start;
mLength = length;
mDestination = dst;
}

protected void computeDirectly() {
int sidePixels = (mBlurWidth - 1) / 2;
for (int index = mStart; index < mStart + mLength; index++) {
// Calculate average.
float rt = 0, gt = 0, bt = 0;
for (int mi = -sidePixels; mi <= sidePixels; mi++) {
int mindex = Math.min(Math.max(mi + index, 0), mSource.length - 1);
int pixel = mSource[mindex];
rt += (float)((pixel & 0x00ff0000) >> 16) / mBlurWidth;
gt += (float)((pixel & 0x0000ff00) >> 8) / mBlurWidth;
bt += (float)((pixel & 0x000000ff) >> 0) / mBlurWidth;
}

// Reassemble destination pixel.
int dpixel = (0xff000000) | (((int)rt) << 16) | (((int)gt) << 8) | ((int)bt);
mDestination[index] = dpixel;
}
}

protected void compute() {
if (mLength < sThreshold) {
computeDirectly();
return;
}

int split = mLength / 2;
invokeAll(new ForkBlur(mSource, mStart, split, mDestination),
new ForkBlur(mSource, mStart + split, mLength - split, mDestination));
}
}

public class Example {
public static void main(String[] args) {
ForkBlur fb = new ForkBlur(src, 0, src.length, dst);
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(fb);
}
}

Garbage-First Collector (G1)

The G1 garbage collector was not fully supported until Oracle JDK 7 update 4. G1 is primarily designed for garbage collection on multi-core servers with large heap memory. G1 works by dividing the heap into a series of equal-sized contiguous regions, then performing a concurrent global marking phase to determine object liveness across the entire heap. Once marking is complete, G1 knows which regions are mostly empty and collects those first, freeing up significant space – hence the name. G1 focuses its collection and compaction activity on heap regions likely to be full of reclaimable objects. It uses a pause prediction model to meet user-defined pause time targets and selects the number of regions to collect based on the specified pause time goal.

PermGen Removal

Starting from Java 7, some data previously residing in the permanent generation was moved to the Java heap or native heap:

  • The symbol table was moved to native heap
  • Interned Strings were moved to the Java heap
  • Static members of classes were moved to the Java heap

For more details, see: https://www.oracle.com/java/technologies/javase/jdk7-relnotes.html