Abstract: Surprisingly, the compound arithmetic expression contains a cast that can produce some interesting side effects. In this newsletter we explore this and other edge cases in the Java Language Specification.
Welcome to the 333rd edition of The Java(tm) Specialists' Newsletter, which is a rerun from Issue 245. I am back in my childhood bedroom typing this, visiting my mother in Cape Town, in a lovely suburb at the base of Lion's Head called Bantry Bay. Since my focus this week and next is spending time with family, I decided to send you a rerun that I hope you will enjoy. We will look again at a very strange effect with += that has caught me out on more than one occasion. It is nice here in Bantry Bay, and every day I run past the Ellerman House - named after Lord and Lady Ellerman who lived there. My mom used to do physio for Lady Ellerman and she took a liking to my two brothers and I. We once went for tea, and had a huge blast running around her enormous house that sported a lift and, if I recall correctly, a tiger and polar bear skin on the floor. For Christmas she sent us three ginormous chocolate Father Christmas. She unfortunately never had children and loved the energy of three crazy young Kabutzes.
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
'Twas the year 1999. Martin Fowler was coming to town. Cape Town, that is. I had no idea who he was, only that some Java event was happening. In my city! I had to be there! So I arrived, all decked out, wearing my gleaming "Sun Certified Java Programmer" badge. Perhaps I'd meet rich companies looking for Java programmers?
Martin Fowler spoke about something called "refactoring". I looked it up in the dictionary and came back empty. Microsoft Word marked it as a spelling error. But it sounded interesting, so I decided to take a look. I sat in front. In those days, my eyesight was a lot better than it is now. I do have glasses, which I bought especially for Java conferences. But I don't like wearing them. So my seat is usually in front. Call me a keen bean.
First impressions were just "wow". I had never before heard anyone as loud as Martin. He would make a perfect ship's captain, bellowing orders as another hurricane was bearing down on his vessel. What he said, or rather thundered, about "refactoring" resonated with me. Unit tests to check the basic functionality and automatic tools to do the heavy lifting? Refactorings that were scientifically proven to alter the structure of the source code, yet still give the same results? Sounded like magic!
(Whilst searching for the Refactoring book on Amazon [ISBN 0201485672] , they reminded me "You purchased this item on July 8, 1999." My copy is from the first printing, June 1999 - a collector's item by now.)
Two things stood out to me during his talk (besides Martin's built-in megaphone).
First off, his Visual Age IDE crashed badly, taking the entire project with it.
He had to do a restore whilst muttering that this "wasn't very nice".
Secondly, in one of his refactorings he extracted a method.
He "accidentally" changed the return type from double to int.
Amazingly, the code still compiled, because he was using the result in a += calculation.
It went something like this:
// Extract from Refactoring by Martin Fowler [ISBN 0201485672]
double thisAmount = 0;
Rental each = (Rental) rentals.nextElement();
//determine amounts for each line
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) * 1.5;
break;
case Movie.NEW_RELEASE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDRENS:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) * 1.5;
break;
}
The code smells, so he extracted the switch statement manually:
double thisAmount = 0;
Rental each = (Rental) rentals.nextElement();
thisAmount = amountFor(each);
private int amountFor(Rental each) {
int thisAmount = 0;
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) * 1.5;
break;
case Movie.NEW_RELEASE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDRENS:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) * 1.5;
break;
}
return thisAmount;
}
If you look at the code above, the amountFor() method should
have returned a double. But when
Martin Fowler refactored it, he used an
int as a return type.
Two things show us how old this book is. First off, he is talking about a computerised billing system for a "video store". In the days before Netflix, we used to drive to a shop that had hundreds of videos. These were like DVDs, but on magnetic tape. There were no chapters to jump to. The quality was analog and we never knew if the tape had been damaged. We used to pay to rent videos for a night and then had to return them the next day. If we forgot or missed the cut-off time, we'd be billed for an extra day. Some stores even penalized us if we didn't rewind the videos. Those were dark days.
Secondly, when Martin extracted the code, I thought it would
not compile. I expected javac to complain about him
calling thisAmount += 1.5; without an explicit
cast. In my mind, I thought that this was equivalent to
thisAmount = thisAmount + 1.5; However, it is
rather
thisAmount = (int)(thisAmount + 1.5);
That's right, javac adds an explicit cast when we use a
compound operation in Java. It is described in the
Java Language Specification section 15.26.2 "Compound
Assignment Operators":
"A compound assignment expression of the form
E1 op= E2 is equivalent to
E1 = (T)((E1) op (E2)), where T is
the type of E1, except that E1 is
evaluated only once."
Last week, Dr Fatma C. Serce sent me an email outlining three cases that her student Chao Tan had shown her. It reminded me of this little factoid. Here are their cases:
public class CastSurprise {
public static void main(String... args) {
// case 1:
char c1 = 'a' + 5; // compiles
// case 2:
char c2 = 'a';
c2 = c2 + 5; // does not compile
// case 3:
char c3 = 'a';
c3 += 5; // compiles
}
}
In case 1, javac could determine at static compile time that the value would not lose precision. If instead of 5 we add 500_000, then javac would complain, because we would overflow the 16 bit char type.
In case 2, since we are increasing c2 without an explicit cast, javac cannot guarantee that the result would fit inside 16 bits. It thus forces us to add a cast.
In case 3, the cast to char is added
by the compiler. As we saw above, the statement c3 += 5; is equivalent to
c3 = (char)(c3 + 5);
Onto something else, mildly related. To test how well you understand Java, here are three code snippets. Your job is to guess the output without running them. In C it wouldn't necessarily be well-defined. In Java you can find the definition in JLS 15.7.1. However, please try figure it out before running them:
public class Test1 { // JLS 15.7.1
public static void main(String... args) {
int i = 2;
int j = (i = 3) * i;
System.out.println(j);
}
}
And now for the second one:
public class Test2 {// JLS 15.7.1
public static void main(String... args) {
int a = 9;
a += (a = 3); // first example
System.out.println(a);
int b = 9;
b = b + (b = 3); // second example
System.out.println(b);
}
}
And our last one:
public class Test3 {// JLS 15.7.1
public static void main(String... args) {
int j = 1;
try {
int i = forgetIt() / (j = 2);
} catch (Exception e) {
System.out.println("Now j = " + j);
}
}
static int forgetIt() throws Exception {
throw new Exception("I'm outta here!");
}
}
I hope you enjoyed these and that you'll try them out.
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.