<<Previous | ToC
| Next >>
You probably didn't know that the word "eclipse" is derived from the Greek verb meaning to "fall out" or "fail". What we now know as the shadow of the earth covering the moon, or else the shadow of the moon blocking sunlight on earth, the ancient Greeks thought of as the moon or sun falling apart and failing in its main purpose, so they called it "failure" ('eclipsis'). Sometimes I think it's funny that we have taken the Greek word to mean the exact opposite, that "eclipse" in English refers to something better taking over what is thus deemed to be inferior. Me, I always think of "eclipse" in its original Greek sense, as a failure, and my first experience with the Eclipse IDE was consistent with this interpretation. But solar and lunar eclipses pass, and the IDE by that name also seems to have recovered somewhat (but not entirely) from its early failures.
If you are running Windows10, this will be somewhat more difficult, so see separate instructions here.
If you are running Chromebook, which is Linux under the hood, Eclipse was designed by unixies to run on Linux (the other operating systems were an afterthought), so it should in principle be possible to install it in your Chromebook, but I don't know how, and I don't particularly want to figure it out. Google "install Eclipse on Chromebook" and the Top Ten is filled with advice ranging from "You can't" to detailed instructions on how to do it if you are very familiar with Linux. If you succeed, you can send me a link (if it worked perfectly with no fixups) or else a detailed description of what you did (or both) and I'll post it here.
I Googled "download eclipse IDE" and got several hits near the top. Pick one and download the package suitable for your computer. It's some kind of zip file, which will unpack when you double-click it. Most people have a place for programs and shortcuts, so put the resulting folder there. Inside that folder, find the Eclipse application icon (or shortcut) and 2-click it. This opens a window with a half-dozen random icons over an artsy background. Hover over each one to pop up a label, then select the Tutorials. The first one is relatively easy, but if you make a mistake, you cannot get back to the beginning without the magic incantation. After you succeed at the first one, you might try the second, although we will be doing most of that next week. The others are probably over your head at this time. Eclipse is intended for people who already know what they are doing and what they want, not people like yourself just getting started. You have a steep climb ahead of you, but you can do it. If (or rather, when) you get stuck, get help. Help is free on the internet. Just Google your question, and if one of the top hits is in the "stackoverflow.com" domain, look there first, they are usually the best you can do -- at least for Java (they are kind of snotty about C questions).
Before I forget, the magic incantation to get back to the beginning
is to find the "Workspace" folder you agreed to and delete it. If you checked
the "Do not ask again" checkbox, it will re-create it the next time you
start up, but if you want that as a do-over also, delete also the unpacked
Eclipse folder and unpack it again. You must delete both folders to truly
start over.
In particular, we do not live in a perfect world, and the programs we write will not be perfect. Sometimes the data is bad, but more often our code is bad. Professional programmers prepare for Bad Things to Happen, and the approved Java way to do that is with exceptions. When some part of the program -- or more often, a library method -- cannot make sense of its data, it throws an exception, which some other part of the program can catch. It's like a return or break, but it can go a long way, and it can carry information about what went wrong (or at least what data didn't make sense and who thought so and how it got there).
"Exception" is a predefined class, which you can use as-is, or you can subclass it (which will be covered in my Tutorial on OOPS) to allow for more information. Any method that can throw an exception (or calls a method that can throw an exception not caught in that method), must declare that fact in its header:
public static void BadThingsHappen() throws Exception {
This puts the Java compiler on notice that whoever calls BadThingsHappen
must be able to catch exceptions or else pass them through uncaught (by
means of another "throws" clause). The compiler then forces you
to think about those problems -- at least as long as it takes you to catch
them or pass them through. Usually, as you consider each possible exception,
there is one best place to catch the problem and do something intelligent
about it. Java requires that to be in the calling chain of the method that
detected the error, that is either its caller, or its caller's caller,
or somewhere up the ladder, and the mechanism for doing that is the "try"
clause with one or more attached "catch" clauses, one for each
kind of exception you plan to catch there. Class "Exception" catches
them all. For now we'll do that:
try { // these things can cause exceptions...
/// do something dangerous here, like
BadThingsHappen();
} // end of try
catch(Exception e) { // catch all exceptions, information in e
/// do something to recover from the fact that BadThingsHappen didn't finish, or
System.out.println("Got exception " + e);
} // end of catch
The catch clause is kind of like a method declaration with
a single parameter of type Exception. You can give that parameter
any reasonable name you want, whether "e" or "whatsThis"
or "ImTryingToConfuseEverybody", just so that's the name you refer
to it by inside the catch clause.
Take a look at Zystem. The method System.in.read()
normally does not throw any exceptions, unless input has been redirected
from a file or something like that, and the file does not exist. We are
not doing redirection this week, so my catch clause does nothing
at all other than to set a default value. A credible way to deal with exceptions
is to ignore them and keep running, but you must do that on purpose. If
you are doing a nuclear reactor program or a medical program -- the license
you agreed to makes you promise not to do that, but suppose you were --
then if the core got too hot, your catch clause might want to
shut down the reactor, or if the patient went into cardiac arrest, your
program could sound an alarm at the nurse's station, depending on what
the system designer deemed appropriate for that particular catastrophe.
You can do that in Java, even if the vendors' lawyers don't want you to.
The earliest computers were used in calculating ballistic tables during WWII, so that gunners (especially on battleships) could know what angle to set the gun for to get a particular distance, and that involved very tiny fractional numbers, so they invented a computer number type that resembled what is otherwise known as scientific notation, a fraction times some power of the exponent (usually a power of ten for people to look at, but a power of two in computers), which is basically how much to shift this fraction left or right to get its true value. This gives a dynamic range from something less than 10-40 to more than 1035.
To give you an idea of what that means, the smallest particle I could find the size of is a proton or neutron at 10-15m, and the size of the visible known universe is estimated at 1024m. Floating point numbers in Java follow the IEEE-754 standard (I was draft editor on the committee that defined it some 40+ years ago), and like the standard they come in two sizes, single (float, the above range) and double, which is a lot more range and more than twice as many significant digits (bits) of precision. The fractional part (sometimes incorrectly called "mantissa" as if this were a logarithm, which it is not) of float is 23 bits, which is not quite seven decimal digits of precision. If you are trying to steer a rocket to Jupiter or calculate the interest on a 30-year million-dollar loan compounded daily, you need double; otherwise single-precision float is adequate (but probably not faster on modern 64-bit computers).
When doing math or compares on them, all Java numbers are automatically promoted to the better representation of the two operands (if different), or you can explicitly cast a numeric value into some other numeric representation by using the type name inside parentheses, thus:
int anInt = (int) 3.14159;
Casts into a smaller (or less precise) representation (like the
example above) can lose information, so you need to think about what you
are doing.
Java has four unary operators -- they take a single operand -- and a really weird rule about how they combine with other operators in larger expressions. Operator precedence is defined for each language that has more than one operator, which operations get done first when there are several different operators in a single expression with no parentheses. In Java some of the precedence rules are bizarre (Google "Java operator precedence" if you care). You really only need to know that multiply and divide happen before add and subtract (same as you learned in grade school arithmetic class); I find it useful to know also that logical AND happens before OR (which is true in every language with any precedence at all). Most of the rest are goofy or just plain illogical. They are the same as C/C++ but different from other languages, so I refuse to depend on operator precedence (other than as mentioned) and liberally sprinkle parentheses around in all other cases. Parentheses are essentially free.
Anyway, you probably need to know that the unary operators don't work as you'd expect, they have the highest precedence of all operators (not counting parentheses, which are not operators). Unary negative "-a" is not the equivalent of "0-a" but more like "(0-a)" -- including the parentheses, so you can actually write goofy things like "b+-a" and Java will not complain. I always put the parentheses in.
Besides the obvious negative and plus, there is another numeric unary operator ("~") which inverts every bit so that "~a" can also be thought of as the same as either "a^(-1)" or "-1-a". There is a unary boolean NOT operator "!" which does the same for the single-bit boolean type and is useful inside an if-statement where you have a simple boolean value (variable or function result) and you want the condition to activate on the false result instead of true:
if (!somevalue) DoSomething(); // do it only if (somevalue==false)
There is a third shift operator ">>>" that shifts right
with zero fill instead of extending the sign. There is a fifth arithmetic
operator, sort of a remainder, but its rather strange treatment of negative
operands limits its usefulness to places where you know they are positive
(which is most of the time). However, it costs an integer divide, which
is rather expensive in time, so most of the time we try to use a power
of two as the divisor, and (for non-negative a) "a%8"
has exactly the same value as "a&7" but takes much longer
to compute in most hardware. It only matters inside the inner loops.
There are logical AND ("&&") and OR ("||") operators that apply only to boolean values and give a boolean result with the additional quality that expression evaluation is terminated as soon as the answer can be known (called short-circuit boolean). This is because "false&&anyboolean" is always false, and "true||anyboolean" is always true. You are expected to be able to depend on that in complex expressions like where you want to do something if (for example) an object whom is not defined or if it is defined but some method (say theMethod) defined for whom's class returns true:
if ((whom==null) || whom.theMethod()) DoSomething();which is less code but otherwise exactly the same as:
if (whom==null) DoSomething();
else if (whom.theMethod()) DoSomething();
If (whom==null) but you try to call whom.theMethod()
anyway, the runtime system will throw a null-object exception. Either of
the above two ways to write it prevents that error.
One final operator is ternary, that is, it takes three operands in a short-circuit boolean sort of way. If all you wanted to do is assign to a variable theVar either TrueValue() or FalseValue() depending on whether TestMe is true or false, you could write:
if (TestMe) theVar = TrueValue();
else theVar = FalseValue();
If you wanted to use that value in a larger expression before assigning
it, or if you wanted to use it (or the value of a larger expression containing
it) you'd need a temporary variable, or else a conditional expression like
this:
theVar = TestMe ? TrueValue() : FalseValue();or for example in a conditional:
if ((TestMe ? TrueValue() : FalseValue())>0) DoSomething();
Note that TrueValue() and FalseValue() are never
in the same execution path. If TestMe is true, then TrueValue()
is evaluated (it could be any expression, possibly including operations
like divide that might fail if TestMe evaluates to false)
and not FalseValue(), and the other way around if TestMe
is false. Me, I never use conditional expressions, it's just too
much to remember (and get compile errors if I remembered wrong), and most
compilers generate exactly the same code if you create and use a temporary
variable. In other words, the compiler usually creates its own temporary
variable. But I mention it because you will see it in the code of programmers
with more dollars than sense. You want your code to be readable and admired
because you did clever things, not because you used obscure operators which
only get you dissed.
if (whom == 'N') {
NewFile();
OpenFile();
} // end of 'N'
else if (whom == 'O') {
OpenFile();
} // end of 'O'
else if (whom == 'V' || whom == 'P') {
Paste();
} // end of 'V'/'P'
else if (whom == 'X') {
Copy();
Delete();
} // end of 'X'
else if (whom == 'C') {
Copy();
} // end of 'C'
else { // none of the above..
System.out.println("Dunno what " + whom + " is");
} // end of default
The switch statement identifies a value to index the cases
by, then lists the cases corresponding to the value of the control variable,
like this:
switch (whom) {The braces are required, and you can have any number of statements between case labels -- including none, if you want two or more cases to do the same thing. Java defines the break statements at the end of each case to be optional -- mostly so you can have several case labels do the same block of code -- but if you forget to put the break in, Bad Things Happen. The Java compiler won't complain, and it won't throw any exceptions, so you may not discover the problem right away. I think it's one of the C mistakes that Java didn't fix, but nobody cares what I think. My compiler calls it an error.
case 'N':
NewFile();
OpenFile();
break; // end of 'N'
case 'O':
OpenFile();
break; // end of 'O'
case 'V':
case 'P':
Paste();
break; // end of 'V'/'P'
case 'X':
Copy();
Delete();
break; // end of 'X'
case 'C':
Copy();
break; // end of 'C'
default:
System.out.println("Dunno what " + whom + " is");
} // end of switch
There is also another kind of loop, but I never use it because it's just as easy to write:
while (true) {whatever(); if (!again) break;}as it is to write:
do {whatever();} while (again);and I don't need to remember so much. But it's there in the language. Take it or leave it.
The different conditionals and loops are called control structures,
and programs using them used to be called "structured programs" before
there was OOPS to brag about. The theory is that each
control structure has exactly one entry and one exit, but obviously the
break
and continue statements sort of put the lie to that idea. Maybe
if you consider the opening and closing braces to be those entry and exit
points, but then there are exceptions. A continue from within
a switch statement jumps out of the switch and out of
any containing if-else's to the front of the nearest containing loop. Even
a break leaps out of any containing if-else's to the
end of the nearest containing loop or switch statement. The try-catch
combination makes a giant jump out of any number of nested structures --
including any deeply nested subroutine calls -- from where the exception
was detected out to whatever catch clause catches it. It's not
very fast (you don't want to be doing it all the time), but it's extremely
useful. Maybe that's why we never hear about the virtues of structured
programming any more, because nobody wants to give up exceptions. All reductionisms
are wrong -- including this one. The real world is far too complicated
to fit into our nice reductionistic boxes.
But you don't need a subroutine to achieve the effect of testing possibly complex nested conditions, then getting out as soon as one of them is satisfied, you can do the same thing with break within a "while (true)" loop that never actually loops:
while (true) { // once through (never repeat), exit early when it succeeds
if (someTest) { // try this combination
DoSomething(); // to prepare additional tests...
if (test2) { // condition met, do it
/// do whatever you want done on this condition
break;}} // end of 1st test+whatever
if (anotherTest) { // it doesn't need to be complicated
/// do whatever you want done on this condition
break;} // end of 2nd test+whatever
if (moreTest) { // ... and so on
/// do whatever you want done on this condition
break;} // end of 3rd test+whatever
/// default, if nothing succeeds
break;} // end of while loop
Notice in particular the DoSomething() within the first
test, which cannot be done with a conventional if-else structure,
because the else only applies to the most recent if (before
the brace pair), not both of them -- including the second test, which cannot
be tested until after DoSomething() happens, and that cannot be
done outside the protection of the first "if(someTest)". You can
still do this kind of thing apart from the while-break structure,
but it involves tearing apart the contiguous code, or else an accessory
boolean variable that gets repeatedly tested -- and the program fails if
you missed one of them:
boolean didit = false;
if (someTest) { // try this combination
DoSomething(); // to prepare additional tests...
if (test2) { // condition met, do it
/// do whatever you want done on this condition
didit = true;}} // end of 1st test+whatever
if (!didit) if (anotherTest) { // it doesn't need to be complicated
/// do whatever you want done on this condition
didit = true;} // end of 2nd test+whatever
if (!didit) if (moreTest) { // ... and so on
X = A;
A = B;
B = X;
But what if you cannot allocate a temporary variable? You can do
the same thing using the exclusive-OR operator:
A = A^B;
B = A^B;
A = A^B;
The exclusive-OR operator is a little hard
to understand, so this is really obscure, but if you understand this, you
can use the operator anywhere. Let's go through it slowly, starting
with all possible results of the exclusive-OR operator
applied to two bits:
^ | 0 1
--+-----
0 | 0 1
1 | 1 0
Notice first that the operation is symmetrical, that is, A^B
== B^A. Notice also that A^A == 0, regardless of what is
in A. You can try this in Java, in your test program, running the debugger:
I try to use initial values 3 and 5 for testing bitwise operations, because
all four possible combinations are represented in four bits:
0011 3
^0101 5
----- -
0110 6
After the first line executes we have the combination of both variables
in A. The second line starts with that combination in A,
but toggles B back off, that is, A^B^B == A^0 == A, leaving
the original contents of A now in variable B. The third
line starts with that same combination in A, but toggles A
back off, that is, A^B^A == B^0 == B, leaving the original contents
of B now in variable A. Step through this in the debugger
and convince yourself that it works.
Your mission, should you choose to accept it, is to do the same thing
in the same number of steps, but using plus and/or minus instead of exclusive-OR.
If you really understand how this work, you can do it. But don't get stuck,
it's cute and fun, but not overly useful -- except as a way to hone your
control over the language. If you are good at puzzles like this, you will
be a good programmer. See my solution
after you did it (or if you decide you can't).
abit = someBits&-someBits;
To understand how this works you need to understand how negative
numbers work in the two's complement number system, which is used in all
modern computers. Basically it's this: -X = ~X+1, where ~X
as you should know, is all the bits flipped 0 to 1 and 1 to 0. You know
how to do binary arithmetic, right? The sum of two single bits in the same
bit position is the same as the exclusive-OR of those
two bits, with a carry out when both bits coming in are 1. So let's see
how different numbers go negative in a four-bit integer (where we discard
the final carry out):
0: 0000 1: 0001 2: 0010 4: 0100 6: 0110 8: 1000
~ 1111 ~ 1110 ~ 1101 ~ 1011 ~ 1001 ~ 0111
+1 (1)0000 +1 1111 +1 1110 +1 1100 +1 1010 +1 1000
I think of it this way: the rightmost run of zeros in the number,
and the rightmost one, are all preserved in its negative; everything to
the left of the rightmost one is complemented. Convince yourself this is
true. Then we remember that X&~X==0 for all values of X.
Recall the operation tables of AND and OR
for all possible single-bit values:
& | 0 1 | | 0 1
--+----- --+-----
0 | 0 0 0 | 0 1
1 | 0 1 1 | 1 1
Notice that if either input bit is zero, the AND
result bit is zero, so if you toggle all the bits to their opposite state,
then either the original bit is zero or the toggled bit is zero, so the
result is necessarily zero. Now applying that to the negative, all the
bits to the left of the rightmost one are toggled, so the AND
of those bits is all zero. Both the rightmost one and all the zeros to
its right are preserved, and the AND of the zeros
is still zero, but the AND of that rightmost one with
its negative (still a one in that bit position) is one, the single bit
left standing after everything else has been zeroed.
When you are working with pixels in an image, it is often useful to find runs of the same pixel value. This is easy with black and white images, where each pixel occupies one bit only, 32 (or 64) pixels in each integer. Assuming the pixels are packed "Little-Endian" (the little end of the number is bit 0 = pixel #0), then the first black pixel can be found by the formula above. Rather than step through each successive pixel in turn, we can get a mask of the whole first run of black pixels like this:
abit = somePix&-somePix; // 1st black pixel
nxtz = (somePix+abit)&~somePix; // next white pixel
run = nxtz-abit; // the whole run of black pixels
somePix = somePix&-nxtz; // remove that run from somePix
/// process the run...
You already understand getting the first black pixel. The second
line adds that single bit to all the pixels in the original integer; we
know that first bit is a one, so adding one to it produces a zero in that
bit position and a carry out to be added to the next pixel position. If
that one is originally a zero, then the sum leaves a one there with no
carry out and all the rest of the pixels remain unchanged in the sum, but
if it's also black (one) then it gets flipped to zero and the carry propagates
to the next pixel, until the run of black ends and you are left with a
single one in the position of that next white pixel (plus all the following
pixels unchanged). ANDing the complement of the original
pixels zeroes out all those remaining pixels, but keeps that final carry
out of the sum, which replaced a zero in the original, the complement of
which is one. Confusing? Yes, but a good programmer can focus on those
itty BITty details and squeeze awesome results
out of your computer. You can do that, if you want to.
But you don't need to do this today, unless you want to. For the first
couple years of your career as a programmer, you probably will never need
anyhing like this. Bookmark this link and come back
to it when you are ready. Then write up a small program with those four
lines in it, and maybe a test pattern of pixels like somePix=0x0510F7E0.
Put it in a loop and step through in the debugger, watching what happens
to the bits. The first run for that test pattern is six pixels 0x07E0,
and the second run is four pixels 0xF000, then the remaining three
runs are only one pixel each. Do you see how it works? Go thou and do likewise.
Invent something useful that nobody else ever thought of.
Next Week: Build Your Own Game in Java
Revised: 2021 May 31a