Advanced Topics in Programming Languages: Java Puzzlers,...

Uploaded by GoogleTechTalks on 08.10.2007

>> ...or counterintuitive behavior and it's your job as the audience to guess what the
program does. So, we'll poll you after showing the program. You will tell us what it does
by show of hands, multiple choice, typically four choices, and then we reveal the mystery.
But it's not all fun and games. With each of these puzzle is a moral. And the moral
tells you how to avoid whatever trap or pitfall was displayed in that particular program.
Typically, in these puzzles' presentations, we discuss only the language and the core
libraries of mainly Java.language and Jave.util. >> But today...
>> But since I'm the special guest, we're actually going to be throwing a few new things
into the mix and you'll see them as we get to them.
>> Excellent. Why don't you--why don't you give me the first one?
>> Okay. So, I've got a puzzle for you, "The Joy of Sets." So, we're going to take a set
and add things to it and remove things from it. And at the end we ask, "How big is the
set?" >> Okay.
>> And the question is, "What does this program print?"
>> Well, this doesn't look too hard, at least it's short. In fact, it's a set of shorts.
And let's see. We initialized it to a new hashed set of shorts, that's kind of the basic
set implementation. And this is the sort of the idiomatically correct way to do it, where
the type on the left-hand side is set. On the right-hand side the implementation type.
We iterate. For i it starts at zero and goes all the way up to 100, actually 99 because
it's i less than 100. And then we add that element to the sets. So first we add zero,
and we remove i minus one. So I guess that's like the previous element. In the first case,
we're going to try to remove -1. Now the set doesn't contain -1. So that's a no op. Second
time through the loop, we add one and remove zero, leaving one in the loop. Next time,
we add two and remove one leaving two. So, each time through loop, we only have the most
recently added element. >> Mom always said to clean up after ourselves.
>> Yes, that appears to be what we are doing here. So after the 99th iteration, the loop--the
set contains 99 and no other elements. What we're printing is the size of the set which
is one. So I would have to say this program prints one. Well let's see if that's one of
the options. Yes indeed, it is. And the question is, "Does this program print 1, 100? Does
it throw an exception or none of the above? And that could mean, you know, that it varies
from run to run. But there's one thing it could not mean.
>> Yes, that's right. So sometimes in the past, we've had Java puzzlers that don't compile.
>> And we have found that the audience gets very, very angry when this happens. So this
year, we make you a pledge, space cadet's honor...
>> Space cadet's honor, all puzzles... >> All of our programs compile.
>> ...compile. >> All right. So...
>> Yes. >> So, now it's time for you guys to decide
does this program print 1, 100, throw an exception or do something else? You're all required
to look. This is not something where you're allowed to simply sit on your hands, right?
It's okay to be wrong, many people are. Okay. We think we're ready. We're ready. All right.
So, how many people think the answer is A, that this program prints 1? Okay. How many
people think that this program prints 100, B? Okay. How many people think that it throws
an exception? Okay. And how many people think that the answer is D, none of the above? Okay.
I'd say that was about evenly split between A, B and C, roughly.
>> Yeah, I would say so. >> Well, let--let's find out what it really
does. Shall we? >> Okay. So what does it actually do? It prints
100. >> One hundred?
>> Yes. >> Why does it do that? It removes all those
elements. >> Ah, the problem is, is that it adds shorts
to the set but it removes integers. >> Whose integers? Let me take another look
at this. >> Okay. So the problem here is that i minus
1 is an int valued expression. >> Wait a minute, i is a short and 1 is little
tiny number. But why is it? >> Yes. But whenever you perform any sort
of arithmetic operation on any int--anything that could be an int and the result is either
an int or possibly a long, if you're working with long. But any sort of combination involving
short spites or anything, any sort of integer operation with that produces something of
type int. And so, this int computation is going to get auto boxed into an integer object.
And the short that contains the value 1 and the integer containing the value 1 are actually
distinct objects that do not compare as equal. And thus, you have a set that contains the
short 1 and remove the integer 1, it is a no op.
>> Well, that's all well and good, Bill. But wasn't set parameterized? That is generified
in one five? >> Yes, it was--it was generified in one five
and you can see we defined this as a set of short.
>> But shouldn't the compiler complain when you try to remove an integer from a set of
shorts? >> You might think so. And you would be wrong,
right? >> Damn.
>> So it turns out that for the set to try to add something to a set of short, the compiler
enforces the restriction that you can only add a short. However the removed method for
a set of E allows you to remove anything from it. You can remove a string buffer, you can
remove a string, you can remove anything you want.
>> But isn't it a bug if you try to remove the wrong kind of thing from a set?
>> Well, it's type safe. >> Okay.
>> And to some extent that was one of the things that designed how things got generified.
It turns out that there are some cases in which you might want to iterate through a
collection of objects and remove everything in that collection of object from the collection
of shorts. And since there--and in order to maximize backwards compatibility it was decided
that there are a number of methods such as the remove, also, very important, the get
method on a map where the type of the parameter is object. If you have a map from strings
to strings, and pass it a string buffer with get, that will compile and will overturn no,
because there are no string buffers in a map from strings to strings.
>> In fact, by the way, when we were generifying the collections libraries, we first attempted
it to enforce that restriction and we found that it simply didn't work. There were many
reasonable programs that just could not be generified if you were only allowed to remove
a T from a collection of Ts. For example, suppose you want to intersect a list of numbers
with a list of longs, that's perfectly reasonable, you know, you're kind of going to take out
all the longs or, you know, get all the longs from the list of numbers, it should compile
and it does. Great. So what can we--how do we fix this one?
>> Well, one way you can do it is you can cast these results--result back to a short,
a primitive short, and then this will get auto boxed into a short object. And therefore
the short object--the short one will find the matching short one and will actually remove
the object that you'd added to the set in the previous iteration.
>> And so then the program would print one? >> Then it will print one.
>> And my answer will be correct? >> Yes, it would.
>> Great. >> Once the programmed is fixed, yes.
>> And what can we learn from this one? >> All right. So first of, that there a number
of methods in the collection classes that even when you supply generic types, they take
objects as parameters and you have to watch for these. So the removed method takes object
map.get and so forth. You just have to be careful about that. Integral Arithmetic, any
oper--math operation involving any sort of integer value results in always in inter-long,
never a bytes, a short or a character. >> And what that means, by the way, is that
byte shortened char are sort of inferior types. You can have things of that type but once
you operate on them, they always turn into ints.
>> So, avoid mixing types, you know, instant shorts that can bite you in a number of ways.
>> Is that a pun? >> Yes. And avoid short--avoid int, you know,
prefer int long. Basically the only really compelling application for shorts in your
programs is if you want to have a very large array of shorts. You really buy absolutely
nothing by having local variables or fields of type short. It doesn't get you anything
other than confusion. >> And by very large I think Bill means very,
very large because, you know, suppose you got, say a million shorts, is that large?
Well, you know, I guess each short is two bytes, so a million shorts is two megabytes.
But, I don't know about you, I buy my memory in gigabytes these days.
>> Some people run Java on small platforms. >> Oh yeah, that's true. So those people should
worry about it. Okay. So that was a great problem Bill. But now I have one for you.
>> All right. >> Yours was "The Joy of Sets," mine is "More
Joy of Sets." >> Okay.
>> And yours--well, mine is sort of superficially similar to yours. Yours considered sets of
shorts, mine considers a set of URLs. So what we do is we have a list of strings, they are
my favorite URLs and we make a hash set. We put the URLs into it and then we print the
size. So my question is simple, what does this program print?
>> And why are we so preoccupied with size? Okay.
>> Good question. >> So, what does this program do? We've got
an array that's going to be initialized to contain six different strings, Java puzzlers,
apache2-snort.skybar? >> Yes, snort. It's the best.
>> Ahh, okay. Yeah, Josh has some interesting favorites. Google Java puzzlers, now that's
a repeat of this one. The Findbugs website and the university mailing website. Okay.
So we've got these--again, the main and we create a set of URLs. And in the size with
the hash set, then we go through all of these URL names which are strings. And so for each
string, we create a URL object and we add that to the set and that at the end, we print
out what the size of the favorites are. Okay. So, there are six strings but there are only
five unique ones. And so, I'm going to guess that this program create five URLs and when
you put things into a hash set, duplicates find themselves. So I'm going to guess that
the answer is five. Fine, that's a reasonable guess. Let's see if it's one of your choices.
Yes, your choices are four, five and six, and of course, the ever popular none of the
above, which as I remind you, could mean, you know, a different number, could mean throws
an exception, could mean varies from run to run, it could mean all sorts of things. So,
audience, how many of you think this program prints? Choice A, four. Can you give me a
little bit more of a moment to think about it? You guys ready? That's not a hard problem.
All right. Nobody for four. How many for choice B, five? Wasn't that your choice?
>> Yes, that's my choice. >> Got about five of you. How many people
for choice C, six? Ahh, the great bulk of you. And finally choice D, the ever popular,
none of the above. Well, a smattering. But this is a clear win for choice C, six. And
let us--let us see what this program actually does, shall we? Well, as a practical matter,
it does print four. If you run this on four--I'm sorry, if you run this it will print four,
if you are connected to the Net and you Googlers are always connected to the Net. So, it will
print four. If you look in the specification, you'll find that the answer is it varies from
run to run. And as for the intuition... >> How is that possible?
>> URLs equals and hash code methods are completely screwed up. It's as simple as that. Let's
take a closer look. Here's the deal, these two URLs maybe different, double
and the apache-snort.skybar,, but they resolve to the same IP address. And
so, from the perspective of the marvelous and magnificent URL class, they are equal.
If you look at the specification, you will find out that two URL objects are equal if
they reference equal one hosts. And furthermore, two hosts are considered equivalent if both
host names can be resolved to the same IP address. And the other kind of interesting
factoid here is that since host comparison requires name resolution, checking if two
URLs are equal can block, is that not delightful? But, I mean, so apache2-snort and Java puzzlers,
if you actually go there in the web browser, you see entirely different websites. Yeah,
they call it virtual hosting and it turns out that the equals method for URL is incompatible
with virtual hosting because, you know, when URL was added to the platform which was like
1993, there wasn't a lot of virtual hosting going on. Those were kind of the early days
of the Web. So, how do we fix this one? Well, it's really easy. URL is broken, so don't
use it. Use URI instead which is a newer class and much better designed. It turns out that
the equals method for URI is kind of structural. It's just based on the text here. It does
a little bit of canonicalization. So for example, it strips out extra spaces and so forth. I
think it might even do capitalization, does it? I'm not sure. But anyway it is well specified
and it does the right thing. So, if you run the same program using URIs instead of URLs
it will print out five because of the fact that Java puzzlers is on the list twice. So
it is equal to itself but the others are all distinct even though two happen to resolve
to the same IP address. Notice, by the way, that instead of calling it constructor we
have a static factory here because it turns out that static factories are just generally
better things than constructors and since this is a later class they added a nice static
factory to it, so use it. And what can we learn from this one? Well, the main thing
that we can learn is because URLs are broken you should not use them as set elements or
map keys. In fact, you pretty much shouldn't use them much at all. It turns out that they're
equals and hash code methods simply aren't well-defined and they disobey the general
contracts for equals and hash codes. One of the contract stipulations says that the equality
in the hash code of elements should not change over time unless you change the elements.
But in this case whether or not you're connected to the network can change the behavior of
your program. So, if you experience an intermittent web connection failure, but that never happens
to you guys, right? But if you do, the hash code of your object can change. Isn't that
horrible? >> Well, I can also imagine in Google, you
guys might create some large hash maps of URLs. And that could create a serious network
load when you're trying to do some tests or stuff like that.
>> It's true. So just don't use URLs as set elements or map keys. Use URIs instead and
if you need to have a URL because some other API requires it, simply call the method that
allows you to view a URI as a URL. And that the general methods for API designers here
is that your equals method should never depend on the environment. When you're checking if
two objects are equal it should involve quick tests of in memory objects, simple as that.
>> All right. Well, that was a good puzzle. Now, I have one for you, a "Racy Little Number".
>> Ooh. >> So, we're introducing some new things here
which I think have never been seen in a puzzler before, JUnit and concurrency. So, we're basically
going to create a test case in which we create a thread, which is going to check that a number
is equal to two and we're going to set the numbers, start the thread, increment it, join--I'll
let you work it out. >> Okay. Well, let's take a look at it. So,
let me see, we have a single test method. By the way, I don't see any print statements.
So, what I am supposed to tell you here? >> Well, okay, so this is JUnit and JUnit
doesn't depend on print statements. Rather you write methods, test methods that throw
an exception if something goes wrong. >> So, you want to know of the test passes
or fails. >> Right.
>> Well, why didn't you ask me that? >> Right. And so the question is, does this
test case pass or fail? >> Okay. Let's take a look at it then. So
we set number to zero. We create a new thread whose run method invokes the runnable whose
running a method is--just asserts that the number is equal to two. So the question is,
will the thread see the number as two or some other value?
>> Okay. >> If it is two, the test passes and if it's
some other value, it fails. >> Right.
>> So, let's see. We start out with the number one, then we start the thread. Then we increment
the number and finally we wait for the thread to complete.
>> Right. >> Now, the thing is that when you start a
thread it doesn't necessarily start right away. So, although this number is actually
after the start method, I think the number could get incremented to two before the thread
actually runs. So, it's a practical matter, I think this program will always--the number
will be equal two by here. So the assertion is going to succeed and I think that the test
will always pass. >> All right. Let's...
>> Let's see if it's true. Let's see if that's one of the options. >> So, the question is
does this JUnit test always fail, sometimes pass, always pass or always hangs? You know
concurrency. Always got to worry about hanging. All right. So, fail, sometimes pass, sometimes
fail, always pass or it always hangs? Give yourselves a moment. Okay. How many people
think that this test case always fails? Okay. Good number. How many people think that it
sometimes passes and sometimes fails? Okay. How many people think that it always passes?
>> Okay. And how many people think that it always hangs? All right. So that looks like
a clear win for B. >> Yup. So let's see what this program does
in fact do. It always passes... >> I got it right.
>> encounters nothing. Yes, you got it right. Okay. The problem is, is that JUnit
doesn't get to see whether or not the assertion fails.
>> It doesn't see it? >> Let's take another look--let's take a look
at that code. Okay. JUnit reports that a test method fails if the test method throws an
exception, right? And this assert equals methods test the condition and if the condition is
fail, the call to assert equals throws an exception. The problem is, is that this assert
equals isn't occurring in the same thread that invoked the test method. And thus what
will happen is that this exception will simply propagate to the top of the stack and then
get printed on the console. But the JUnit test harness will see that the test method
returns normally and will report a little green dot saying that your test method passed
every single time. >> Well, that's terrible. So, you're saying
that the test method will always succeed whether or not the test succeeds.
>> Whether or not the assertion passes or fails, the test method will always succeed.
>> Okay. >> Now it turns that the assertion will sometimes
pass and sometimes fail. So those of you who had that intuition were halfway there.
>> There was a little--a little clue in the puzzle, it was called a "Racy Little Number"
because it is in fact a race condition. So, how do we fix this travesty?
>> Okay. Well, unfortunately this is not one of those one line fixes. The problem is, is
that getting--what you need to do is if in JUnit you need to start any threads and you
want to report an exception thrown in those threads as a failure, you need to have some
way to get the exception propagated back to the right guy. And so we're just going to
declare fields that will declare an exception or error if they're thrown and then in the
teardown method for the test, we'll check if either of those are non-nil. And basically
all the threads we're going to start, if an exception occurs they'll shove it into those
fields. And then the teardown method will basically check: Was an error or exception
thrown by any of the threads that we started? So this is sort of what we've set up outside
and then each individual test method we need to do something else, next slide please. And
then here we basically need to, in a run method, we need to catch any exceptions that wind
up occurring and store them so that when the main thread, the thread that called the test
method, will also be the one that calls the teardown method. These errors will--these
exceptions will wind up being propagated into that thread, and therefore JUnit will see
it and it will get reported. >> Okay.
>> You have a question? >> Is there a reason not to just use throwable
as opposed to using error and exception right there?
>> The reason is, is that then you would have to--go back to the teardown slide.
>> Yes. The question by the way, for our viewers, is why didn't we use throwable? Why do we
have two fields, one an exception the other an error? And while you're at it, you might
as well answer why they're volatile. >> Okay. So the reason is that this way we
can declare teardown as throwing an exception. In general, I dislike declaring methods that
throw throwable. You know, and they're weird things, like you can declare things that are
throwable that are neither exceptions nor errors and so forth. These are volatile partially
because I'm a little bit over conservative. This is a field that could potentially be
updated simultaneously by multiple threads, right? And thus whenever you have a field
which could be simultaneously touched by multiple threads and at least one of those updates
is a right, make it volatile. In this case, two people competing to store different values,
it probably wouldn't actually impact anything but better be safe.
>> Wise advice. Okay. So now that we know how to fix it, what can we learn from it?
>> Okay. So basically, JUnit does not support test cases in which multiple threads have
to coordinate. If you want to do that and want to get errors reported back from any
of the threads you create you have to roll your own or find somebody else who's already
rolled one. >> And perhaps the scariest part of it is
it gives you a full sense of security. You can write something that looks like it ought
to fail, but if it fails in the wrong thread you'll never see the failure. Please hold
your questions till the end of the talk just because it's kind of a long talk but do remember
them and we will answer them. Okay. So that was a fine puzzle and now I have one for you,
just a moment, here, this one. >> Sartorial effect.
>> Okay. Because this ones about Elvis. It's called "Elvis Lives Again". People who have
been to our presentations before know that we have a thing for Elvis. Because Elvis is
a singleton, there's only one of him. So, in this program...
>> Funny, I think I've seen more than that in Vegas.
>> Well, but do you know they were Elvis? Anyway, so here's the deal, I think we do
live do in a monoelvistic universe and here's how we insure that. We have a public static
final Elvis called "Elvis" and we initialized it to a new Elvis with a private constructor
that's the standard singleton pattern. And then what we do is we keep track of whether
Elvis is living or dead. It turns out that the tabloids care about this, I don't why.
So what our program does is it simply prints out "Hound Dog" if the King is alive and "Heartbreak
Hotel" if he breathes no more. So tell me, Bill, which is Hound Dog or Heartbreak Hotel?
>> All right. So, this is going to get our constructor, this is the main method. Now
we have to see if he lives, and lives returns the value of alive, alive is a variable that
is initialized to living, and living is initialized to true, and now this one is trinary operators,
always have to double look at those when you do those but this evaluates to true. So, Hound
Dog, Elvis lives. >> Hound Dog, okay. Well let's see if--I'm
sure that is one of your options since there's only two sensible options here. We got Hound
Dog, Heartbreak hotel, it varies from run to run, or the ever popular none of the above.
Let's give the audience some moment to think about this one.
>> I hear more muttering on this one than with some of the others.
>> Yeah, yeah, yeah. Now, when the muttering stops, we'll ask. I see Dick smiling, I think
he got it. >> All right. I think we're ready.
>> Ready? Is everybody ready? Time is up the pens are down.
>> Well, oh. Oh well, I spoiled that one, I apologize. Shall we...
>> How many people think the answer is A? >> Okay. So, since I--since I spoiled it,
shall we just go on? Fine. Okay. The correct answer is D, none of the above. It always
throws in NullPointerException and I apologize that I didn't give you an opportunity to vote.
So, as for the intuition, class initialization is a tricky business and auto-unboxing happens
when you least expect it. So let's take another look at this.
>> How did Null get in there? >> How did Null get in there? It's a very
good question. So here's the deal. First of all, the initialization of this program is
less than perfect. When you're looking at these things which involve class initialization,
you have to remember that class initialization proceeds top to bottom and the only way to
figure out what the program does is to basically simulate it, to run it top to bottom. So what
do we do here? First thing we do when we initialize Elvis is we create a new Elvis. Now in order
to create a new Elvis we must initialize the Elvis class, but we're already initializing
it, that's called recursive initialization. And does anyone know what the system does
when you try to do recursive initialization? Shout it out.
>> It ignores it. >> It ignores it, that's exactly right. It's
says, "Oh gee, we're already trying to initialize it, we don't want to go into an infinite regress
so, we'll just barrel through and create the new Elvis which runs the sole constructor..."
>> But that's empty? >> It is empty which means all it does is,
it does the initialization of all of the instance fields for Elvis. So, where are the instance
fields? Well, we've got one. We have alive. And it is set to the value of living. And
what is the value of living? >> True.
>> No. You know it says private static final Boolean LIVING = true. So you might think
it's a constant true and always true. It turns out not to be a compiled time constant and
here is why. It's because it's a capital B Boolean that's an object reference. It turns
out that except for strings, object references are never actually compiled time constants.
So this variable is a final, that means once it is initialized, it cannot change. But it
turns out that in order to initialize it, you actually have to execute this line of
code. And before it is executed, the value of living is not true but no. You see this
little true? It's actually gets auto-boxed into the capital B Boolean true, a wrapped
true. But before this line executes, it is no. So in particular, when we're executing
this line initializing the alive field of the sole Elvis instance, we copy living which
is at that point no into alive. So now alive is no and it's final, it's never going to
get changed. It will stay no forever. Then the constructor returns and we finish initializing
the class. So the next thing we do is we set living to true but it's too late. We've already
used it; we've already copied it into the instance variable so it is of no help to us.
And that's it for the class initialization. Then we run the main method. So the first
thing we do is we execute this trinary operator, Elvis lives?, Hound Dog, Heartbreak Hotel.
Well, what is the value of Elvis? >> The one and only.
>> No. >> No. There is only one no, by the way, so
I guess in a sense it's... >> But the value of Elvis is Elvis but Elvis.lives
is no. >> I'm sorry. Yes, Elvis.lives is no, and
I made this mistake last time as well, Elvis.lives returns no and then we have to turn this no
into a Boolean. >> Little b boolean.
>> Little b boolean. And in order to do that, we have to unbox it and what happens when
you unbox no? >> No pointer exception.
>> No pointer exception. That's called a surprise left jab while auto-unboxing. That's the problem
here. So, how do we fix it? Well, what we do is if we create a single after we've initialized
the field upon which it depends, so all I've done is sort of swap these two lines around
then we fix the problem. We haven't done away with the recursive initialization but it's
okay because by the time we do the recursive initialization, we have already assigned a
value to living and alive takes on that value which is true and so it prints out Hound Dog
just as Bill said it would. But there's a better way to fix it. Why are we using these
nasty capital B Booleans? Let's not, right? If we simply use lower case b booleans, that
is the primitive booleans, then the behavior of the program is much more predictable and,
you know, generally speaking, it'll run faster, if not this program, a program that uses a
lot of them. So, you don't want to use those wrapped values unless you have to. Now, I've
actually kept the order the same of the initialization of the living field and of the Elvis instance
but it turns out it's no longer necessary even if you swap them, it would still do the
right thing because of the fact that living is now a true compiled time constant. This
value happens to be true. And what can we learn from this one? Well, wrapped primitives
aren't primitives. So auto-boxing blurs but does not erase the distinction between primitives
and object references. Capital true is not in a Boolean.true is not the same thing as
true. You should prefer primitives to wrap the values because auto-unboxing can occur
when you least expect it and it can and often does cause no pointer exceptions. So, one
thing you should never do is never use Boolean as a returned--a capital B Boolean, I should
say, as a returned value from a method to allow three possible values, true, false,
or I don't know. That's just a prescription for disaster because if someone then just
does an if statement, that takes the return value from that method, that if statement
will blow up whenever it returns no. And it turns out; by the way, that there is at least
one method in the JDK unfortunately that does this. You know, the problem is the method
predates the introduction of auto-boxing into the language and before auto-boxing, it may
not have been a great idea but it wasn't a terrible idea. Now, it's a terrible idea.
So never use capital B Boolean as a three-valued return. And also watch out for circularities
in class initialization. You can't avoid them. They do occur in a whole bunch of common patterns
like singleton and Type C [INDISTINCT] and so forth. But, when you have one, you should
make sure that you've initialized all the fields that you depend on before you use them.
>> All right. That was a good puzzle with a good moral. Now I have one for you, "Mind
the Gap". So, we're going to write out a file and then we're going to read it back in, skipping
over everything but the first and last byte, and see what the first and last byte were.
And the question is, what does this program print?
>> This is a long program but it doesn't look like a hard program. There's nothing objectory
entered about it, let's just go over it. So the first thing we do is we set gap size to
10x25, that is 10k and I see the private static final int, not integers so this is a true
compiled time constant which means that it's just going to get replaced by the value of
10k wherever it's used. So let's find out where we'll use it. We set temp to create
a file whose name is Gap.txt, and I think it throws some numbers in there.
>> This will--this will create a random sort of name that's not being used.
>> Okay. A random name that's not being used. So get ourselves a file and a good temp sort
of area and then we get a file output stream on that temp, and we write to the file output
stream the byte one, I guess that's an ASCII control A, is that right?
>> Yeah. >> Okay.
>> It's not ASCII this is bytes. >> Well, yeah. I guess--see you're writing
to a file system so it's--I don't know. Anyway, it's the byte one.
>> Yes. >> And then we write a bunch of bytes.
>> Gap size. >> In fact 10 kilobytes of zeroes because
we create an array of length 10K which is initially empty. So we write all these zeroes,
then we write 2, that will a control B, correct? >> Yes.
>> And we close the file. And then we open. Okay. A buffered input stream of a file input
stream on the same name and that's good because I know you don't get buffering for free so
if you want to have buffered I/O which you do, you do this wrapping thing, I guess that's
a decorator pattern, right? >> Yes.
>> And we read the first byte into an int, and then we skip the size of the gap which
is 10K. We read the last byte, presumably this is two, this is one, and finally we print
first plus last. So that's 1+2 which was 3 the last time I checked.
>> Is that your answer? >> It's my final answer.
>> All right. Let's see if that's one of the options. Okay. So, question is, what does
this program print? And now, actually one thing, we're actually dealing with the file
system here, right? And so to avoid any questions, I'm actually going to promise you that there
are no issues with the file system. Nobody else touches the file; the file doesn't go
away, we don't run out of space on the disc, there are no I/O exceptions. Really, the question
is, reading, writing the files works just as you would expect, and the question is what
does this program print? >> But that's right generous of you Bill.
>> Yes. I'm a generous person. >> Okay.
>> So the question is does it print 0, 1, 3 or it varies from run to run? Okay. One
moment. >> Looks like they're ready.
>> I don't hear a lot of muttering. So yes, now keep your hands off that button. All right.
>> You make one mistake and you pay for it all your life.
>> All right. So, how many people think that the answer is A, 0? Smattering. How many people
think that the answer is B, 1? Pretty many. How many people think that the answer is C,
3? How many people think that the answer is D, it varies? I think there were a few people
sitting on their hands but I think maybe D won?
>> Yeah, I think by a hair. >> All right.
>> Let us find out what the program actually does. Now can I press the button?
>> Now you can press the button. >> Okay.
>> Okay. In practice it will print 1. In theory it will vary from run to run.
>> One? Why will it print one? >> The problem is, is that the skip method
returns the number of bytes that it skipped. It may decide on a whim to not skip the full
number of bytes you requested. >> Wait. Let me--let me take a look at this.
Why would it do that? It doesn't make any sense.
>> Well, that's what the API says it doesn't necessarily have to make sense. So the skip
method, as I said, is declared that it can decide to just return any value less than,
you know, between 0 and the number of bytes you requested.
>> And you said it like, always returns one as a practical matter, why it would do such
a thing? >> So it turns out that a buffered input stream,
if there's already some data buffered, it will only skip the amount that's buffered
and won't go down to the underlying stream to try to skip additional bytes.
>> That's ridiculous. >> And so as a result--yes. And as a result
this will always perform a short skip and thus this [INDISTINCT] won't actually be the
last byte in the file but rather a byte in the middle of the file which will be zero,
and thus first will be zero--sorry, first will be one and last shall be zero and thus
we will print one. >> Ouch. So, how do you fix it?
>> All right. Well, it turns out there's not a one line fix. And in fact, a lot of times
when you find yourself having to do something more than a one line fix the best thing to
do is don't try to do it in place but compartmentalize it. Define your own method. And so here, we're
going to define a method called skipFully that will in fact skip all the bytes you request
it to. It will never return a short number of bytes. And if for some reason, it gets
back to the--skips zero byte, it will throw an end of file exception because that is the
one place where skip is documented that if you're at the end of file, it will--skip will
return zero, that's the only way to find out that you're at the end of file when you're
using skip. It won't throw an exception. And so this method will always skip. It'll either
skip the number of bytes you've requested or it will throw end of file exception.
>> Can you show me how it works? >> Yes. Let's take a look at that. And once
you've written this, next slide. >> Well, you can't show me how it works if...
>> I'd switch to the next slide. I want to know how this method works. I don't believe
it works. >> Oh, sorry. How this method works? Sorry.
All right. So, we've taken the number of bytes that we've been requested to skip and then
we're simply going to have a loop. You know, you know, how many bytes left do we need to
skip. And so, I call skip and I've asked, "Skip this many bytes, please, please, please."
And it's going to return some value, right? If we return zero then--although the spec
doesn't say that that only happens under the file, there's no other logical situation in
which you do. So, we're going to assume... >> In fact they promised me that they're going
to change the specs so that it does say that. >> So that--if this returns zero then we must
be at the end of file and we'll throw in the end of file exception, otherwise while we
requested this many bytes, we're going to decrement it by the number of bytes we actually
skipped. And then we're in a Y loop. If we actually skip as many bytes as we requested,
remaining will be zero and we'll be out of the loop and the method returns. If we didn't
skip as many bytes as we requested, we go back for another skip.
>> So I believe that works, but it's a royal pain.
>> Yes. >> And what can we learn from it?
>> So the skip method is hard to use and error prone. You roll your own skipFully method
and if you--if you need to use skip, all right? There's a request for enhancement to had--add
it to input stream, some--other people are trying to do would summon this. In general,
if you have an API which is broken and there are APIs that are broken, and promise, once
you make an API, you're--it's cast in stone. It's forever, all right? And if you're stuck
with that forever broken API come up with a better way to call it, rather than persisting
with the old use. And for API designers, don't violate the principle of last--least astonishment,
right? I mean, make it easy to do the simple things. I mean, I find it hard to be--so,
interesting story about skip. There are 63 places in the JDK where skip is called, and
in 56 of them, they ignored the return value, right?
>> So that behavior is so astonishing, that even the people who wrote it don't believe
it. >> Right. And I think also pretty clearly
the people who wrote that API never actually tried writing any code that uses it. Because
if they had actually written anything more than a simple test case that used it, they
would have found out, "Gee, this is really hard to use correctly. Why don't we redesign
the API?" >> So the operative phrase here is used cases,
the way to find out if your API violates the principle least astonishment is try and use
it. If you haven't tried to use your API it doesn't work.
>> All right. >> Okay. Well that was a fine problem. Now
I have one for you. This one we call "Histogram Mystery". It almost rhymes. So, what we do
in this program is we have a list of words. The words are, "I recommend polygene lubricants."
>> You do? >> I do. And what we do is we make a histogram
out of these words. The histogram is a length five and the way we make the histogram is
we iterate over all the word pairs. So, we have a doubly nest of loop of words. And we--we
pair the words together, compute their hash code, choose a bucket based on the hash code
and increment that bucket. Then when we're all done, we add up the contents of the histogram
and we print it out. So I want to know, Bill, what the program prints?
>> All right. So, let's see. I recommend polygene lubricants. They are formed from doubly nested
loops. So they're a total of 16 pairs including like, you know, "I", "I", "recommend", "recommend,"
so forth. And for each one, we take the hash code of it, take the absolute value. So, we
don't... >> So just to--just to interject here, Bill.
Word1 plus Word2 is straight concatenates the two, so just concatenating the strings.
>> Right. Right. >> So we concatenate the strings, take the
hash code, take the absolute value, then we take that MOD to histogram length. So this
will give us a number between zero and four and then we increment the appropriate bucket.
Okay. Now at the end of doing this, I have like no idea how big histogram subzero is,
but I don't need to know. >> No, you don't.
>> Right? Because all we're going to do at the end is we're going to go through and we're
going to sum up all the values that are in any of the buckets, all right? So pair count
is zero and I go through and I sum up anything that got added. So, I don't know exactly where
each pair wound up. But since there are 16 combinations, each combination incremented
one bucket. When I add up all the buckets, I should get 16. And do this, so I set it--I
see these prints out C16. >> C16. Well, that's a reasonable guess. Let's
see if it's one of the options. >> It doesn't [INDISTINCT].
>> Let's see. Your options are [a] 83, [b] C16, [c] S, and [d] none of the above. And
I should mention for those of you who are too young to have--to have dealt with ASCII
directly. That capital A is a 65 decimal in ASCII. I'm not saying this is necessary to
solve the problem but if you find it useful, go ahead. We'll give them a moment, this one's
tricky. >> Okay. It is? Looks straightforward to me.
All right. I think we should move along. >> Okay.
>> I don't. They're still chattering. >> All right.
>> We don't--we don't have a hard stop. >> Okay. A little bit more. That's good. No
little red buzzer like there was at JavaOne? >> No.
>> No? All right. >> And they can edit these things out of the
video. Nobody wants to hear you chattering, I'm sorry. It's nice chattering, but. Okay.
The chattering seems to have died down. So, I think everyone knows the correct answer.
Now, keep your hands up. So how many people think that the answer is [a] 83?
>> We've got about a... >> A third.
>> ... [INDISTINCT] vote for something. >> How many people think choice [b] C16? Only
one? >> Poor lonely Bill.
>> I'm unique. I'm going to--I'll--I'll join Bill, moral support.
>> Thank you. >> How many people see, go with choice [c]
S? >> Okay. We got another third of you. And
how many people go with choice [d] none of the above? Smattering. So some of you aren't
voting. But anyway, it seems to be a tie between choices A and C. Is that right?
>> Yes. >> Yes. Okay. Let's find out what this program
really does shall we? >> All right.
>> None of the above. It always throws ArrayOutOfBoundsException. >> How did that happen?
>> Well, the intuition is that Math.abs does not necessarily return a positive--a non-negative
value, I should say. Absolute value can in fact be negative under certain circumstances.
And it turns out that the MOD operator, that doesn't necessarily return non-negative values
either. >> All right. Let's see this.
>> Let's take another look. See, these words were chosen very, very carefully. Now it turns
out that the hash code of the word polygene lubricants is Integer.MIN_VALUE. And that
the abs... >> And what's special about Integer.MIN_VALUE?
>> Well, it turns out that the absolute value of Integer.MIN_VALUE is Integer.MIN_VALUE.
>> Well, but that's negative. >> It is negative. So, here's what's going
on. In the two's-complement arithmetic, there are more negative numbers than positive. The
idea is that you have an even number values, so I get 2 to the 32 int values. One of them
is reserved for zero. That leaves you an odd number of values. So roughly half are negative
and half are positive. But there's one negative number without a partner and that's Integer.MIN_VALUE.
And if you apply the standard algorithm for negating it, it's the binary representation
is a one in a high order bit and zeros in all the other bits. So, to negate it, first
you complement it. So, you got a zero here and all ones. And then you add one to it and
you end up with all zeros and a one. You're back where you started. Negative Integer.MIN_VALUE
is Integer.MIN_VALUE, an absolute value. If it finds a negative number, it returns its
negative. So in every case but one, it does return a nonnegative value. But if we pass
an Integer.MIN_VALUE, you'd get out Integer.MIN_VALUE. And then we take it MOD histogram.length.
Well, the histogram length is five. So, you got a negative power of two MOD five. What
does it return? It's guaranteed to be a negative number because it isn't zero, right? The power
of two is not divisible by five. And it turns out that division in Java always returns something
if it's nonzero whose sign is the same as the numerator, which in this case is negative,
it turns out, as a practical matter, it returns negative three. And by the way, it always
returns negative three because the hash code of string is precisely specified in the spec.
So, we get bucket minus three and we tried to add one to bucket minus three and there's
your ArrayOutOfBoundsException. So, it never even gets down here. It turns out that there
is another little trick down here. It does not print out C16 because it turns out what
we're doing here is we are adding a character value to an int value. And what happens is
they are both integral types, so when you're adding two things of integral types you do
what's called the primitive widening conversion. You turn the smaller one, the character C
into the type of the bigger one, integers. So we do turn C into an integer. And as I
said A is 65, C is two more than A, so it's 67. So, we add 67 and 16 getting 83. So, the
program, you know, would print out 83, if in fact it didn't have this problem.
>> We talked about that in the first puzzle, didn't we?
>> We did. >> So how do we fix it? Well, two things.
First of all, we do the absolute value after the MOD operator, right? If the first thing
that we do is we take the hash code MOD length, that gives us a value between negative four
and positive four, right? We--if we take any value, whether negative or positive MOD five,
the result is going to be between negative four and positive four. And by the way, zero
occurs with twice the likelihood of any of the other values, roughly speaking. And then
we take the absolute value of that. And we take the absolute value of something between
negative four and four; you get something between zero and four. So now, we are guaranteed
to get an actual array location and now the program does end up with 16 as pair count.
And notice I've also changed C from a character to a string. So now it really will print out
C16. Just as my brother said it should. >> Okay.
>> There you have it. And what can we learn from this one? Well, Math.abs doesn't guarantee
nonnegative value, Integer.MIN_VALUE equals negative itself.
>> But, you know, that problem was probably okay. It only goes wrong once every four billion
times, you know. I'm sure it would never go wrong while we're showing our software to
the VC. >> No. No. I don't--it wouldn't go wrong then,
nor, I think, after we sold it to an important customer. It would never blow up, you know,
for them. It would blow up when we were testing it, right?
>> If we do good tests. >> Yes. And this thing, which everyone calls
the MOD operator, it isn't. It's the remainder operator and it is defined to have the sign
of the numerator of the first number, the left upper hand if it is non-zero. And finally,
when you are faced with this problem of translating a signed hashed value to a bucket that is
an array index, you can do anyone of these four things, either you can do the remainder
operator before you take the absolute value as we did when we fixed the program, or you
can right shift the number one, effectively throwing away the left most bit, or you can
mask it, effectively throwing away the right most bit. I said that backwards. The first
one throws away the higher order bit. The second one, I should've just said higher.
This one throws away the low order bit. This one throws away the high order bit,
>> Yes. >> Right?
>> Correct. >> And finally, if you use a power of two
length array, you can simply look at the low order bits of the hash code, which is good
if you are, you know, believe that your hash code to be of high enough quality that for
low order bits contain enough entropy. So that's it.
>> All right. So I have one for you, "A Sea of Troubles". And we're going to create a
random number, right? Well, actually a random Boolean and we're going to ask whether to
be or not to be. >> That is the question.
>> Yes, it is. And with that we're going to get the integer 3 or a floating 1, and print
out the result. >> Well, this looks utterly straightforward.
We get a random number generator, exceed it with time, so who knows what it's going to
do. We get the next Boolean, and I know although this is a capital B Boolean, that it actually
returns a primitive, so there's no boxing nonsense. So, to be is going to be either
true or false. So, it's going to be either true or false, or false or true. Either way,
one of them has got to be true. So once you order the two together, this is going to be
true, which means that we're always going to end up with the left side which is the
integer 3. So when we print it out, it's going to print 3, right?
>> Well, that sounds like a reasonable option. Let's see. Yes, indeed it is. So the question
is does this program print 3, 1.0, throw an exception, or none of the above? Put on your
thinking caps. You know, sometimes he's right. >> Rarely. I was right once before.
>> You were. May set a record. All right. I think we're ready to poll the audience.
>> Okay. >> So how many people think that this program
prints 3? Raise your hands. All right. How many people think that this program prints
1.0? No takers on that one. How many people think that it throws an exception? Be proud.
>> Be proud. Yeah, I see these tentative. What is this? Is this...?
>> You're allowed to be wrong. It's okay. >> I [INDISTINCT] around friends.
>> Right. >> Okay. And how many people think that the
answer is D, none of the above? Okay, I think D 1?
>> No, I think it was a tie between A and D.
>> Okay. All right. Well, let's see what the answer is in fact. It prints 3.0.
>> 3.0? >> Yes.
>> That wasn't even in the program. >> Yes. And the problem is, is that ternary
operator has very strange behavior when operating with mismatched integral rap types.
>> Well, let's take a look at the program again here.
>> Okay. So the problem is, is here we have an integer object and here we have a float
object. >> But they are both object references. What's
the problem? They don't even pick this one. >> Well, the process is it's going to take
the int--so this will evaluate to true, right? No surprises with what's going on with the
Boolean object. But it's actually the result of this operation is that it is going to create
a float object containing the value 3. >> So why does it do that?
>> That's why. It turns out that it's--you don't--it's not--because it's a wrap primitive
type, it's not actually doing what it would do if it was simply an object reference where
it simply chooses either this pointer value or this pointer value. It's doing what it
would do if it--you had a ? : with an int and a float where it says, "Okay, let me take
these two numeric types and convert them to whatever type is more general than both of
them." >> You know, I have an observation. This thing
here, it looks like a car rental contract. >> Yes.
>> And nobody ever reads those. >> Yes.
>> So, how do we fix this? >> Okay. Well, one way to do it is to avoid
the ternary operator, right? The ternary operator has a number of nasty cornered cases. If you
think you might not--are not sure what the ternary operator does in particular case,
don't use it. So here we simply declare of value type result and we use an if statement,
either assigned to a new integer or a new float. And in this case we will get the integer
object 3 which prints as 3. >> Okay. And what can we learn from it?
>> Okay, so avoid mixing types. The ? : operator has counterintuitive semantics when used with
two different wrapper types. And if you must select between two different wrapped integral
types, use else-if rather than the ? : operator. >> Now, I should point out, by the way, that
the ? : operator has sort of gotten a bad rep. It is very useful when it is being used
on two objects of the same type. So feel free to use it under those circumstances. It's
just these odd mixtures of wrapper types where it really screws up. Okay. So, that was a
fine problem. I have one last problem for you, the last problem of the day. Is that
a sigh of relief that I hear? >> This one we call "Ground Round". And the
reason we call it that is because it involves the round operator, Math.round. All we do
is we generate a random integer and if the rounded value of that integer is unequal to
the integer itself, we print Ground Round. So, what I want to know is, you know, what
does this program do most of the time? How does it--how does it behave?
>> All right. So we create a random number generator, we get an int value from it, and
you take an int value and you round it and round to an integer value and then you ask
is that not equal to an integer? I mean, round takes a number and moves it to the nearest
integer. And the last time I checked, the nearest integer to an integer was itself.
And so you should get back the same integer. This should be the identity function on ints.
And so it should never print Ground Round. >> Never. Let's see. That's an option. Well,
the options are, never, seldom, almost every time it's run, and every time it's run. So
what do you think? Does this program never print Ground Round, that will be choice A?
I thought... >> Are we ready to vote?
>> I think we're ready to vote. Are we...? Now, I hear some chattering.
>> We'll give them a moment. >> I think most of them have reached the conclusion.
>> No, we'll give them a few more seconds. This is the last puzzle of the day.
>> Yes. The last one we present. >> A good point.
>> They have the whole afternoon. Okay, I think we're ready.
>> Well, get--well, let's give them 10 seconds. >> All right. Okay.
>> I don't know. We got about a few of you. And how many people say D, every time it's
run? A smattering. Almost no one. So the winner I guess is seldom. Is that right?
>> Yeah. >> Seldom. Okay. Time to reveal the answer.
The answer is in fact, almost every time it's run, 97% for those of you who are obsessed
with quantification. It turns out the intuition here is that there is a silent, lossy conversion
from int to float, and that, in combination with the fact that math.round of float is
the method that is invoked is killing us. So let's take another look at it. See, the
problem here is when we call math.round of i, an integer, there is no method defined
in math called round that takes an argument whose type is integer. There are two versions
of this, two overloadings defined, round of float and round of double. And the float one
is the one that gets invoked. And that is true because whenever you have two applicable
methods, it chooses the most specific. So is every float a double or is every double
a float? Every float value is a double value and that makes float the most specific one.
So this is the one that gets invoked. Now, in order to call round a float on an integer,
you have to convert the int to a float. That is called a primitive widening conversion,
but it isn't really a widening conversation. It looses precision. You have 32 bits for
an int and 32 bits for a float. Well, 8 of the bits in the float are used to represent
the exponent. You can represent huge values with the float and tiny values and that means
you do not have one value left for every integer. So some integers must end up with the same
float value. By the time you've converted the int to a float, you've already lost. So
how many of them do you lose? Well, as I said, there are 8 exponent bits. There are 256 possible
values of those 8 bits. So that means, you know, that roughly speaking you're going to
lose sort of 99% of it. But it turns out it's not quite 99%. And to understand why, you
actually have to go into the details of floating point representation and we don't have time
for that today. But because of that, virtually no ints when converted to float and then converted
back to int are equal to themselves. >> Statistically speaking.
>> Statistically speaking. >> You know, 0 to 10 are okay.
>> Zero to ten are okay. The low ones are okay. It's when you--when you start getting
high that you start really, you know, losing big time. So, how do we fix it? Well, if we
cast i to a double, then we end up invoking the version of math.round where every int
has a unique value because there are only 32 bits in an int and 64 in a double. So by
converting an int to a double, if we convert it back to an int, we'll end up where we started.
And what can we learn from it? That silent so-called widening conversation from int to
float is both lossy and dangerous. It was a mistake in the language design. You know,
basically, you should never get a conversion where you lose precision and not get a warning
or even an error. You should have to explicitly cast to get from int to float. But unfortunately,
you don't. More generally, the float type is seldom called for, as my brother said earlier.
Unless you have a huge array of the things, don't use float, use double. Double is better.
In this case, you weren't really choosing these float, but you were sort of getting
it shoved down your throat. And finally, method overloading is dangerous. Arguably what killed
us is that there were two overloadings of round. If they're, you know, if they've given
them different names or if there had been no over--no version of it for float or something,
we wouldn't have ended up in this nasty situation. And that brings the normal part of our talk
to an end. So now we have the abnormal part. Java is a reasonably simple and elegant platform.
I used to say it is simple and elegant. But I think as of Java 5, I can't quote that anymore.
It has gotten a lot more complex. It has a few sharp corners and you should learn to
avoid them. And if someone wants to make it more complex, you should think very hard about
whether that is something you really want to do to the language.
>> And may I well mention that we had to work long and hard to come up with these puzzlers.
>> It is true. You know, it used to be easy to find puzzles that we haven't presented
yet. It's gotten much more difficult because we've sort of mined them all. And unless we
make the language more complex, you know I'm afraid we're not gonna get a lot more fundamentally
new puzzles. But, here's a good rule. If you weren't sure what the program does, it probably
doesn't do what you want. Keep your program simple and you will stay out of trouble.
>> Another important thing is to use FindBugs. It's a static analysis tool that I've written.
It's actually used within Google Bell Ball through Bugbot, and very shortly through Mondrian
and it actually finds all eight of the puzzlers that we discussed today. If you'd enter those
codes, FindBugs will say, "Hey, you're making this kind of mistake. Fix your code".
>> And for our viewers on the Web, where should you go to find FindBugs?
>> Well, you can just Google find bugs, and it will take you to the right location.
>> Excellent. >> And finally, don't code like my brother.
>> Don't code like my brother. And now, a word from our sponsors. If you enjoyed this
talked, then you should get a copy of this fine book called "Java Puzzlers" by myself
and Neal Gafter. It contains 95 puzzles, none of which we presented today. So they're 95
different puzzles, 52 optical illusions and tons of fun. And in case you wonder what this
is here, this is a shameless plug, you know. And that brings to a conclusion our talk.
So thank you for coming. And thank you for staying.
>> And I think now we can take some questions. >> [INDISTINCT]
>> Excellent question. So the question was, how long did it take me to find a string whose
hash value was integer.min_value and that made sense. So I have a dual processor Uptron
170 at home and I unleashed it. I have a dictionary of 200,000 words, which means there are 40
billion word pairs and unleashed to the Uptron on that. And it took it 10 minutes to calculate
all the hash values of every word pair. It found 11 collisions and this was the most
amusing one. >> Can you give an example, where you catch
error and accept it? >> Yes.
>> Some of the coded side of the thread had died by throwing a throwable, would you have--would
that have not just [INDISTINCT] >> We wouldn't have not cut that, yes.
>> So to repeat the question, if in our JUnit example, the code had died by throwing a throwable
that was neither an exception nor an error, would it have not caught it? And the answer
is yes, it would not have caught it. >> Don't do that.
>> However... Yeah. Don't do that. I mean, you--the...
>> It hurts when you go like that. >> You've dumped her. Yes, the language...
>> [INDISTINCT] up there; I think you should dump [INDISTINCT] as well.
>> Well, but I mean--I mean, very simple. I mean, the language would allow you to define
things that are neither an exception nor an error. I think there is no absolutely no use
case for that, the language should have forbidden it.
>> I agree with my brother in this instance. >> Yes sir, in the back?
>> Is there any reason that [INDISTINCT] >> So the question is should we define a math.round
that takes an int value? >> Yeah. [INDISTINCT]
>> So, actually--so I had one suggestion for how to fix the problem in Ground Round. That
we could declare a function in math called round that takes an int, returns an int, it
would be the identity function, and we would deprecate that function from the moment we
added it. All right. So if ever you use that--I mean, because there's no sense in calling
that method. And so if you did it, you would get--rather than getting the float version
which is lossy, you would get the int to int version which is marked as deprecated. And
that would be one reasonable thing to do, and--you know, one of the things which is
often interesting is you find yourself with a mistake in an API. And then say, "Well,
we can't actually fix it because that would be incompatible." And then you try to figure
out, "Well, is there some way that we can tweak things to make it be not so troublesome?"
And that might be one way how you could something like that.
>> Yeah, although it is arguably not upward compatible, you're changing the behavior of
existing programs. I would say a better solution is to run FindBugs. Now, there is something
my brother did not say because he was too modest. But I will say it. FindBugs catches
all eight problems in this year's talk. If you simply run these things through FindBugs,
it will draw your attention to every single one of them. So just run FindBugs and that
will take care of this one. Yes? >> What if we were to use an int and just
cast the integer [INDISTINCT]? I mean, I think there is [INDISTINCT] just rounds to the double
value that is equal to the integer value or something like that?
>> If there is, I've never heard of it. And I don't have time to check right now if it
exists. >> We can check [INDISTINCT].
>> But the problem, you know, remains that if you do the obvious thing, you get hurt.
So this is a case where, you know, it definitely does represent a trap or pitfall.
>> Well, I mean the problem is that nobody is probably going to, you know, deliberately
invoke the round function passing it an int. Sometimes we have seen cases where people
will do a computation and they think they're getting a float or a double, but they're not.
They're actually getting an int and they're accidentally doing the silent lossy conversion,
right? I mean this was a contrived example, most--many of the puzzlers are, but the important
lesson is, is this accidental lossy conversion. >> Yes?
>> Isn't that actually [INDISTINCT] likely happens with the generics? And is there a
[INDISTINCT] all of those integers and puzzle libraries [INDISTINCT] that most likely happened?
>> Okay. It is more likely to happen with generics?
>> I don't know if this is more--the question was is it more like to happen with generics?
And, you know, is there--is there any solution? I don't know if this one is. It is the case
that generics, especially in conjunction with auto-boxing, auto-unboxing, and varargs complicates
enormously the--the overload resolution algorithm. So it does make it more likely that you will
invoke the incorrect overloading and, you know, I think the only solution there is added
care, especially added care in API designs. API designs that used to be reasonable are
no longer. You know, it is even less advisable to use method overloading than it used to
be. And static analysis tools can help you find potential problems in preexisting APIs.
>> I don't actually think that this will be a problem because the generics is done by
type erasure and you can't do something where you define something that's generic over numbers.
And if you actually give it into invoke this overloaded version in among number of [INDISTINCT]?
>> Yes. This one--this one wouldn't. >> But other ones would.
>> Yes. >> We have other puzzles that rely on exactly
that. >> Right.
>> You know, for example, in list--there is a list.get method that takes an int. Well,
if you have and unless that removed, that removes the i element. If you have a list
of integers, you now how this funny overload resolution problem where if you remove 14,
are you removing the number 14 or the 14th element? It turns out it's the 14th element
and that's very confusing. So, any other questions? If not, thanks again for coming.
>> Thank you. >> Thanks again for staying and see you next