What's New in Java 5
J2SE 5.0, codenamed Tiger, was released on September 30, 2004. Originally numbered 1.5 following the internal versioning scheme, the number was changed to “better reflect the maturity, stability, scalability, and security level of J2SE.” This release introduced several significant new language features, developed under JSR 176.
Generic
Using generics in code brings many benefits:
- Strong type checking at compile time
- Elimination of type casts
Without generics:With generics:1
2
3List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);1
2
3List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); - Implementation of generic algorithms, reducing code redundancy
Enhanced for Loop
Before Java 5.0, iterating over arrays required a traditional for loop:
1 | for (int i = 0; i < array.length; i++) { |
or
1 | for (int i = 0; i < list.size(); i++) { |
or
1 | for (Iterator<Object> it = list.iterator(); it.hasNext();) { |
Starting from Java 5.0, both arrays and collections can be iterated using a unified for-each loop:
1 | for (item : arrayOrList) { |
The benefit of for-each is that without JIT enabled, for-each offers noticeable performance improvements over a traditional for loop. With JIT enabled, the difference is negligible. But as Java developers, we use Java to eliminate platform differences. Unless there are special performance requirements, we should prefer for-each. So how does for-each unify the iteration of arrays and collections?
1 | public static void main(String[] args) { |
The compiler generates the following bytecode:
1 | 0: aload_0 |
Decompiling the generated bytecode gives us:
1 | public static void main(String[] var0) { |
As we can see, the compiler optimized the for loop by extracting the array length access into a variable outside the loop body. So what about iterating collections with for-each? Take this code as an example:
1 | public static void main(String[] args) { |
The compiler generates the following bytecode:
1 | 0: aload_0 |
Decompiled:
1 | public static void main(String[] var0) { |
So for-each uses Iterator when iterating collections, and an optimized traditional for loop when iterating arrays.
Autoboxing / Unboxing
Autoboxing & Unboxing automatically performs implicit conversions between primitive types and their corresponding wrapper classes, eliminating redundant code. For example:
1 | Integer a = 100; |
The compiler automatically generates the following bytecode:
1 | bipush 100 |
From the bytecode, we can see that autoboxing is essentially the compiler converting 100 to Integer.valueOf(100). Unboxing automatically calls Integer.intValue(). For example:
1 | int a = new Integer(100); |
The compiler generates:
1 | new #2 // class java/lang/Integer |
Typesafe Enum
Before Java 5.0, there was no real enum class. Enumerations were typically represented using int values, which brought several problems:
- Not type-safe – any
intvalue could be used as a parameter, and the compiler had no way to validate it - No namespace – only variable prefixes could distinguish enums, easily leading to naming conflicts
- Fragile references – since
intenums are typically constants inlined by the compiler, changing enum values or inserting new ones would require recompilation of all code using them - Log-unfriendly –
intvalues printed in logs convey no meaning
Starting from Java 5.0, you can define typesafe enums with the enum keyword:
1 | public enum Color { |
Long ago, the official Android Performance Tips had a section called Avoid Enums Where You Only Need Ints, recommending against using Enum because it consumed more memory. This section was later removed. The reason was tied to Android‘s Runtime – early Android used Dalvik, which was weak in memory allocation, hence the recommendation. Starting from Android 5.0, which uses ART, the memory overhead of Enum became negligible.
Varargs
Varargs must be the last parameter of a method:
1 | public void printf(String format, Object... args) { |
What’s the difference between Object... and Object[]? Let’s look at the bytecode:
1 | public void printf(java.lang.String, java.lang.Object...); |
It turns out args is actually Object[] – Object... is merely syntactic sugar. What happens if we declare another method with the same name but Object[] as the last parameter?
1 | class VarArgs { |
As expected, the compiler throws an error:
1 | VarArgs.java:5: error: cannot declare both printf(String,Object[]) and printf(String,Object...) in VarArgs |
Static Import
Before Java 5.0, accessing static members of a class required qualifying them with the class name:
1 | double r = Math.cos(Math.PI * theta); |
A common workaround was defining static members in an interface and inheriting from it, but this was never a good idea. A class’s use of another class’s static members is an implementation detail. When a class implements an interface, the interface’s members become part of that class’s public API – implementation details should not leak into the public API. To properly solve this, Java 5.0 introduced static imports:
1 | import static java.lang.Math.PI; |
Annotation
Many APIs require substantial boilerplate code. For example, writing a JAX-RPC web service requires both an interface and its implementation class, leading to redundant boilerplate. Before Java 5.0, Java only provided limited ad-hoc annotations such as @deprecated. Starting from 1.5, Java added the ability to define custom annotations and provided APT for compile-time annotation processing.
How did Java support Annotation without changing the class file structure? It comes down to the ClassFile structure:
1 | ClassFile { |
The ClassFile structure ends with an attribute_info array. See the JVM specification. Annotation exists in the class file as a form of attribute_info. According to the JVM specification, Java 5.0 supports the following forms:
RuntimeVisibleAnnotations- annotations visible at runtimeRuntimeInvisibleAnnotations- annotations invisible at runtimeRuntimeVisibleParameterAnnotations- parameter annotations visible at runtimeRuntimeInvisibleParameterAnnotations- parameter annotations invisible at runtimeAnnotationDefault- default values for annotation methods
Whether an annotation is visible at runtime depends on its @RetentionPolicy. From the Java source code, @RetentionPolicy has three values:
SOURCE- retain the annotation in source code onlyCLASS- retain the annotation in the class fileRUNTIME- retain the annotation at runtime
The mapping between RetentionPolicy and annotation visibility:
RetentionPolicy |
Visibility |
|---|---|
SOURCE |
RuntimeInvisible |
CLASS |
RuntimeInvisible |
RUNTIME |
RuntimeVisible |
So if you want to access a custom Annotation at runtime, you must declare its RetentionPolicy as RetentionPolicy.RUNTIME. Otherwise, it will not be accessible at runtime.
For more details, see: https://docs.oracle.com/javase/1.5.0/docs/guide/language/
- Blog Link: https://johnsonlee.io/2021/05/07/java-5-new-features.en/
- Copyright Declaration: 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
