Abstract: We look at the new Java 5 features: Generics, autoboxing and the for-in statement. It should make our code more maintainable without sacrificing performance.
Welcome to the 90th edition of The Java(tm) Specialists' Newsletter. I would like to thank all my loyal readers for spreading the word about this newsletter. Today is a special day for my son Maxi, as he celebrates his 6th birthday. Kids are a great way of reminding you how old you are getting... Before I know it, he'll be asking me to buy him a car...
The Psychology of Computer Programming [ISBN 0932633420] : A great book by Gerald Weinberg (also wrote Secrets of Consulting, reviewed in TJSN 048) This is the type of book that you can pick up, read three pages, have a few laughs, and then put it down again.
In his book, Weinberg assumes that programmers are of above-average intelligence. The first edition of the book was written in the year I was born (1971). At the time of writing, before the invention of contact lenses, most programmers really did look like archetypal nerds. Popular thought was that you had to be good at Mathematics to become a programmer. Since it takes a bit of intelligence to be good at Maths (and a lot of hard work), the less-brainy kids stayed away from computer programming. My, how the industry has changed!
Today, I cringe when people ask me what I do for a living. My favourite answer is: "I stand in front of crowds and tell jokes." That sounds far more interesting than: "I go to corporations and teach their programmers how to be more effective in Java and Object Orientation." As soon as I mention that my job has anything to do with computers, the reply I get every time is: "Oh yes, my uncle/brother/sister/father/son/mother/auntie is an absolute genius with computers - he even did an MCS-whats-the-name."
This is a classic book that every programmer and project manager should read. You can probably pick up a dusty copy of the original 1971 print in your computer science department library.
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
From the old (1971) to the new (2004).
On my last Java Course in Pretoria (South Africa), I demonstrated to my class how autoboxing worked. Our experiments showed that autoboxing can be inefficient. As nice as the feature is, it is also rather dangerous. Java learners could use autoboxing by mistake, negatively affecting performance.
However, before I delve into autoboxing, I would like to
write something about the new for
construct.
About two-and-a-half years ago, I complained bitterly in
TJSN 040 about the Iterator
idiom. I felt that using a
for
or a while
loop, together with a typecast, tended to make our code look
ugly. The new for
construct in JDK
5 finally addresses my complaint. This is how you can use
it in your code:
import java.util.*; public class NewFor { public static void main(String[] args) { // we can use type-safe collections... Collection<String> names = new ArrayList<String>(); names.add("Maxi"); names.add("Connie"); names.add("Helene"); names.add("Heinz"); //names.add(new Integer(42)); -- does not compile! // look at the new for construct: for (String name : names) { System.out.println("name = " + name); } } }
Endorsement: After having spent several years using different IDEs and editors (JBuilder, VI, Notepad, Eclipse), I have finally come to rest. Last year I took IntelliJ IDEA for a spin, and have not looked back since. It just always does exactly what I expect it to do. I can do almost anything with keystrokes, instead of moving my hands off the keyboard onto my mouse. In addition, it supports all of the JDK 5 features already, even though that version of the JDK is only in beta. Do yourself a favour, take the latest early access version for a test run: IntelliJ EAP. [No, I am not being paid for endorsing IntelliJ, and I understand completely if you prefer Eclipse.]
We can combine autoboxing with generics. (Autoboxing is the process of converting primitives to objects and vice-versa, automatically). My example code in newsletter # 40 could have been written more elegantly like so:
public void showAging(Collection<Integer> ages) { for(int age : ages) { System.out.println("Now you're " + age + ", in 3 years time, you'll be " + (age + 3)); } }
I discovered by accident that you can also use this new
for
construct with arrays:
public class NewForArrays { public static void main(String[] args) { String[] names = {"Maxi", "Connie", "Helene", "Heinz"}; for (String name : names) { System.out.println("name = " + name); } } }
Isn't that beautiful? At long last, a consistent way of iterating through collections and arrays. This even works for arrays of primitives:
public class NewForPrimitiveArrays { public static void main(String[] args) { int[] daysPerMonth = {31,28,31,30,31,30,31,31,30,31,30,31}; int totalDays = 0; for (int days : daysPerMonth) { totalDays += days; } System.out.println("totalDays = " + totalDays); } }
When we decompile the class, we see the following (not too unreasonable):
public class NewForPrimitiveArrays { public static void main(String args[]) { int daysPerMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; int totalDays = 0; int arr$[] = daysPerMonth; int len$ = arr$.length; for(int i$ = 0; i$ < len$; i$++) { int days = arr$[i$]; totalDays += days; } System.out.println("totalDays = " + totalDays); } }
A great fear of programmers is that you might use some new
construct that then makes your code unacceptably slow. Here is
some code that compares the performance of "old" and "new"
for
constructs:
import java.util.*; public class NewForPerformance { public static void main(String[] args) { // let's look at the performance difference of for construct Collection<Integer> numbers = new ArrayList<Integer>(10000); for(int i=0;i<10000; i++) { // I can add an "int" to a collection of Integer, thanks // to the autoboxing construct shamelessly copied from C# numbers.add((int)Math.random()); } oldFor(numbers); newFor(numbers); } private static void oldFor(final Collection<Integer> numbers) { measureIterations("oldFor", new Runnable() { public void run() { for(Iterator<Integer> it = numbers.iterator(); it.hasNext();) { Integer i = it.next(); } } }); } private static void newFor(final Collection<Integer> numbers) { measureIterations("newFor", new Runnable() { public void run() { for(Integer i : numbers); } }); } private static void measureIterations(String method, Runnable r) { long start = System.currentTimeMillis(); int iterations = 0; while(System.currentTimeMillis() - start <= 2000) { r.run(); iterations++; } System.out.println(method + ": " + iterations + " in " + (System.currentTimeMillis()-start) + "ms"); } }
When I run this, I get the following output:
oldFor: 3532 in 2003ms newFor: 3561 in 2003ms
The two methods are similar enough that we can declare that
there is no difference between them. So, would you not
rather use the new
for
construct instead of struggling
with the old iterators?
Let us presume that armies of Java programmers will jump at
the opportunity of using Generics and the new
for
construct. This will make
arrays redundant, since autoboxing allows us to use
int
s with collections (note that
we are adding and getting the values from the collection as
the primitive data type int
, but the
type of object in the collection is Integer):
import java.util.*; public class AutoBoxing { public static void main(String[] args) { Collection<Integer> values = new ArrayList<Integer>(); for (int i=0; i<100; i++) { values.add(i); } for(int val : values) { System.out.println(val); } } }
Let's see what happens when we have a collection of numbers, and we want to increment all the values:
import java.util.*; public class AutoBoxingIncrement { public static void main(String[] args) { // we set up a Collection containing Integers and an int[] List<Integer> values = new ArrayList<Integer>(); int[] valuesArray = new int[1000]; for (int i = 0; i < 1000; i++) { values.add(i); valuesArray[i] = i; } // let's time how quickly we can increment the 1000 values long time = System.currentTimeMillis(); // we must do it a few times to see the difference for (int j = 0; j < 100000; j++) { for (int i = 0; i < values.size(); i++) { values.set(i, values.get(i) + 1); } } System.out.println("autoboxing with generics took " + (System.currentTimeMillis() - time) + "ms"); // now we try with an array time = System.currentTimeMillis(); for (int j = 0; j < 100000; j++) { for (int i = 0; i < valuesArray.length; i++) { valuesArray[i]++; } } System.out.println("Using a plain array took " + (System.currentTimeMillis() - time) + "ms"); } }
When I run this on my little notebook, I see a huge difference in performance. The direct array method of using ints is about 20 times faster!!!
autoboxing with generics took 9954ms Using a plain array took 551ms
Generics were extremely easy to learn, and after using them for a few hours, I did not want to go back to type-unsafe Collections. However, we have to be aware when we may be doing stupid things that could impact performance, such as autoboxing when we should not.
Kind regards
Heinz
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.