Java Notes
These notes are intended to give an introduction to the Java programming language. They have been adapted and extended from several sources (see credits at the end of these notes). The material can be easily covered in 3 - 4 days for the reader with some experience in programming. At the end of that the reader will be able to write and run his own applets.
These notes assume that you have the Java Development Kit (JDK) version 1.1 or later from Sun (http://www.javasoft.com) and a simple text editor such as NotePad. The instructions below will help you with that.
· The Java compiler is available from Sun for Windows and Windows NT, for UNIX, and MacIntoshes.
· The basic Java environment consists of a web browser that can play Java applets, a Java compiler to turn to Java source code into byte code, and a Java interpreter to run Java programs. These are the three key components of a Java environment. You'll also need a text editor like NotePad.
· Sun has made the Java Developers Kit available for its supported platforms. It includes an applet viewer that will let you view and test your applets. The JDK also includes the javac compiler, the java interpreter, the javaprof profiler, the javah header file generator (for integrating C into your Java code), the Java debugger and limited documentation. However most of the documentation for the API and the class library is on Sun's web site.
· Windows Installation Instructions
The Windows release is a self extracting archive. You will need about 50
megabytes of free disk space to install the JDK. Execute the file by
double-clicking on it in the File Manager or by selecting Run...
from the Program Manager's File menu and typing the path to the file.
This will unpack the archive. The full path is unimportant. It could be your C:
drive. If this is the case the files will live in C:\java. If you
unpacked it somewhere else just replace C:\ by the full path to the java
directory in what follows. You will need to add C:\java\bin directory to
your PATH environment variable
· Unix Installation Instructions
If you're on a shared system at a university or an Internet service provider,
there's a good chance Java is already installed. Ask your local support staff
how to access it. Otherwise follow these instructions.
The Unix release is a compressed tar file. You will need about 80
megabytes of disk space to uncompress and untar the JDK. You do this with the
commands:
% uncompress
JDK-1_0_2-solaris2-sparc.tar.Z
% tar xvf
JDK-1_0_2-solaris2-sparc.tar
The exact file name may be a little different if you're retrieving the release
for a different platform such as Irix or if the version is different. You can untar
it in your home directory, or, if you have root privileges, in some convenient
place like /usr/local where all users can have access to the files.
However root privileges are not necessary to install or run Java. Untarring the
file creates all necessary directories and sub-directories. The exact path is
unimportant. It could be in /usr/local. If a sysop already installed it,
this is probably where it lives. (Under Solaris it's also possible the sysop
put it into /opt.) If this is the case the files live in /usr/local/java.
If you unpacked it somewhere else, just replace /usr/local by the full
path to the java directory in what follows. If you installed it in your home
directory, you can use ~/java and ~/hotjava instead of a full
path.
You now need to add /usr/local/java/bin directory to your PATH
environment variable. You use one of the following commands depending on your
shell.
csh, tcsh:
% set path=($PATH
/usr/local/java/bin)
sh:
% PATH=($PATH
/usr/local/java/bin); export $PATH
You should also add these lines to the end of your .profile and .cshrc
files so you won't have to do this every time you login.
· Java is a "simple, object-oriented, distributed, interpreted, robust, secure, architecture neutral, portable, high-performance, multithreaded, and dynamic language."
· Java was made to look and act much like C and C++, since so many people are already familiar with those languages. However, to simplify matters, several features common to C and C++ were not included. Among those are pointers, header files, and goto's. Java is not 100% object-oriented like SmallTalk, but it is much more so that C++.
· Distributed: Java was designed with today's distributed networks in mind. It is capable of network communication and handling socket calls.
· Interpreted: Java code is compiled into byte-code, which is somewhat similar to a binary executable. Instead of being run by your operating system, though, the byte-code is run by the Java Virtual Machine, a byte-code interpreter. The advantage is that the Java code doesn't have to be directly translated into the system-specific machine code. Instead, the Java Virtual Machine acts as an intermediary which will be the same on every system. So, the end result is code that acts the same on every computer.
· Robust: Java is a strongly typed language, and so will catch most errors long before run-time. Also, since Java doesn't support pointers, code written in Java is much more reliable. Another feature adding to Java's robustness is its exception handling. When an error occurs, the code is able to deal with it on the fly, allowing for a high rate of recovery.
· Secure: Java was designed with the internet in mind. As such, it has to be secure to be usable at all. Java applets are not allowed to do any file I/O. Also, the lack of pointers prevents a large amount of potential problems. The internet is a dangerous place. There is really no way to be completely secure without being useless. Java has tried to find a middle ground between being unprotected and being unusable.
· Portable: Because Java runs on the Java Virtual Machine, and not the individual system itself, Java code is easily portable to any system capable of running the virtual machine.
· Performance: Java is approximately 20 times slower than C. However, it is more than fast enough for most of its more commons uses such as applets and multimedia. Java does allow you, though, to import native C code for the times when speed is essential.
· Multithreaded: Java can run several simultaneous process threads. This allows the user to easily code programs with can do more than one thing at once. Such multitasking capability is invaluable for use in building GUI's (Graphic Unit Interface) and other such applications.
· Dynamic: Java was built to be able to adapt to the continually evolving environment. It can dynamically load in classes when they are needed, and it can even got out over the 'Net to find them.
· Initially computer memories were so small and the machines so slow, that program efficiency was the primary concern. Algorithms were very closely tied to the capabilities of the specific machine they ran on. This is called machine language programming. The toggling of individual memory locations (by switch or other means) is called a first-generation language. In a first generation language there is almost no abstraction.
· As computers grew in power and memory, it was no longer possible for a programmer to keep track of what was happening at every location in the machine's physical memory. Card readers and assembly language were invented to make programming more feasible. In assembly language the programmer uses mnemonic codes like MOV to represent particular bit sequences. These codes mapped directly to individual instructions on the CPU, and memory was still addressed directly. One code meant exactly one CPU instruction.
· The first high-level programming language, Fortran, was invented to spare programmers from the pains of dealing with keeping track of the location of their variables in memory. Fortran was the first example of a third-generation language. In a third generation language you tell the computer the algorithms and data structures it should use to calculate the results you want; but you use more abstract logical and mathematical operators rather than directly manipulating addresses in memory and CPU instructions. In a third generation language, statements represent several machine instructions. Which instructions they represent may even depend on their context. These languages may be compiled or interpreted. In either case your program code needs to be translated into equivalent machine instructions. This level of abstraction made considerably more powerful algorithms and data structures possible.
· Java is a very advanced third generation language. Most of the other computer languages you're probably familiar with, Fortran, Basic, C, C++, Cobol, Pascal, as well as most of the one's you're not familiar with (AppleScript, Frontier, Eiffel, Modula-3, ADA, PL/I, etc.) are also third-generation languages (or 3GL's for short). Fourth generation languages (or 4GL's for short) moved the abstraction level a step higher. In these languages you tell the computer what results you want rather telling it how to calculate those results. For instance you would ask for the total sales for the year, without specifying the loops necessary to sum all the sales of all the salespeople. SQL is the most popular fourth generation language.
· Pascal and C were the next widely successful languages. They made possible a style of programming known as structured programming. Structured programming languages have many different flow control constructs (switch statements, while loops, and more) as well as tools for more complicated data structures (structs, records and pointers). Finally they have subroutines with local variables that are capable of splitting the code into more manageable and understandable chunks. These languages proved more capable of writing larger, more maintainable programs. However they too began to bog down when faced with the need to share code among programmers and to write very large (greater than 50,000 line) programs.
· The third generation of 3GL's (3.3 GL's) began to take hold in the late 80's. These were the object oriented languages. Although object oriented languages had been around since the late 1960's, it wasn't until the late 80's that computer hardware became fast enough and memory cheap enough to support them. (Object oriented programming is not a panacea. It adds a speed penalty over C or Fortran code, and often requires twice as much memory.)
· Object-Oriented Programming, or OOP, is a method of coding which results in better, more reliable and more reusable code.
· Objects. "The fundamental idea behind object-oriented languages is to combine into a single unit both data and the functions that operate on that data. Such a unit is called an object."
· Code in OOP. Let's say we have to
write code that will model a store. We have to keep track of customers, how
much money the customers have, and how much the customers have bought. We have
to keep track of the sales clerks, where they are and when their shifts are.
And we have to keep track of the merchandise, how much we have and where it is.
First we'll look at a Structural method. To keep track of the customers and
their money and purchases, we'll need an array of structures. Same for the
clerks, and same for the merchandise. That pretty much takes care of the data.
Now, we'll need a function to spend a customer's money, one to add a customer's
purchases, one to move the clerks, one to update the clerks' shifts, one to
move merchandise, and one to sell the merchandise. Each of these functions will
be globally available and will be called by a larger control structure. Also,
all of the data will be visible to all of the functions.
Now, let's look at an OOP implementation. We'll define three objects, Customer,
Clerk, and Merchandise. The Customer object will have a Money variable and a
Purchases array. The Customer object will also have a Spend method and a Buy
method to update the money and purchases, respectively.
The Clerk object will have a Location variable and Shift variable. It will have
a Move method and a CheckShift method.
The Merchandise object will have a Price variable and a Location variable. It
will have Move and Sell methods.
We will have to have three arrays, one for each object type. The methods will
be visible to the control structure, but the data will be hidden, or
encapsulated, within each object.
So, what's the difference? They both have pretty much the same stuff; it's just
arranged a little differently.
Well, the different arrangement is what makes OOP more powerful. The first
obvious advantage is that the data is hidden. You cannot accidentally
alter the data for a customer while fiddling with something else. You can only
access one of the object's data by directly going through that object.
Another advantage is reusability of the code. Say, for instance, we now
have to simulate an assembly line. One of the things we'll need to keep track
of is where the workers are, and who's currently working. We can just borrow
the Clerk object from our current model and plop it in our new project without
any modification! It already knows how to keep track of where it is, and when
it's shifts begin and end. That's all we need for an assembly-line worker.
Try doing that with the Structured code! You'd have to copy out the code for
the structure, the code for each of the relevant functions, and modify the
functions to fit the new array name, along with some other house keeping.
OOP allows you to bundle together related data and functions so that they can
be easily moved around and reused.
Yet another advantage is in maintenance. Say we want to be more specific
now. We want to model a grocery store. And, we don't want to keep track of just
"merchandise." We want to keep track of meat, fruits and vegetables,
and dry goods.
With the structured method, we'd have to trade the merchandise array for three
separate item arrays. We would then have to write three new structures to
handle the different data to be kept for each item. We would also have to
modify all the functions which used the merchandise array to now work with the
three new arrays, which could be a pain by itself!
· Inheritance. With OOP we could take
advantage of a property called inheritance.
Inheritance allows an object to inherit code from another object, saving the
time of writing all new code.
We could create three new objects, Meat, Fruit_N_Veggies and Dry_Goods.
Each of these new objects can be a child of our previous Merchandise class. So,
each will automatically inherit all of the data and function contained in
Merchandise.
Then, we could further specify each new object. For example, for Meat,
we could add a SellDate variable and a Spoiled method so that we
can measure losses from spoilage. Now, here's the really cool part. Since they
are all children of Merchandise, they can all still go in the Merchandise
array. We don't have to make a new array for each of them.
· Polymorphism. When we create a new Clerk,
we want to be able to initialize his or her starting position and shifts. So,
when we create a Clerk, we can do this:
Clerk new_clerk = new Clerk();
new_clerk.Location = "aisle
9";
new_clerk.Shift =
"12pm-12am";
But, this is rather long-winded and clumsy. We can define something called a constructor,
which we'll discuss later, which will allow us to do the same thing like this:
Clerk new_clerk = new
Clerk("aisle 9", "12pm-12am");
Now, isn't that better? But, the code for initializing the object is in the
constructor, so we haven't really changed anything.
Here is where the power of polymorphism comes in. What if we don't want
to always assign both the shift and the location at the same time? What if we
want to be able to just assign the location and leave the shift until later? We
can do that without any problem. We can define another constructor so that we
can do this:
Clerk new_clerk = new
Clerk("aisle 9");
Now, look at the two initializations by constructor. They both call Clerk()
(which is the name of the constructor). How can they both be named the same,
but use different arguments?
The answer is polymorphism! In OOP you can declare a method or constructor more
than once, each with a different set of arguments, but the same name. When you
call the method, the compiler knows from the argument list, called the
signature, which version of the method you mean.
This may not be a major code-saving feature, but it reduces confusion by 100%.
Instead of add_int(), add_float(), add_short(), etc., you
can just have an add() which will know by the arguments which one you
need.
· In Java Integer numeric types are 8-bit byte, 16-bit short, 32-bit int, and 64-bit long. A byte is a 8 binary digit. The representation of decimal numbers increases by a factor of 2 with each increasing bit. Thus, a n-bit type can hold (2n -1) decimal numbers. Thus a byte can hold decimals from -128 to 127. A long is an integer type variable that can hold up to 9,223,372,036,854,775,807.
· A Float is a single-precision floating point numeric type with a 32-bit IEEE size (IEEE - Institute of Electrical and Electronics Engineers, New York, www.ieee.org). To represent a real number in binaries, part of the binaries are use for the mantissa (significant digits) and the other part for the exponent of 10. The largest number a float can hold is 3.4028235E38.
· A Double is double-precision floating
point. The largest double value in Java is 1.7976931348623157E308.
Character data types are 16-bit byte, running from 0 through 65,535.
A Java boolean variable assumes the value true or false. A
Java boolean type can't be converted to any numeric type.
· One declares a data type as follows
int Integername;
short IntegerShort;
char ThisChar;
. . . . .
· After declaration, one initializes a
data type as
IntegerName = 125;
IntegerShort = 32;
ThisChar = 'Q'; //
Characters are defined within single quotes
· Once can also declare and initialize the data types simultaneously. The program below declares, initializes, and prints out the largest numbers hold by the data types in Java.
· Compile and Run it. Copy this program
to your java work directory, compile it (in the DOS window type: javac MaxVariablesDemo.java and run it by typing javaMaxVariablesDemo). For the moment, don't worry
much about the verbosity occurring in the first 2 lines of the code and on the
printout lines. But, notice that from some name definitions (syntax) it
is quite straightforward to understand what the program wants to do. To make an
output to a file type java
MaxVariablesDemo > OutputFileName.out. To be able to compile and run from your directory,
you have to set the PATH to it. Follow the instructions from the JDK
installation to find out how change your PATH environment variable.
public class MaxVariablesDemo
{ // defines the class
public static
void main(String args[]) { // the main method inside the class
// integers
byte largestByte = Byte.MAX_VALUE; // MAX_VALUE is of course the maximum
value
short largestShort = Short.MAX_VALUE;
int largestInteger = Integer.MAX_VALUE;
long largestLong = Long.MAX_VALUE;
// real numbers
float largestFloat = Float.MAX_VALUE;
double largestDouble = Double.MAX_VALUE;
// other primitive types
char aChar = 'S';
boolean aBoolean = true;
// display them all
System.out.println("The largest byte value is " + largestByte);
System.out.println("The largest short value is " + largestShort);
System.out.println("The largest integer value is " + largestInteger);
System.out.println("The largest long value is " + largestLong);
System.out.println("The largest float value is " + largestFloat);
System.out.println("The largest double value is " + largestDouble);
if (Character.isUpperCase(aChar)) {
System.out.println("The character " + aChar + " is upper
case.");
} else {
System.out.println("The character " + aChar + " is lower
case.");
}
System.out.println("The value of aBoolean is " + aBoolean);
}
}
Its output yields:
The largest byte value is 127
The largest short value is
32767
The largest integer value is
2147483647
The largest long value is
9223372036854775807
The largest float value is
3.4028235E38
The largest double value is
1.7976931348623157E308
The character S is upper case.
The value of aBoolean is true
· The Java language has some predefined libraries, or packages. This program only needs the standard library. When we type Byte.MAX_VALUE we access the maximum value of the object Byte, defined in this package. System.out.println is a function (in Java we say Method) contained in this package, which prints out everything written inside double quotes. The numeric value of the variable is written after the quotation by concatenating its name with a plus sign. Finally, the method Character.isUpperCase() checks if the variable aChar is uppercase or not. You will see several methods like this along these notes. They are all inherited from the Java packages and their action are most of times easy to figure out from their names. Try to memorize their name definitions.
· Blocks are important both syntactically and logically. Without the braces the code wouldn't compile. The compiler would have trouble figuring out where one method or class ended and the next one began. Similarly it would be very difficult for someone else reading your code to understand what was going on. For that matter it would be very difficult for you, yourself to understand what was going on. The braces, or curly brackets { }, are used to group related statements together. In the broadest sense everything between matching braces is executed as one statement (though depending not necessarily everything inside the braces is executed every time).
· Blocks can be hierarchical. One block can contain one or more subsidiary blocks. In this case we have one outer block that defines the MaxVariablesDemo class. Within the MaxVariablesDemo block we have a method block called main.
· A variable's scope is the block of code within which the variable is accessible and determines when the variable is created and destroyed. The variable inside the block is a local variable.
· You can declare local variables anywhere in a
method or within a block of code in a method. In MaxVariablesDemo, all
of the variables declared within the main method are local variables.
The scope of each variable -- the code that can access each variable -- extends
from the declaration of the variable to the end of the main method
(indicated by the first right curly bracket } that appears in the program
code). As soon as the variable goes out of scope, it does not exist and is a
candidate for the garbage collector. The garbage collector is a part of
the Java interpreter that deletes from memory all unused objects. Ex:
{
some code here // no x
defined here
{
more code here
int x = 2;
}
y = x; // WRONG. No x
defined in this block
int x = 3; // OK. This x is
different from the one in the above block
more code here
}
· Comments can appear anywhere in a source file.
Comments are identical to those in C and C++. Everything between /* and */ , or
after //, is ignored by the compiler and everything on a line after two
consecutive slashes is also thrown away. Therefore the following program is, as
far as the compiler is concerned, identical to the first one:
// This is the Hello World program
in Java
class HelloWorld {
public static
void main (String args[]) {
/*
Now let's print the line Hello World */
System.out.println("Hello World");
}
}
which yields
Hello World
· An operator performs a function on either one, two, or three operands. An operator that requires one operand is called a unary operator. For example, ++ is a unary operator that increments the value of its operand by 1. An operator that requires two operands is a binary operator. For example, = is a binary operator that assigns the value from its right-hand operand to its left-hand operand. And finally a ternary operator is one that requires three operands. The Java programming language has one ternary operator, ?:, which is a short-hand if-else statement.
|
Operator |
Use |
Description |
|
+ |
op1 + op2 |
Adds op1 and op2 |
|
- |
op1 - op2 |
Subtracts op2 from op1 |
|
* |
op1 * op2 |
Multiplies op1 by op2 |
|
/ |
op1 / op2 |
Divides op1 by op2 |
|
% |
op1 % op2 |
Computes the remainder of dividing op1 by op2 |
Ex:
int i = 37;
int j = 42;
double x = 27.475;
double y = 7.22;
System.out.println("
i % j = " + (i % j)); // prints i % j = 37
System.out.println("
x % y = " + (x % y)); // prints x % y = 5.815
|
Operator |
Use |
Description |
|
++ |
op++ |
Increments op by 1; evaluates to the value of op before it was incremented |
|
++ |
++op |
Increments op by 1; evaluates to the value of op after it was incremented |
|
-- |
op-- |
Decrements op by 1; evaluates to the value of op before it was decremented |
|
-- |
--op |
Decrements op by 1; evaluates to the value of op after it was decremented |
Ex: Copy this program to a file named AutoInc.java
and run it to understand the action of post- (pre-) (in) decrement
operators.
// Demonstrates the ++ and --
operators.
public class AutoInc {
public static void
main(String[] args) {
int i = 1;
prt("i :
" + i);
prt("++i :
" + ++i); // Pre-increment
prt("i++ :
" + i++); // Post-increment
prt("i :
" + i);
prt("--i :
" + --i); // Pre-decrement
prt("i-- :
" + i--); // Post-decrement
prt("i :
" + i);
}
static void prt(String s)
{ // to save typing
System.out.println(s);
}
}
which yields
i : 1
++i : 2
i++ : 2
i : 3
--i : 2
i-- : 2
i : 1
|
Operator |
Use |
Returns true if |
|
> |
op1 > op2 |
op1 is greater than op2 |
|
>= |
op1 >= op2 |
op1 is greater than or equal to op2 |
|
< |
op1 < op2 |
op1 is less than op2 |
|
<= |
op1 <= op2 |
op1 is less than or equal to op2 |
|
== |
op1 == op2 |
op1 and op2 are equal |
|
!= |
op1 != op2 |
op1 and op2 are not equal |
|
Operator |
Use |
Returns true if |
|
&& |
op1 && op2 |
op1 and op2 are both true, conditionally evaluates op2 |
|
|| |
op1 || op2 |
either op1 or op2 is true, conditionally evaluates op2 |
|
! |
! op |
op is false |
|
& |
op1 & op2 |
op1 and op2 are both true, always evaluates op1 and op2 |
|
| |
op1 | op2 |
either op1 or op2 is true, always evaluates op1 and op2 |
|
^ |
op1 ^ op2 |
if op1 and op2 are different--that is if one or the other of the operands is true but not both |
Ex: The following code test the use of
relational and logical operators.
// Relational and logical
operators.
import java.util.*;
public class Bool {
public static void
main(String[] args) {
Random rand =
new Random(); // Java random number generator
int i =
rand.nextInt() % 100;
int j =
rand.nextInt() % 100;
prt("i =
" + i);
prt("j =
" + j);
prt("i >
j is " + (i > j));
prt("i
< j is " + (i < j));
prt("i
>= j is " + (i >= j));
prt("i
<= j is " + (i <= j));
prt("i ==
j is " + (i == j));
prt("i !=
j is " + (i != j));
// Treating an
int as a boolean is
// not legal
Java
//! prt("i && j is
" + (i && j));
//! prt("i || j is " +
(i || j));
//! prt("!i is " + !i);
prt("(i
< 10) && (j < 10) is "
+ ((i < 10) && (j < 10)) );
prt("(i
< 10) || (j < 10) is "
+ ((i < 10) || (j < 10)) );
}
static void prt(String s)
{ // to save typing
System.out.println(s);
}
}
In one run of this code I've got
i = -49
j = 29
i > j is false
i < j is true
i >= j is false
i <= j is true
i == j is false
i != j is true
(i < 10) && (j <
10) is false
(i < 10) || (j < 10) is
true
|
Operator |
Use |
Operation |
|
>> |
op1 >> op2 |
shift bits of op1 right by distance op2 |
|
<< |
op1 << op2 |
shift bits of op1 left by distance op2 |
|
>>> |
op1 >>> op2 |
shift bits of op1 right by distance op2 (unsigned) |
Ex: Each shift operator shifts the bits of the
left-hand operand over by the number of positions indicated by the right-hand
operand. The shift occurs in the direction indicated by the operator itself.
For example, the following statement shifts the bits of the integer 13 to the
right by one position:
13 >> 1;
The binary representation of the number 13 is 1101. The result of the shift
operation is 1101 shifted to the right by one position -- 110 or 6 in decimal.
· If you shift a char, byte, or short, it will be promoted to int before the shift takes place, and the result will be an int. Only the five low-order bits of the right-hand side will be used. This prevents you from shifting more than the number of bits in an int. If you're operating on a long, you'll get a long result. Only the six low-order bits of the right-hand side will be used so you can't shift more than the number of bits in a long.
|
Operator |
Use |
Operation |
|
& |
op1 & op2 |
bitwise and |
|
| |
op1 | op2 |
bitwise or |
|
^ |
op1 ^ op2 |
bitwise xor |
|
~ |
~op2 |
bitwise complement |
Ex: Here's an example that demonstrates the
use of all the operators involving bits.
import java.util.*;
public class BitwiseDemo {
public static void
main(String[] args) {
Random rand =
new Random(); // generates a random number
int i =
rand.nextInt();
int j =
rand.nextInt();
pBinInt("-1", -1); // prints out
pBinInt("+1", +1);
int maxpos =
2147483647; // maximum integer
pBinInt("maxpos", maxpos);
int maxneg =
-2147483648; // minimum integer
pBinInt("maxneg", maxneg);
pBinInt("i", i); // prints random integer
pBinInt("~i", ~i); // and its complement
pBinInt("-i", -i); // etc...
pBinInt("j", j);
pBinInt("i
& j", i & j);
pBinInt("i
| j", i | j);
pBinInt("i
^ j", i ^ j);
pBinInt("i
<< 5", i << 5);
pBinInt("i
>> 5", i >> 5);
pBinInt("(~i) >> 5", (~i) >> 5);
pBinInt("i
>>> 5", i >>> 5);
pBinInt("(~i)
>>> 5", (~i) >>> 5);
// now for long
ints
long l =
rand.nextLong();
long m =
rand.nextLong();
pBinLong("-1L", -1L);
pBinLong("+1L", +1L);
long ll =
9223372036854775807L;
pBinLong("maxpos", ll);
long lln =
-9223372036854775808L;
pBinLong("maxneg", lln);
pBinLong("l", l);
pBinLong("~l", ~l);
pBinLong("-l", -l);
pBinLong("m", m);
pBinLong("l & m", l & m);
pBinLong("l | m", l | m);
pBinLong("l ^ m", l ^ m);
pBinLong("l
<< 5", l << 5);
pBinLong("l >> 5", l >> 5);
pBinLong("(~l) >> 5", (~l) >> 5);
pBinLong("l >>> 5", l >>> 5);
pBinLong("(~l) >>> 5", (~l) >>> 5);
} // end main
/* The two methods at
the end, pBinInt( ) and pBinLong( ) take an int or
a
long, respectively, and print it out in binary format along with a
descriptive string. You can ignore the implementation of these for now.
*/
// to save typing
static void pBinInt(String
s, int i) {
System.out.println(
s +
", int: " + i + ", binary: ");
System.out.print(" "); // adds space to a line (no
new line)
for(int j = 31;
j >=0; j--) // prints out "i" in binary (32 bytes)
if(((1 << j) & i) != 0)
System.out.print("1");
else
System.out.print("0");
System.out.println(); // go to other line
} // end pBinInt
// to save typing
static void pBinLong(String
s, long l) {
System.out.println(
s +
", long: " + l + ", binary: ");
System.out.print(" "); // adds space to a line (no
new line)
for(int i = 63;
i >=0; i--) // prints out "i" in binary (64 bytes)
if(((1L << i) & l) != 0)
System.out.print("1");
else
System.out.print("0");
System.out.println(); // go to other line
} // end pBinLong
} // end BitwiseDemo
In one run of this code I've got
-1, int: -1, binary:
11111111111111111111111111111111
+1, int: 1, binary:
00000000000000000000000000000001
maxpos, int: 2147483647,
binary:
01111111111111111111111111111111
maxneg, int: -2147483648,
binary:
10000000000000000000000000000000
i, int: 567291140, binary:
00100001110100000010110100000100
~i, int: -567291141, binary:
11011110001011111101001011111011
-i, int: -567291140, binary:
11011110001011111101001011111100
j, int: 83676736, binary:
00000100111111001100111001000000
i & j, int: 13634560,
binary:
00000000110100000000110000000000
i | j, int: 637333316, binary:
00100101111111001110111101000100
i ^ j, int: 623698756, binary:
00100101001011001110001101000100
i << 5, int: 973447296,
binary:
00111010000001011010000010000000
i >> 5, int: 17727848,
binary:
00000001000011101000000101101000
(~i) >> 5, int:
-17727849, binary:
11111110111100010111111010010111
i >>> 5, int:
17727848, binary:
00000001000011101000000101101000
(~i) >>> 5, int:
116489879, binary:
00000110111100010111111010010111
-1L, long: -1, binary:
1111111111111111111111111111111111111111111111111111111111111111
+1L, long: 1, binary:
0000000000000000000000000000000000000000000000000000000000000001
maxpos, long:
9223372036854775807, binary:
0111111111111111111111111111111111111111111111111111111111111111
maxneg, long:
-9223372036854775808, binary:
1000000000000000000000000000000000000000000000000000000000000000
l, long: -412489331692580499,
binary:
1111101001000110100010110010001101100110110100110101000101101101
~l, long: 412489331692580498,
binary:
0000010110111001011101001101110010011001001011001010111010010010
-l, long: 412489331692580499,
binary:
0000010110111001011101001101110010011001001011001010111010010011
m, long: -3757276681845227476,
binary:
1100101111011011011110001100001111000111110000011100000000101100
l & m, long:
-3872523919420211156, binary:
1100101001000010000010000000001101000110110000010100000000101100
l | m, long:
-297242094117596819, binary:
1111101111011111111110111110001111100111110100111101000101101101
l ^ m, long:
3575281825302614337, binary:
0011000110011101111100111110000010100001000100101001000101000001
l << 5, long:
5247085459546975648, binary:
0100100011010001011001000110110011011010011010100010110110100000
l >> 5, long:
-12890291615393141, binary:
1111111111010010001101000101100100011011001101101001101010001011
(~l) >> 5, long:
12890291615393140, binary:
0000000000101101110010111010011011100100110010010110010101110100
l >>> 5, long:
563570460688030347, binary:
0000011111010010001101000101100100011011001101101001101010001011
(~l) >>> 5, long:
12890291615393140, binary:
0000000000101101110010111010011011100100110010010110010101110100
|
Operator |
Use |
Equivalent to |
|
+= |
op1 += op2 |
op1 = op1 + op2 |
|
-= |
op1 -= op2 |
op1 = op1 - op2 |
|
*= |
op1 *= op2 |
op1 = op1 * op2 |
|
/= |
op1 /= op2 |
op1 = op1 / op2 |
|
%= |
op1 %= op2 |
op1 = op1 % op2 |
|
&= |
op1 &= op2 |
op1 = op1 & op2 |
|
|= |
op1 |= op2 |
op1 = op1 | op2 |
|
^= |
op1 ^= op2 |
op1 = op1 ^ op2 |
|
<<= |
op1 <<= op2 |
op1 = op1 << op2 |
|
>>= |
op1 >>= op2 |
op1 = op1 >> op2 |
|
>>>= |
op1 >>>= op2 |
op1 = op1 >>> op2 |
Ex: The next program gives another example of
mathematical operators in Java. Just copy, paste to a file named MathOps.java,
compile and run it.
// Demonstrates the mathematical
operators.
import java.util.*;
public class MathOps {
// Create a shorthand to
save typing:
static void prt(String s) {
System.out.println(s);
}
// shorthand to print a
string and an int:
static void pInt(String s,
int i) {
prt(s + "
= " + i);
}
// shorthand to print a
string and a float:
static void pFlt(String s,
float f) {
prt(s + "
= " + f);
}
public static void
main(String[] args) {
// Create a
random number generator,
// seeds with
current time by default:
Random rand =
new Random();
int i, j, k;
// '%' limits
maximum value to 99:
j = rand.nextInt()
% 100;
k =
rand.nextInt() % 100;
pInt("j",j); pInt("k",k);
i = j + k;
pInt("j + k", i);
i = j - k;
pInt("j - k", i);
i = k / j;
pInt("k / j", i);
i = k * j;
pInt("k * j", i);
i = k % j;
pInt("k % j", i);
j %= k;
pInt("j %= k", j);
//
Floating-point number tests:
float
u,v,w; // applies to doubles, too
v =
rand.nextFloat();
w =
rand.nextFloat();
pFlt("v", v); pFlt("w", w);
u = v + w;
pFlt("v + w", u);
u = v - w;
pFlt("v - w", u);
u = v * w;
pFlt("v * w", u);
u = v / w;
pFlt("v / w", u);
// the
following also works for
// char, byte,
short, int, long,
// and double:
u += v;
pFlt("u += v", u);
u -= v;
pFlt("u -= v", u);
u *= v;
pFlt("u *= v", u);
u /= v;
pFlt("u /= v", u);
}
}
In one run of this code I've got
j = -51
k = 64
j + k = 13
j - k = -115
k / j = -1
k * j = -3264
k % j = 13
j %= k = -51
v = 0.92248344
w = 0.43780828
v + w = 1.3602917
v - w = 0.48467517
v * w = 0.40387088
v / w = 2.107049
u += v = 3.0295324
u -= v = 2.107049
u *= v = 1.9437178
u /= v = 2.107049
Operator
Use
Description
?:
op1 ? op2 : op3
If op1 is true, returns op2. Otherwise, returns op3.
[]
type []
Declares an array of unknown length, which contains type elements.
[]
type[ op2 ]
Creates and array with op1 elements. Must be used with the new operator.
[]
op1[ op2 ]
Accesses the element at op2 index within the array op1. Indices begin at 0 and extend through the length of the array minus one.
.
op1.op2
Is a reference to the op2 member of op1.
()
op1(params)
Declares or calls the method named op1 with the specified parameters. The list of parameters can be an empty list. The list is comma-separated.
(type)
(type) op1
Casts (converts) op1 to type. An exception will be thrown if the type of op1 is incompatible with type.
new
new op1
Creates a new object or array. op1 is either a call to a constructor, or an array specification.
instanceof
op1 instanceof op2
Returns true if op1 is an instance of op2
· The word cast is used in the sense of
?casting into a mold.? Java will automatically change one type of data into
another when appropriate. For instance, if you assign an integral value to a
floating-point variable, the compiler will automatically convert the int
to a float. Casting allows you to make this type conversion explicit, or
to force it when it wouldn?t normally happen. To perform a cast, put the
desired data type (including all modifiers) inside parentheses to the left of
any value.
Ex:
void casts() {
int i = 200;
long l = (long)i;
long l2 = (long)200;
}
As you can see, it?s possible to perform a cast on a numeric value as well as
on a variable. In both casts shown here, however, the cast is superfluous,
since the compiler will automatically promote an int value to a long
when necessary. However, you are allowed to use superfluous casts in to make a
point or to make your code more clear. In other situations, a cast may be
essential just to get the code to compile. This happens when you perform a
so-called narrowing conversion (that is, when you go from a data type
that can hold more information to one that doesn?t hold as much) and you run
the risk of losing information. Here the compiler forces you to do a cast, in
effect saying ?this can be a dangerous thing to do-if you want me to do it
anyway you must make the cast explicit.? With a widening conversion an
explicit cast is not needed because the new type will more than hold the
information from the old type so that no information is ever lost.
· Java allows you to cast any primitive type to
any other primitive type, except for boolean, which doesn?t allow any casting
at all. Class types do not allow casting. To convert one to the other there
must be special methods. Ex:
// What happens when you cast a
float
// or double to an integral value?
public class CastingNumbers {
public static void
main(String[] args) {
double
above = 0.7,
below = 0.4;
System.out.println("above: " + above);
System.out.println("below: " + below);
System.out.println(
"(int)above: " + (int)above);
System.out.println(
"(int)below: " + (int)below);
System.out.println(
"(char)('a' + above): " +
(char)('a' + above));
System.out.println(
"(char)('a' + below): " +
(char)('a' + below));
}
}
The output is:
above: 0.7
below: 0.4
(int)above: 0
(int)below: 0
(char)('a' + above): a
(char)('a' + below): a
So, we see that casting from a float or double to an integral
value always truncates.
· Hexadecimal (base 16), which works with all the integral data types, is denoted by a leading 0x or 0X followed by 0-9 and a-f either in upper or lowercase. If you try to initialize a variable with a value bigger than it can hold (regardless of the numerical form of the value), the compiler will give you an error message. Notice in the code below the maximum possible hexadecimal values for char, byte, and short. If you exceed these, the compiler will automatically make the value an int and tell you that you need a narrowing cast for the assignment.
· Octal (base 8) is denoted by a leading zero in the number and digits from 0-7. There is no literal representation for binary numbers in C, C++ or Java.
· A trailing character after a literal value establishes its type. Upper or lowercase L means long, upper or lowercase F means float and upper or lowercase D means double.
· Exponents use a notation: 1.39
e-47f. It means 1.39 x 10-47.
Ex:
char c = 0xffff; // max
char hex value
byte b = 0x7f; // max byte
hex value
short s = 0x7fff; // max
short hex value
int i1 = 0x2f; //
Hexadecimal (lowercase)
int i2 = 0X2F; //
Hexadecimal (uppercase)
int i3 = 0177; // Octal
(leading zero)
// Hex and Oct also work
with long.
long n1 = 200L; // long
suffix
long n2 = 200l; // long
suffix
long n3 = 200;
//! long l6(200); // not
allowed
float f1 = 1;
float f2 = 1F; // float
suffix
float f3 = 1f; // float
suffix
float f4 = 1e-45f; // 10 to
the power
float f5 = 1e+9f; // float
suffix
double d1 = 1d; // double
suffix
double d2 = 1D; // double
suffix
double d3 = 47e47d; // 10
to the power
Note that you don't need to use the trailing character when the compiler can
figure out the appropriate type. With
long n3 = 200;
there's no ambiguity, so an L after the 200 would be superfluous.
However, with
float f4 = 1e-47f; // 10 to the
power
the compiler normally takes exponential numbers as doubles, so without the
trailing f it will give you an error telling you that you must use
a cast to convert double to float.
· Use the while statement to loop over a
block of statements while a boolean expression remains true.
while (boolean expression)
{
statements
}
Here's a simple example that generates random numbers until a particular
condition is met. Try it yourself.
// Demonstrates the while loop.
public class WhileTest {
public static void
main(String[] args) {
double r = 0;
while(r <
0.99d) {
r =
Math.random();
System.out.println(r);
}
}
}
· Use the do-while statement to loop
over a block of statements while a boolean expression remains true.
do {
statement
} while (expression);
Ex: Add all integers from 0 through 9
int i = 0;
int j = 0;
do {
j += i;
} while (i <= 9);
· The for statement loops over a block
of statements and includes an initialization statement, a termination condition
statement, and an increment statement.
for (initialization ; termination
; increment) {
statements
}
for loops are usually used for ?counting? tasks:
// Demonstrates "for"
loop by listing
// all the ASCII characters.
public class ListCharacters {
public static void
main(String[] args) {
for( char c = 0; c < 128;
c++)
if (c != 26
) // ANSI Clear screen
System.out.println(
"value: " + (int)c +
" character: " + c);
}
}
· Note that the variable c is defined at the point where it is used, inside the control expression of the for loop, rather than at the beginning of the block denoted by the open curly brace. The scope of c is the expression controlled by the for.
· Traditional procedural languages like C
require that all variables be defined at the beginning of a block so when the
compiler creates a block it can allocate space for those variables. In Java and
C++ you can spread your variable declarations throughout the block, defining
them at the point that you need them. This allows a more natural coding style
and makes code easier to understand.
You can define multiple variables within a for statement, but they must
be of the same type:
for(int i = 0, j = 1;
i < 10
&& j != 11;
i++, j++)
/* body of for loop */;
The int definition in the for statement covers both i and j. The ability
to define variables in the control expression is limited to the for
loop. You cannot use this approach with any of the other selection or iteration
statements.
· A basic if
statement whose single statement block is executed if the boolean
expression is true.
if (boolean expression) {
statements
}
Ex:
int Ten = 0;
if (Ten != 10) {
System.out.println(Ten + "is not Ten");
Ten += 1;
}
System.out.println("All
right." + Ten + "is equal to 10.");
· An if
statement with one boolean expression and two statement blocks. The first
statement block is executed if the boolean expression is true and the second is
executed if the boolean expression is false.
if (boolean expression) {
statements
} else {
statements
}
· An if statement with multiple
expressions and an else statement.
if (boolean expression) {
statements
} else if (boolean expression)
{
statements
} else {
statements
}
Ex: A program to assign a grade based on the value of a test score: an A for a
score of 90% or above, a B for a score of 80% or above, and so on:
public class IfElseDemo {
public static
void main(String[] args) {
int testscore = 76;
char grade;
if (testscore >= 90) {
grade = 'A';
} else if (testscore >= 80) {
grade = 'B';
} else if (testscore >= 70) {
grade = 'C';
} else if (testscore >= 60) {
grade = 'D';
} else {
grade = 'F';
}
System.out.println("Grade = " + grade);
}
}
This code yields the output
Grade = C
· The switch
statement evaluates and integer expression and executes the appropriate case statement.
switch (integer expression)
{
case integer
expression:
statements
break;
...
default:
statements
break;
}
Here's an example that creates letters randomly and determines whether they're
vowels or consonants. Try it on your machine.
// Demonstrates the switch
statement.
public class VowelsAndConsonants {
public static void
main(String[] args) {
for(int i = 0;
i < 100; i++) {
char c = (char)(Math.random() * 26 + 'a');
System.out.print(c + ": ");
switch(c) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
System.out.println("vowel");
break;
case 'y':
case 'w':
System.out.println(
"Sometimes a vowel");
break;
default:
System.out.println("consonant");
}
}
}
}
Since Math.random( ) generates a value between 0 and 1, you need only
multiply it by the upper bound of the range of numbers you want to produce (26
for the letters in the alphabet) and add an offset to establish the lower
bound.
· Java has no goto statement
· The continue statement and labeled
blocks. Ex:
test: for (int i = fromIndex; i + max1
<= max2; i++) {
if (charAt(i) == c0) {
for (int k = 1; k < max1; k++) {
if (charAt(i+k) != str.charAt(k)) {
continue test;
}
} /* end of inner for loop */
}
}
/* end of outer for loop */
Here, the continue test statement is inside a for loop
nested inside another for loop. By referencing the label test, the
continue statement passes control to the outer for statement.
· Here is a demonstration of labeled break
and continue statements with while loops:
// Java's "labeled
while" loop.
public class LabeledWhile {
public static void
main(String[] args) {
int i = 0;
outer:
while(true) {
prt("Outer while loop");
while(true) {
i++;
prt("i = " + i);
if(i == 1) {
prt("continue");
continue;
}
if(i == 3) {
prt("continue outer");
continue outer;
}
if(i == 5) {
prt("break");
break;
}
if(i == 7) {
prt("break outer");
break outer;
}
}
}
}
static void prt(String s) {
System.out.println(s);
}
}
The output is
Outer while loop
i = 1
continue
i = 2
i = 3
continue outer
Outer while loop
i = 4
i = 5
break
Outer while loop
i = 6
i = 7
break outer
· The goal of exception handling is to
be able to define the regular flow of the program in part of the code without
worrying about all the special cases. Then, in a separate block of code, you
cover the exceptional cases. This produces more legible code since you
don't need to interrupt the flow of the algorithm to check and respond to every
possible strange condition. The runtime environment is responsible for moving
from the regular program flow to the exception handlers when an exceptional
condition arises.
In practice what you do is write blocks of code that may generate exceptions
inside try-catch blocks. You try the statements that generate the
exceptions. Within your try block you are free to act as if nothing has
or can go wrong.
throw exception;
Then, within one or more catch blocks, you write the program logic that
deals with all the special cases.
try {
statements
} catch (exceptiontype name)
{
statements
} catch (exceptiontype name)
{
statements
} finally {
statements
}
· Here's an example of exception handling in
Java using the Hello World program above:
// This is the Hello program in
Java
class ExceptionalHello {
public static
void main (String args[]) {
/*
Now let's say hello */
try
{
System.out.println("Hello " + args[0]);
}
catch (Exception e) {
System.out.println("Hello whoever you are");
}
}
}
· Some exceptions need to be caught and dealt with while others are generally considered to be so horrific that the runtime system, just gives up. The compiler will complain if you write code that doesn't watch out for the not-too-dangerous exceptions, but you'll need to watch out for the really dangerous ones (like ArrayIndexOutOfBoundsExceptions) yourself.
· Many times there may not be much you can do. Bad exceptions stop the program by default. This is at least preferable to unhandled exceptions in most programming languages where the entire system can come crashing down around your feet with a core dump or worse. Sometimes you may want to this too. In that case you can call the System.exit(int) method to halt your running program.
· Other times you may just break out of a loop you were in and continue with the rest of your code. This is most common when an exception isn't really unexpected or when it doesn't badly affect your program logic.
· You may or may not print an error message. If
you write an exception handler and you don't expect it to be called, then by
all means put a
System.out.println("Error:
" + e);
in your exception handler. That way if something does go wrong (and something
always does) you'll at least know where it went wrong. However don't put an
error message in exception handlers you expect to be exercised in the course of
normal program flow. Remember, they're exceptions, not errors.
· The HelloWorld class contains one method, the main method. As in C the main method is where an application begins executing. The method is declared public meaning that the method can be called from anywhere. It is declared static meaning that all instances of this class share this one method. (Don't worry. We'll come back to these definitions later.) It is declared void which means, as in C, that this method does not return a value. Finally we pass any command line arguments to the method in an array of Strings called args. In this simple program there aren't any command line arguments though.
· Finally when the main method is called it does exactly one thing: print "Hello World" to the standard output, generally a terminal monitor or console window of some sort. This is accomplished by the System.out.println method. To be more precise this is accomplished by calling the println() method of the static out field belonging to the System class; but for now we'll just treat this as one method.
· Here we show a simple example of use of
command line arguments. args is a special array that holds the command
line arguments. args[0] holds the first command line argument. args[1]
holds the second command line argument, args[2] holds the third command
line argument and so on. Ex:
public class Echo {
public static void main (String[] args) {
for (int i = 0; i < args.length; i++)
System.out.println(args[i]);
}
}
If we enter
java Echo Drink Hot Java
at runtime (Runtime is when we type java File.
Compile time is when we type javac File.java),
we get:
Drink
Hot
Java
· Arrays is a collection of objects with a
given data type, primitive, or not. One declares an array by using the []
operator. To allocate memory one uses the new operator and enters the number of
objects inside []. Ex: An array of Point objects.
Point myPoints[]; //
declaration
myPoints = new
Point[10]; // allocation of memory
for (int i =
0; i < 10; i++) { // real allocation of objects
myPoints[i] = new Point();
}
· The length()accessor method: One way
to avoid errors in accessing the number of elements of an array is to use the
array's built-in length member. Ex:
howMany
= myPoints.length(); // assign the value 10 to the howMany variable
Ex: This example shows a safe way of using the maximum
index of an array.
float[] squares = new float[101];
for (int i=0, i <
squares.length; i++) {
squares[i] = i*i;
}
· To initialize arrays one may use some
shortcuts. Ex:
int[] k = new int[3];
float[] yt = new float[7];
int[] k = {1, 2, 3};
float[] yt = {0.0f, 1.2f,
3.4f, -9.87f, 65.4f, 0.0f, 567.9f};
· A practical example: let's consider a class
that counts the occurrences of the digits 0-9 in decimal expansion of the
number pi, for example. We will do this by creating an array of ten
longs called ndigit. The zeroth element of ndigit will track the number
of zeroes in the input stream; the first element of ndigit will track the
numbers of 1's and so on. We'll test Java's random number generator and see if
it produces apparently random numbers.
import java.util.*;
class RandomTest {
public static void main
(String args[]) {
int[] ndigits =
new int[10];
double x;
int n;
Random myRandom
= new Random();
// Initialize
the array
for (int i = 0;
i < 10; i++) {
ndigits[i] = 0;
}
// Test the
random number generator a whole lot
for (long i=0;
i < 100000; i++) {
//
generate a new random number between 0 and 9
x =
myRandom.nextDouble() * 10.0;
n =
(int) x;
//count the digits in the random number
ndigits[n]++;
}
// Print the
results
for (int i = 0;
i <= 9; i++) {
System.out.println(i+": " + ndigits[i]);
}
}
}
In a run in my machine, I've got
0: 9872
1: 9850
2: 10205
3: 9995
4: 10081
5: 9977
6: 9919
7: 10088
8: 10034
9: 9979
· Two dimensional arrays: Consider the array below.
|
|
||||
|
M[0][0] |
M[0][1] |
M[0][2] |
M[0][3] |
M[0][4] |
|
M[1][0] |
M[1][1] |
M[1][2] |
M[1][3] |
M[1][4] |
|
M[2][0] |
M[2][1] |
M[2][2] |
M[2][3] |
M[2][4] |
|
M[3][0] |
M[3][1] |
M[3][2] |
M[3][3] |
M[3][4] |
Here's some code that would create and fill such an array:
class FillArray {
public static void main
(String args[]) {
int[][] M;
M = new
int[4][5];
for (int row=0;
row < 4; row++) {
for
(int col=0; col < 5; col++) {
M[row][col] = row+col;
}
}
}
}
· Similar construction can be used for arrays of larger dimensions.
· Ex: We declare and initialize a
string like this:
String hello = "Hello
world!"; // instantiates an object of the String class and
// initializes it with a character string containing
// the character representation of "Hello world!".
· The method System.out.println allows
to perform String concatenation: Ex: To print There are
23 characters in the file, we use
int num = 23;
System.out.println("There are " + num + " characters in the
file.");
· The length()accessor method yields the number of characters in the string.
· In code that does any significant input or
output you'll want to begin by importing all the various java.io
classes. import java.io.*; does this and is as ubiquitous
in Java applications as #include <stdio.h> is in C programs. Ex:
import java.io.*;
class PersonalHello {
public static void main
(String args[])
{
byte name[] = new byte[100]; // array of bytes that will hold the
user's name.
int
nr_read = 0;
System.out.println("What is your name?"); // query requesting the
user's name.
try
{
// read the user's name. This method takes a byte array as an
// argument, and places whatever the user types in that byte array.
nr_read = System.in.read(name);
System.out.print("Hello ");
System.out.write(name,0,nr_read); // print the user's name.
}
catch (IOException e) {
System.out.print("I'm Sorry. I didn't catch your name.");
}
}
}
In this code System.in.read() won't read past the end of the array even
though we didn't explicitly check to make sure the input was sufficiently
small. It internally checks the length of the array it's been passed using the name.length
property. Test this program yourself.
· Reading numbers: a lot of times you'll
want to ask the user for a number as input. All user input comes in as strings
so we need to convert the string into a
number. Ex: We write a getNextInteger() method that will
accept an integer from the user:
static int getNextInteger() {
String line;
DataInputStream
in = new DataInputStream(System.in);
try {
line = in.readLine();
int
i = Integer.valueOf(line).intValue();
return
i;
}
catch
(Exception e) {
return -1;
}
} // getNextInteger ends
here
· Sometimes you may need to read text and
numbers on the same line. For this purpose Java provides the StreamTokenizer
class.
1 - Open a FileOutputStream using a line like
FileOutputStream fout = new
FileOutputStream("test.out");
This line initializes the FileOutputStream with
the name of the file you want to write into.
2 - Convert the FileOutputStream into a PrintStream
using a statement like
PrintStream myOutput = new
PrintStream(fout);
The PrintStream is passed the FileOutputStream
from step 1.
3 - Instead of using System.out.println() use myOutput.println().
System.out and myOutput are just different instances of the PrintStream
class.
To print to a different PrintStream we keep the syntax
the same but change the name of the PrintStream.
· Ex: The program below makes the
Fahrenheit to Celsius conversion and writes the output to a file:
// Write the Fahrenheit to Celsius
table in a file
import java.io.*;
class FahrToCelsius {
public static void main
(String args[]) {
double fahr,
celsius;
double lower,
upper, step;
lower =
0.0; // lower limit of temperature table
upper =
300.0; // upper limit of temperature table
step =
20.0; // step size
fahr = lower;
try {
FileOutputStream fout = new FileOutputStream("test.out");
//
now to the FileOutputStream into a PrintStream
PrintStream myOutput = new PrintStream(fout);
while (fahr <= upper) { // while loop begins here
celsius = 5.0 * (fahr-32.0) / 9.0;
myOutput.println(fahr + " " + celsius);
fahr = fahr + step;
}
// while loop ends here
} // try
ends here
catch
(IOException e) {
System.out.println("Error: " + e);
System.exit(1);
}
} // main ends here
}
The test.out file will contain the output
0.0 -17.77777777777778
20.0 -6.666666666666667
40.0 4.444444444444445
60.0 15.555555555555555
80.0 26.666666666666668
100.0 37.77777777777778
120.0 48.888888888888886
140.0 60.0
160.0 71.11111111111111
180.0 82.22222222222223
200.0 93.33333333333333
220.0 104.44444444444444
240.0 115.55555555555556
260.0 126.66666666666667
280.0 137.77777777777777
300.0 148.88888888888889
· Ex: Reading a text file. He is
a code which imitates the Unix cat utility that reads and prints out text
files.
// Imitate the Unix cat utility
import java.io.*;
class cat {
public static void main
(String args[]) {
String thisLine;
//Loop across the arguments
for (int i=0; i <
args.length; i++) {
//Open the file for reading
try {
FileInputStream
fin = new FileInputStream(args[i]);
// now turn the
FileInputStream into a DataInputStream
try {
DataInputStream myInput = new DataInputStream(fin);
try
{
while ((thisLine = myInput.readLine()) != null) { // while loop begins
here
System.out.println(thisLine);
} // while loop ends here
}
catch (Exception e) {
System.out.println("Error: " + e);
}
} // end try
catch
(Exception e) {
System.out.println("Error: " + e);
}
} // end try
catch (Exception e) {
System.out.println("failed to open file " + args[i]);
System.out.println("Error: " + e);
}
} // for end here
} // main ends here
}
· Java has no pointers. Also, the Java run-time system checks all array indexing to ensure indices are within the bounds of the array.
· The sole reason for the existence of the garbage collector is to recover memory that your program is no longer using.
· In C++ if an object is created as a local (i.e., on the stack-not possible in Java), then the destruction happens at the closing curly brace of the scope in which the object was created. If the object was created using new (like in Java) the destructor is called when the programmer calls the C++ operator delete (which doesn?t exist in Java). If the C++ programmer forgets to call delete, the destructor is never called and you have a memory leak, plus the other parts of the object never get cleaned up. This kind of bug can be very difficult to track down.
· In contrast, Java doesn?t allow you to create
local objects-you must always use new. But in Java, there?s no
?delete? to call to release the object since the garbage collector releases the
storage for you. The Java memory manager keeps track of references to objects.
When an object has no more references, the object is a candidate for garbage
collection. Ex:
class
ReverseString {
public static String reverseIt(String source) {
int i, len = source.length();
StringBuffer dest = new StringBuffer(len);
for (i = (len - 1); i >= 0; i--) {
dest.appendChar(source.charAt(i));
}
return dest.toString();
}
}
The variable dest is used as a temporary object
reference during the execution of the reverseIt method. When dest goes
out of scope (the reverseIt method returns), the reference to that
object has gone away and it's then a candidate for garbage collection.
· Java has no preprocessor, no #define and related capabilities, no typedef, and absent those features, no longer any need for header files. Instead of header files, Java language source files provide the definitions of other classes and their methods. The import statement in Java is similar to the #include statement in C or C++. It pulls in the classes that are contained in a package elsewhere. A package is merely a collection of related classes.
· Java has no structures or unions
as complex data types, only classes.
Ex: (1) A class called Point:
class Point extends Object {
double x;
double y;
. . . // methods to access the instance
variables
}
(2) A class called Rectangle, that uses objects of the Point
class as instance variables.
class Rectangle extends Object {
Point lowerLeft;
Point upperRight;
. . . // methods to access the instance
variables
}
· No more Functions: anything you can do
with a function you can do just as well by defining a class and creating methods
for that class.
Ex: (1) Consider the Point class from above. We've added
public methods to set and access the instance variables:
class Point extends Object
{
double x;
double y;
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
public double x() {
return x;
}
public double y() {
return y;
}
}
(2) Here's how you'd use objects of the Point class from
within, say, an object of the Rectangle class:
class Rectangle
extends Object {
Point lowerLeft;
Point upperRight;
public void setEmptyRect() {
lowerLeft.setX(0.0);
lowerLeft.setY(0.0);
upperRight.setX(0.0);
upperRight.setY(0.0);
}
}
· Java has no Multiple Inheritance. The desirable features of multiple inheritance are provided by interfaces. An interface is not a definition of an object. Rather, it's a definition of a set of methods that one or more objects will implement. An important issue of interfaces is that they declare only methods and constants. No variables may be defined in interfaces.
· Java has no goto statement.
· Java has no Operator Overloading. The effects of operator overloading can be just as easily achieved by declaring a class, appropriate instance variables, and appropriate methods to manipulate those variables.
· Java has no Automatic Coercions.
Ex:
int myInt;
double myFloat =
3.14159;
myInt =
myFloat; // Java compiler error! Lost of precision.
myInt =
(int)myFloat; // OK. Cast into Integer.
· The single biggest difference between Java and C or C++ is that Java's memory model eliminates the possibility of overwriting memory and corrupting data. Instead of pointer arithmetic, Java has true arrays and strings, which means that the interpreter can check array and string indexes. In addition, a programmer can't write code that turns an arbitrary integer into an object reference by casting.
· The Java compiler is written in Java. The
Java run-time system is written in ANSI C.
· In the programming implementation of an object, its state is defined by its instance variables.
· An object's behavior is defined by its methods. Each calculation part of a program is called a method. Methods are logically the same as C's functions, Pascal's procedures and functions, and Fortran's functions and subroutines.
· The return statement sends a value back to the calling method. The type of this value must match the declared type of the method as in C.
· In general any line that looks like Text(arg1, arg2) or text(arg1) or text() is a method call.
· Scope: A variable's scope is the block of code within which the variable is accessible and determines when the variable is created and destroyed.
· A class is a software construct that defines
the instance variables and methods of an object. A class in and of itself is
not an object. A class is a template that defines how an object will
look and behave when the object is created or instantiated from the
specification declared by the class.
Ex:
class Point extends Object {
public double x; /* instance variable */
public double y; /* instance variable */
}
· Both syntactically and logically everything in Java happens inside a class. Methods are defined inside the classes they belong to. This may be a little confusing to C++ programmers who are used to defining all but the simplest methods outside the class block. Even basic data primitives like integers often need to be incorporated into classes before you can do many useful things with them. The class is the fundamental unit of Java programs, not source code files like in C.
· Ex: Consider the Java program:
class HelloWorld {
public static void main
(String args[]) {
System.out.println("Hello
World");
}
}
class GoodbyeWorld {
public static void main
(String args[]) {
System.out.println("Goodbye Cruel World!");
}
}
Save this code in a single file
called hellogoodbye.java in your directory, and compile it with the
command javac hellogoodbye.java. Then list the contents of the
directory. You will see that the compiler has produced two separate class
files, HelloWorld.class and GoodbyeWorld.class.
The second class is a completely independent program. Type java GoodbyeWorld
and then type java HelloWorld. These programs run and execute
independently of each other although they exist in the same source code file.
· The primary distinguishing feature of OOP
(Object Oriented Programming) languages is the class. A class is a data
structure that can associate the methods which act on an object with the object
itself. In pre-OOP languages methods and data were separate. In OOP languages
they are all part of classes. Programming languages provide a number of simple
data types like int, float and String. However very often the data you want to
work with may not be simple ints, floats or Strings. Classes let programmers
define their own more complicated data types. For instance let's suppose your
program needs to keep a database of web sites. For each site you have a name, a
URL, and a description. In traditional programming languages you'd have three
different String variables for each web site. With a class you combine these
into one package like so:
class website {
String name;
String url;
String description;
}
These variables (name, url and description) are called the members of
the class. They tell you what a class is and what its properties are.
· In our web site database we will have many thousands of websites. Each specific web site is an object. The definition of a web site though, which we gave above, is a class. This is a very important distinction. A class defines what an object is, but it is not itself an object. An object is a specific instance of a class. Thus when we create a new object we say we are instantiating the object. Each class exists only once in a program, but there can be many thousands of objects that are instances of that class.
To instantiate an object in Java we use the new operator. Here's how we'd create a
new web site:
website x = new
website();
Once we've got a website we want to know something about it. To get at the
member variables of the website we can use the .
operator. Website has three member variables, name, url
and description, so x has three member variables as well, x.name,
x.url and x.description. We can use these just like we'd use any
other String variables. For instance:
website x = new website();
x.name =
"Cafe Au Lait";
x.url =
"http://metalab.unc.edu/javafaq/";
x.description =
"Really cool!";
System.out.println(x.name + " at " + x.url + " is " +
x.description);
· The print() method is completely enclosed within the website class. All methods in Java must belong to a class. Unlike C++ programs, Java programs cannot have a method hanging around in global space that does everything you forgot to do in your classes.
· For the class Point defined above we
would use
Point myPoint;
// declares a variable to refer to a Point object
myPoint = new
Point(); // allocates an instance of a Point object
and access the variables of this Point object:
myPoint.x
= 10.0;
myPoint.y = 25.7;
This works because the instance variables of Point were declared public
in the class declaration, otherwise the Point class declaration would need to
provide accessor methods to set and get its variables.
· Methods with the same name as the class as in
the code fragment are called constructors. The first method most classes
need is a constructor. A constructor creates a new instance of
the class. It initializes all the variables and does any work necessary to
prepare the class to be used.
Ex:
class Point
extends Object {
public double x; /* instance variable */
public double y; /* instance variable */
Point() { /* constructor to
initialize to default zero value */
x = 0.0;
y = 0.0;
}
/* constructor to initialize to specific value */
Point(double x, double y) {
this.x = x; /* set instance variables to passed
parameters */
this.y = y;
}
}
· Use of Point:
Point lowerLeft;
Point
upperRight;
lowerLeft = new
Point(); /* initialize to
default zero value */
upperRight =
new Point(100.0, 200.0); /* initialize to non- zero */
· Often, constructors are necessary.
Ex:
class Rectangle
extends Object {
private Point lowerLeft;
private Point upperRight;
Rectangle() {
lowerLeft = new Point();
upperRight = new Point();
}
. . .
instance methods appear in here
. . .
}
In this example, the Rectangle() constructor is vitally necessary
to ensure that the two Point objects are instantiated at the time a Rectangle
object is instantiated, otherwise, the Rectangle object would
subsequently try to reference points that have not yet been
allocated, and would fail.
· To learn more from examples, lets turn
to our website class. In the line website
x = new website(); website() is a constructor. If no
constructor exists Java provides a default one, but it's better to make sure
you have your own. You make a constructor by writing a public method
that has the same name as the class. Thus our website constructor is called website().
Here's a revised website class with a constructor that initializes all the
members to null Strings.
class website {
String name;
String url;
String description;
public website() {
name =
"";
url =
"";
description =
"";
}
}
Better yet, we should create a constructor that accepts three Strings as
arguments and uses those to initialize the member variables like so:
class website {
String name;
String url;
String description;
public website(String n,
String u, String d) {
name = n;
url = u;
description =
d;
}
}
We'd use this like so:
website x = new
website("Cafe Au Lait", "http://metalab.unc.edu/javafaq/",
"Really cool!");
x.print();
This fits in well with the goal of keeping code relevant to the proper
functioning of a class within the class.
However what if sometimes when we want to create a web site we know the URL,
name, and description, and sometimes we don't? Best of all, let's use both!
class website {
String name;
String url;
String description;
public website(String n,
String u, String d) {
name = n;
url = u;
description =
d;
}
public website() {
name =
"";
url =
"";
description =
"";
}
}
This is called method overloading or polymorphism. Polymorphism
is a feature of object oriented languages that lets one name refer to different
methods depending on context. The important context is typically the number and
type of arguments to the method. In this case we use the first version of the
method if we have three String arguments and the second version if we don't
have any arguments. If you have one or two or four String arguments to the
constructor, or arguments that aren't Strings, then the compiler generates an
error because it doesn't have a method whose signature matches the
requested method call.
· The static keyword. When you say
something is static, it means that data or method is not tied to any particular
object instance of that class. So even if you?ve never created an object of
that class you can call a static method or access a piece of static data. With
ordinary, non-static data and methods you must create an object and use that
object to access the data or method, since non-static data and methods must
know the particular object they are working with. Of course, since static
methods don?t need any objects to be created before they are used, they cannot
directly access non-static members or methods by simply calling those other
members without referring to a named object (since non-static members and
methods must be tied to a particular object).
Ex:
class StaticTest {
static int i =
47;
}
Now even if you make two StaticTest objects, there will still be only one piece
of storage for StaticTest.i. Both objects will share the same i. Consider:
StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();
At this point, both st1.i and st2.i have the same value of 47
since they refer to the same piece of memory.
· There are two ways to refer to a static
variable. As indicated above, you can name it via an object, by saying, for
example, st2.i. You can also refer to it directly through its class
name, something you cannot do with a non-static member. (This is the preferred
way to refer to a static variable since it emphasizes that variable?s static
nature.)
StaticTest.i++;
The ++ operator increments the variable. At this point, both st1.i and st2.i
will have the value 48.
· Similar logic applies to static methods.
You can refer to a static method either through an object as you can with any
method, or with the special additional syntax ClassName.method( ). Ex:
class StaticFun {
static void incr() {
StaticTest.i++; }
}
You can see that the StaticFun method incr( ) increments the
static data i. You can call incr( ) in the typical way, through an
object:
StaticFun sf = new StaticFun();
sf.incr();
Or, because incr( ) is a static method, you can call it directly through
its class:
StaticFun.incr();
· While static, when applied to a data member, definitely changes the way the data is created (one for each class vs. the non-static one for each object), when applied to a method it?s not so dramatic. An important use of static for methods is to allow you to call that method without creating an object. This is essential, as we will see, in defining the main( ) method that is the entry point for running an application.
· The final keyword. You can declare a
variable in any scope to be final. The value of a final variable cannot
change after it has been initialized. Ex:
final int aFinalVar = 0;
A final variable is like a constant (const) variable in C.
· A field that is both static and final has only one piece of storage that cannot be changed.
·
With a primitive data type, final
makes the value a constant, but with an object reference, final makes the
reference a constant. Once the reference is initialized to an object, it can
never be changed to point to another object. However, the object itself can be
modified; Java does not provide a way to make any arbitrary object a constant.
(You can, however, write your class so that objects have the effect of being
constant.) This restriction includes arrays, which are also objects.
Ex:
//: FinalData.java
// The effect of final on fields.
class Value {
int i = 1;
}
public class FinalData {
// Can be compile-time
constants
final int i1 = 9;
static final int VAL_TWO =
99;
// Typical public constant:
public static final int
VAL_THREE = 39;
// Cannot be compile-time
constants:
final int i4 =
(int)(Math.random()*20);
static final int i5 =
(int)(Math.random()*20);
Value v1 = new Value();
final Value v2 = new
Value();
static final Value v3 = new
Value();
// Arrays:
final int[] a = { 1, 2, 3,
4, 5, 6 };
public void print(String
id) {
System.out.println(
id
+ ": " + "i4 = " + i4 +
", i5 = " + i5);
}
public static void
main(String[] args) {
FinalData fd1 =
new FinalData();
//! fd1.i1++;
// Error: can't change value
fd1.v2.i++; //
Object isn't constant!
fd1.v1 = new
Value(); // OK -- not final
for(int i = 0;
i < fd1.a.length; i++)
fd1.a[i]++; // Object isn't constant!
//! fd1.v2 =
new Value(); // Error: Can't
//! fd1.v3 =
new Value(); // change reference
//! fd1.a = new
int[3];
fd1.print("fd1");
System.out.println("Creating new FinalData");
FinalData fd2 =
new FinalData();
fd1.print("fd1");
fd2.print("fd2");
}
}
· Since i1 and VAL_TWO are final primitives with compile-time values, they can both be used as compile-time constants and are not different in any important way. VAL_THREE is the more typical way you?ll see such constants defined: public so they?re usable outside the package, static to emphasize that there?s only one, and final to say that it?s a constant. Note that final static primitives with constant initial values (that is, compile-time constants) are named with all capitals by convention, with words separated by underscores (This is just like C constants, which is where the convention originated.) Also note that i5 cannot be known at compile-time, so it is not capitalized.
· Just because something is final doesn?t mean
that its value is known at compile-time. This is demonstrated by initializing i4
and i5 at run-time using randomly generated numbers. This portion of the
example also shows the difference between making a final value static or
non-static. This difference shows up only when the values are initialized at
run-time, since the compile-time values are treated the same by the compiler.
(And presumably optimized out of existence.) The difference is shown in the
output from one run:
fd1: i4 = 15, i5 = 9
Creating new FinalData
fd1: i4 = 15, i5 = 9
fd2: i4 = 10, i5 = 9
Note that the values of i4 for fd1 and fd2 are unique, but
the value for i5 is not changed by creating the second FinalData object.
That?s because it?s static and is initialized once upon loading and not each
time a new object is created.
· this refers to the object you're "in" right now. In the two-parameter Point method, this.x means the x instance variable of this object, rather than the x parameter to the Point method.
· If you have two objects of the same type
called a and b, you might wonder how it is that you can call a
method f( ) for both those objects:
class Banana { void f(int i) { /*
... */ } }
Banana a = new Banana(), b = new
Banana();
a.f(1);
b.f(2);
If there's only one method called f( ), how can that method know whether it's
being called for the object a or b?
To allow you to write the code in a convenient object-oriented syntax in which
you ?send a message to an object,? the compiler does some undercover work for
you. There's a secret first argument passed to the method f( ), and that
argument is the reference to the object that's being manipulated. So the two
method calls above become something like:
Banana.f(a,1);
Banana.f(b,2);
This is internal and you can't write these expressions and get the compiler to
accept them, but it gives you an idea of what's happening.
Suppose you're inside a method and you'd like to get the reference to the
current object. Since that reference is passed secretly by the compiler,
there's no identifier for it. However, for this purpose there's a keyword: this.
The this keyword-which can be used only inside a method-produces the
reference to the object the method has been called for. You can treat this reference
just like any other object reference. Keep in mind that if you're calling a
method of your class from within another method of your class, you don't need
to use this; you simply call the method. The current this
reference is automatically used for the other method. Thus you can say:
class Apricot {
void pick() { /* ... */ }
void pit() { pick(); /* ...
*/ }
}
Inside pit( ), you could say this.pick( ) but there's no need to.
The compiler does it for you automatically. The this keyword is used only
for those special cases in which you need to explicitly use the reference to
the current object. For example, it's often used in return statements
when you want to return the reference to the current object:
// Leaf.java
// Simple use of the
"this" keyword
public class Leaf {
int i = 0;
Leaf increment() {
i++;
return this;
}
void print() {
System.out.println("i = " + i);
}
public static void
main(String[] args) {
Leaf x = new
Leaf();
x.increment().increment().increment().print();
}
}
Because increment( ) returns the reference to the current object via the
this keyword, multiple operations can easily be performed on the same object.
· public methods and instance variables are available to any other class anywhere.
· protected means that instance variables and methods so designated are accessible only to subclasses of that class, and nowhere else.
· private methods and instance variables
are accessible only from within the class in which they're declared--they're
not available even to their subclasses.
Ex:
class Point extends Object {
private double x; /* instance variable */
private double y; /* instance variable */
Point() { /* constructor to initialize to zero */
x = 0.0;
y = 0.0;
}
/* constructor to initialize to specific value */
Point(double x, double y) {
this.x = x;
this.y = y;
}
public void setX(double x) { /* accessor method
*/
this.x = x;
}
public void setY(double y) { /* accessor method
*/
this.y = y;
}
public double getX() { /* accessor method */
return x;
}
public double getY() { /* accessor method */
return y;
}
}
Point
myPoint; //
declares a variable to refer to a Point object
myPoint = new
Point(); // allocates an instance of a Point object
myPoint.setX(10.0); // sets the x variable
via the accessor method
myPoint.setY(25.7);
· By making instance variables public, you are exposing some of the details of the implementation of the class, thereby providing higher efficiency and conciseness of expression at the possible expense of hindering future maintenance efforts. By hiding details of the internal implementation of a class, you have the potential to change the implementation of the class in the future without breaking any code that uses that class.
· Print methods are common in some languages
but most Java programs operate differently. You can use System.out.println() to print any
object. However for good results your class should have a toString() method that formats the
objects data in a sensible way and returns a String. Here's how we'd implement
it in the website
example:
public class ClassTest {
public static void
main(String args[]) {
website x = new
website("Cafe Au Lait", "http://metalab.unc.edu/javafaq/",
"Really cool!");
System.out.println(x);
}
}
class website {
String name;
String url;
String description;
public website(String n,
String u, String d) {
name = n;
url = u;
description =
d;
}
public website() {
name =
"";
url =
"";
description =
"";
}
public String toString() {
return
(name + " at " + url + " is " + description);
}
}
· Optional. Illustration of a finalize
method in a class.
/**
*
Close the stream when garbage is collected.
*/
protected void
finalize() {
try {
file.close();
} catch (Exception e) {
}
}
In this case, the 'finalize' method merely closes an I/O file stream
that was used by the object, to ensure that the file descriptor for the
stream is closed.
· Mechanism by which new and enhanced objects
can be defined in terms of existing objects.
Ex: a zebra is a horse with stripes. Thus, in Java,
class Zebra extends Horse {
. .
. Your new instance variables and new methods go here
}
Zebra is said to be a derived class -- it's derived from Horse,
which is called a base class.
· Another example:
class
Point extends Object {
protected double x; /* instance variable */
protected double y; /* instance variable */
Point() { /* constructor to initialize to zero */
x = 0.0;
y = 0.0;
}
}
class
ThreePoint extends Point {
protected double z; /* the z coordinate of the
point */
ThreePoint() { /* default constructor
*/
x = 0.0; /* initialize the
coordinates */
y = 0.0;
z = 0.0;
}
ThreePoint(double x, double y, double z) {/* specific constructor */
this.x = x; /* initialize the
coordinates */
this.y = y;
this.z = z;
}
}
The x and y instance variables are inherited from the original Point
class, so there's no need to declare them in ThreePoint. However, notice
we had to make Point's instance variables protected instead of private as in
the previous examples. Had we left Point's instance variables private,
even its subclasses would be unable to access them, and the compilation would
fail.
· Normally, variables you declare in a class definition are instance variables -- there is one of those variables in every separate object that's created (instantiated) from the class. A class variable, on the other hand, is local to the class itself -- there's only a single copy of the variable and it's shared by every object you instantiate from the class.
· Final variables are variables to be
kept constant. Ex:
final int aFinalVar = 0;
final int blankfinal;
. . .
blankfinal = 0;
· To declare class variables and class methods,
you declare them as static. Ex:
class
Rectangle extends Object {
static final int version = 2;
static final int revision = 0;
}
· Every instance of Rectangle that you create from this class will share these same variables. Notice they're also defined as final because you want them to be constants. Static methods have only one instance per class rather than one instance per object. All objects of the same class share a single copy of a static method. By default methods are not static.
· Recursive Methods. Ex: Calculating
the factorial of a number
public static long factorial (int n) {
if (n == 0) {
return 1;
}
else {
return n*factorial(n-1);
}
}
· With the this keyword in mind, you can more fully understand what it means to make a method static. It means that there is no this for that particular method. You cannot call non-static methods from inside static methods (although the reverse is possible), and you can call a static method for the class itself, without any object. In fact, that's primarily what a static method is for. It is as if you're creating the equivalent of a global function (from C). Except global functions are not permitted in Java, and putting the static method inside a class allows it access to other static methods and to static fields.
· An abstract superclass is a class in
which you define methods that aren't actually implemented by that class--they
only provide place-holders such that subsequent subclasses must override those
methods and supply their actual implementation.
Ex:
abstract class Graphic
extends Object {
protected Point
lowerLeft;
// lower left of bounding box
protected Point
upperRight;
// upper right of bounding box
. . .
more instance variables
. . .
public void
setPosition(Point ll, Point ur) {
lowerLeft = ll;
upperRight = ur;
}
abstract void
drawMyself(); // abstract method
}
}
· You can't instantiate the Graphical class, because it's declared abstract. You can only instantiate a subclass of it.
· Example of use for a Rectangle Object:
abstract class Rectangle
extends Graphical {
void
drawMySelf() { // really
does the drawing
moveTo(lowerLeft.x, lowerLeft.y);
lineTo(upperRight.x, lowerLeft.y);
lineTo(upperRight.x, upperRight.y)
lineTo(lowerLeft.x, upperRight.y);
. . .
and so on and so on
. . .
}
}
· Unfortunately no popular computer language
other than Fortran provides them as a built-in data type. It was recently added
to the C++ as a standard library. Let's see how we might implement them in
Java.
Since they are complex numbers it's not unlikely that we'll need to add
them, subtract them, multiply them and divide them. We'll also want to be able
to access their real and imaginary parts as well as their absolute values and
arguments. The following class does all that.
//
public class Complex extends
Object {
private double u;
private double v;
Complex (double x, double
y) {
u=x;
v=y;
}
public double Real () {
return u;
}
public double Imaginary ()
{
return v;
}
public double Magnitude ()
{
return Math.sqrt(u*u
+ v*v);
}
public double Arg () {
return
Math.atan2(u, v);
}
// Add z to w; i.e. w += z
public Complex Plus
(Complex z) {
return new
Complex(u + z.u, v + z.v);
}
// Subtract z from w
public Complex Minus (Complex
z) {
return new
Complex(u - z.u, v - z.v);
}
public Complex Times
(Complex z) {
return new
Complex(u*z.u - v*z.v, u*z.v + v*z.u);
}
// divide w by z
public Complex DivideBy
(Complex z) {
double rz =
z.Magnitude();
return new
Complex((u * z.u + v * z.v)/(rz*rz),(v * z.u - u * z.v)/(rz*rz));
}
}
Notice especially that x and y are now private. They cannot be accessed by
external code even if we want them to be.
The use of one of these methods will look like the following. Add the following
ComplexExamples class to the Complex.java file and compile. Then run
ComplexExamples in the usual way by typing java
ComplexExamples.
//Complex Arithmetic Examples
class ComplexExamples {
public static void main
(String args[]) {
Complex u, v,
w, z;
u = new
Complex(1,2);
System.out.println("u: " + u.Real() + " + " + u.Imaginary()
+ "i");
v = new
Complex(3,-4.5);
System.out.println("v: " + v.Real() + " + " + v.Imaginary()
+ "i");
// Add u + v;
z=u.Plus(v);
System.out.println("u + v: "+ z.Real() + " + " +
z.Imaginary() + "i");
// Add v + u;
z=v.Plus(u);
System.out.println("v + u: "+ z.Real() + " + " +
z.Imaginary() + "i");
z=u.Minus(v);
System.out.println("u - v: "+ z.Real() + " + " +
z.Imaginary() + "i");
z=v.Minus(u);
System.out.println("v - u: "+ z.Real() + " + " +
z.Imaginary() + "i");
z=u.Times(v);
System.out.println("u * v: "+ z.Real() + " + " +
z.Imaginary() + "i");
z=v.Times(u);
System.out.println("v
* u: "+ z.Real() + " + " + z.Imaginary() + "i");
z=u.DivideBy(v);
System.out.println("u / v: "+ z.Real() + " + " +
z.Imaginary() + "i");
z=v.DivideBy(u);
System.out.println("v / u: "+ z.Real() + " + " +
z.Imaginary() + "i");
}
}
· Here we agree on one thing. In C++ one can perform operator overloading and get the complex objects manipulated with the usual operators (+, -, /, *, =, etc.).
· Our printing in the last program was quite
stilted because we needed to break a complex number into its real and imaginary
parts, print them, and then put it all back together again. Wouldn't it be nice
if we could just write:
System.out.println(u);
instead? It turns out we can. All objects have a toString method which
is inherited from the Object class. However the default toString()
method isn't very useful so we want to override it with one of our own
creation that handles the conversion to complex numbers. Add the following
method to the Complex class:
public String toString() {
if (v >= 0)
return (String.valueOf(u) + " + " + String.valueOf(v) +
"i");
else return
(String.valueOf(u) + " - " + String.valueOf(-v) + "i");
}
You should also modify the ComplexExamples class as follows:
class ComplexExamples {
public static void main
(String args[]) {
Complex u, v,
z;
u = new
Complex(1,2);
System.out.println("u: " + u);
v = new
Complex(3,-4.5);
System.out.println("v: " + v);
// Add u + v;
z=u.Plus(v);
System.out.println("u
+ v: " + z);
// Add v + u;
z=v.Plus(u);
System.out.println("v + u: " + z);
z=u.Minus(v);
System.out.println("u - v: " + z);
z=v.Minus(u);
System.out.println("v - u: " + z);
z=u.Times(v);
System.out.println("u
* v: " + z);
z=v.Times(u);
System.out.println("v * u: "+ z);
z=u.DivideBy(v);
System.out.println("u / v: " + z);
z=v.DivideBy(u);
System.out.println("v / u: " + z);
}
}
That's about an order of magnitude easier to understand and to write.
· So far our methods just do arithmetic on two
complex numbers. It's not uncommon to want to multiply a complex number by a
real number. To add this capability to our class we'll add the following
method:
public Complex Times (double x) {
return new
Complex(u*x, v*x);
}
Here's a simple test program for your new method:
class RealComplex {
public static void main
(String args[]) {
Complex v, z;
double x = 5.1;
v = new
Complex(3,-4.5);
System.out.println("v: " + v);
System.out.println("x: " + x);
z=v.Times(x);
System.out.println("v * x: " + z);
}
}
· The astute among you may be saying to hold on
here, we've redefined the Times method. Now how can we multiply two complex
numbers? However there's really no problem. The compiler notices that the
arguments of the two methods named Times (not the same as the arguments of the
two complex numbers but unfortunately the terminology fails us here) are different.
One multiplies two complex numbers, the other multiplies a real number and a
complex number. The compiler is smart enough to figure out which version of
Times to use when. This is called method overloading or polymorphism.
In some object-oriented languages like C++ you can not only overload methods
but even operators like + and =. However while this makes numeric classes like
complex numbers easier to work with, it tends to lead to unreadable and
unmaintainable code for non-numeric classes. Therefore Java's designers elected
not to add this feature to the language.
· Until now we've stored almost every program in a single file. This becomes unwieldy as programs grow large. It becomes impossible to manage when more than one person is working on a program. It also loses out on one of the key benefits of OOP, code reusability. As long as all the code for a program is stored in one file, you can't reuse code except by cut and paste, just like in a non-object oriented language.
· There has been some code that hasn't resided in our source files. Remember all those import statements at the top of every file? What they do is pull in prewritten and precompiled code from various locations so we can use it in our programs. You can do the same thing with classes you write. However to do this you do need to be aware of several conventions and restrictions.
You can split the example of the previous sections into two
separate files, each of which contains one class. Begin by creating a file that
contains the Complex class. Save this file as Complex.java. Next save
the examples from the previous exercises in a separate file called ComplexExamples.java
in the same directory as Complex.java. Now compile both files and run Complex
Examples.java.
· Threads are an essential keystone of Java.
The Java library provides a Thread class that supports a rich collection of
methods to start a thread, run a thread, stop a thread, and check on a thread's
status. Ex:
public class SimpleThread extends Thread {
public
SimpleThread(String str) {
// Calls a
superclass constructor. Used only to set the Thread's name.
super(str);
}
public void
run() { // Run method. The heart of any Thread.
for (int i = 0; i < 10; i++) {
// Get thread's name
System.out.println(i + " " + getName());
try {
// Sleeps for a
random interval of up to 1 second.
sleep((long)(Math.random() * 1000));
} catch (InterruptedException e) {}
}
System.out.println("DONE! " + getName());
}
}
public class TwoThreadsTest
{
public static void main (String[] args) {
new SimpleThread("Jamaica").start();
new SimpleThread("Fiji").start();
}
}
· Methods within a class that are declared
synchronized do not run concurrently. Ex:
public synchronized void
stop() {
if (kicker !=
null) {
kicker.stop();
kicker = null;
}
}
private synchronized void
startSort() {
if (kicker = =
null || !kicker.isAlive()) {
kicker = new Thread(this);
kicker.start();
}
}
The stop and startSort methods are declared to be synchronized --
they can't run concurrently, enabling them to maintain consistent state in the
shared kicker variable. When a synchronized method is entered, it acquires a
monitor on the current object. The monitor precludes any other synchronized
methods in that object from running. When a synchronized method returns by any
means, its monitor is released. Other synchronized methods within the same
object are now free to run.
· If you wish your objects to be thread-safe, any methods that may change the values of instance variables should be declared synchronized.
· Ex:
import
java.applet.Applet; // import packages from basic release.
import
java.awt.Graphics; // awt stands for "advanced
window toolkit" or "applet window toolkit".
public class HelloWorldApplet
extends Applet { // extends indicates that this class is a subclass of
the Applet class
public void paint(Graphics
g) {
g.drawString("Hello world!", 50, 25);
}
}
Compile this file by typing javac HelloWorldApplet.java at the command line
prompt. If all is well a file called HelloWorldApplet.class will be created.
You will need an HTML code (ex: HelloWorldApplet.HTML) with a single line, like
that:
<APPLET
codebase="classes" code="HelloWorldApplet.class" width=200
height=200 ></APPLET>
Now you can see the applet either using a web browser and opening the HTML
file, ot by typing appletviewer
HelloWorldApplet.HTML in a DOS or UNIX window. The
height and width are in pixels.
· In the code one sees that there's no main method! Applets don't need them. The main method is actually in the browser or the AppletViewer, not in the Applet itself.
· Rather than starting at a specific place in
the code applets are event driven. An applet waits for one of a series
of events such as a key press, the mouse pointer
being moved over the applets visible area, or a mouse click and then executes
the appropriate event handler. Since this is our first program we only have one
event
handler, paint.
· The paint method is passed a Graphics
object which we've chosen to call g. The Graphics class is defined in
the java.awt.Graphics package which we've imported.
Within the paint method we call g's drawString method to draw the string
"Hello World!" at the coordinates (50,25). That's 50 pixels across
and twenty-five pixels
down from the upper left hand corner of the applet.
· The CODE parameter tells the browser where to
look for the compiled .class file.
ALIGN also works exactly as for images (in those browsers that support ALIGN)
defining how the applet's rectangle is placed on the page relative to other
elements. Possible values include LEFT, RIGHT, TOP, TEXTTOP, MIDDLE, ABSMIDDLE,
BASELINE, BOTTOM and ABSBOTTOM. Finally you can also specify an HSPACE and a
VSPACE in pixels to set the amount of blank space between an applet and the
surrounding text.
· The area between the opening and closing
APPLET tag is also used to pass parameters to applets. This is done through the
use of the PARAM HTML tag and the getParameter method of the
java.applet.Applet class. Ex:
import java.applet.Applet;
import java.awt.Graphics;
public class DrawStringApplet
extends Applet {
String input_from_page;
public void init() {
input_from_page
= getParameter("String");
}
public void paint(Graphics
g) {
g.drawString(input_from_page, 50, 25);
}
}
Now you need to create an HTML file that will include your applet. The
following simple HTML file will do:
<HTML>
<HEAD>
<TITLE> Draw String
</TITLE>
</HEAD>
<BODY>
This is the applet:<P>
<APPLET
codebase="classes" code="DrawStringApplet.class" width=200
height=200>
<PARAM name="String"
value="Hello World!"></APPLET>
</BODY>
</HTML>
This allows you to change the output of the applet without changing or
recompiling the code. All parameters are passed as Strings. If you want to get
something else like an integer then you'll need to pass it as a String and
convert it into the type you really want.
· The PARAM HTML tag has two parameters of its own, NAME and VALUE. The NAME identifies which parameter this is for the getParameter method. VALUE is the value of the parameter as a String. Both must be enclosed in double quote marks like all other HTML tag parameters.
· Whenever an event occurs, the applet below
responds by printing the name of the event at the command line.
import java.applet.Applet;
import java.awt.*;
public class EventTutor extends
Applet {
public void init() {
System.out.println("init event");
}
public void paint(Graphics
g) {
System.out.println("paint event");
}
public void start() {
System.out.println("start event");
}
public void destroy() {
System.out.println("destroy event");
}
public void update(Graphics
g) {
System.out.println("update event");
}
public boolean
mouseUp(Event e, int x, int y) {
System.out.println("mouseUp event");
return false;
}
public boolean
mouseDown(Event e, int x, int y) {
System.out.println("mouseDown");
return false;
}
public boolean
mouseDrag(Event e, int x, int y) {
System.out.println("mouseDrag event");
return false;
}
public boolean
mouseMove(Event e, int x, int y) {
System.out.println("mouseMove event");
return false;
}
public boolean
mouseEnter(Event e, int x, int y) {
System.out.println("mouseEnter event");
return false;
}
public boolean
mouseExit(Event e, int x, int y) {
System.out.println("mouseExit event");
return false;
}
public void getFocus() {
System.out.println("getFocus event");
}
public void gotFocus() {
System.out.println("gotFocus event");
}
public void lostFocus() {
System.out.println("lostFocus event");
}
public boolean
keyDown(Event e, int x) {
System.out.println("keyDown event");
return true;
}
}
Once you've compiled and loaded this applet play with it. Click the mouse in
the applet window. Double-click the mouse. Click and drag. Type some text.
Resize the browser window. Cover it and then uncover it. Keep your eye on the
standard output (Java console in Netscape) while doing this.
· This time we need more than one class from the awt package so rather than worrying about which one to import, we just get them all with the import java.awt.*. The compiler is smart enough to only link in those that it actually uses.
· It is extremely bad form to use System.out.println() in an applet. For
better layout one can use a List. A List is a scrolling list of
Strings defined in java.awt.List. We create a new List with new, just as
we create any other Object. The specific constructor we use asks for an int
that's the number of visible lines and a boolean that tells whether or not multiple
selections are allowed. We'll ask for 25 lines and no multiple selections.
List theList;
theList = new List(25,
false);
We add Strings to the list by using the addItem method of the List we're
adding to like so:
theList.addItem("This is a list
item");
Finally we need to actually add this List to our applet (more precisely the
applet's container). We do this with the line
add(theList);
in the init method. That's all. We can use the same applet we used
before with these simple changes.
import java.applet.Applet;
import java.awt.*;
public class EventList extends
Applet {
List theList;
public void init() {
theList = new
List(25, false);
add(theList);
theList.addItem("init event");
}
public void paint(Graphics
g) {
theList.addItem("paint event");
}
public void start() {
theList.addItem("start event");
}
public void destroy() {
theList.addItem("destroy event");
}
public void update(Graphics
g) {
theList.addItem("update event");
}
public boolean
mouseUp(Event e, int x, int y) {
theList.addItem("mouseUp event");
return false;
}
public boolean
mouseDown(Event e, int x, int y) {
theList.addItem("mouseDown");
return false;
}
public boolean
mouseDrag(Event e, int x, int y) {
theList.addItem("mouseDrag event");
return false;
}
public boolean
mouseMove(Event e, int x, int y) {
theList.addItem("mouseMove event");
return false;
}
public boolean mouseEnter(Event
e, int x, int y) {
theList.addItem("mouseEnter event");
return false;
}
public boolean
mouseExit(Event e, int x, int y) {
theList.addItem("mouseExit event");
return false;
}
public void getFocus() {
theList.addItem("getFocus
event");
}
public void gotFocus() {
theList.addItem("gotFocus event");
}
public void lostFocus() {
theList.addItem("lostFocus event");
}
public boolean
keyDown(Event e, int x) {
theList.addItem("keyDown event");
return true;
}
}
· List of methods:
- init
The init() method is called when
your applet begins executing. Generally you use this method to set up any data
structures or perform any tasks you need to get ready to run the applet.
public void init() {
System.out.println("init event");
}
- paint
We've already seen the paint()
method. Almost any applet is going to need to override this method. This is the
method in which you will do all your drawing. You can only write to the applet
screen in the paint method. However there are times when you'll want to write
to an offscreen image in another method and then just quickly copy that image
to the screen in your paint()
method.
public void paint(Graphics
g) {
theList.addItem("paint
event");
}
- stop
A stop() message says the
user is no longer looking at the page that contains the applet. This is usually
because the user left the page or minimized the window. At this time you should
stop any CPU eating activities that don't matter when the user isn't looking at
your page. Once the user returns to the page the start() method is called.
public void stop() {
theList.addItem("stop event");
}
- start
The start() method is called when a user brings their attention back to
an applet, for instance after maximizing a window or returning to the applet's
page. It is called after the init() method. Initialization code that needs to
be performed every time an applet is restarted should be put here.
public void start() {
theList.addItem("start event");
}
- destroy
The destroy() method is called before the applet is unloaded completely.
It is called after the stop() method. Users may reload the applet later, but if
they do it will be as if they've never seen it before. All variables, static,
member, local or otherwise will be initialized to their initial state. If you
have any final cleanup to do (for instance sending output back to the httpd
server) do it here.
public void destroy() {
theList.addItem("destroy event");
}
- update
The update() method is
called automatically by the system. It's often overridden when you want to use
offscreen Images to avoid flicker.
public void update(Graphics
g) {
theList.addItem("update event");
}
- mouseUp
The mouseUp() method is
called whenever the mouse button is released in your applet. In most
cases this is the event you'll want to watch out for, not mouseDown. A button
is typically highlighted when the mouse button is pressed on it, but it is not
activated till the user releases the mouse button. This gives the user a chance
to change their mind by moving the cursor off the object without releasing it.
The exception would be when you want an action to continue as long as the mouse
button is held down, a fast forward button on a movie playing applet for
instance.
mouseUp() methods also receive
the coordinates of the point where the mouse was released.
public boolean mouseUp(Event
e, int x, int y) {
theList.addItem("mouseUp event at (" + x + "," + y +
")");
return false;
}
- mouseDown
The mouseDown() method
is called whenever the mouse button is pressed in your applet. In most
cases you'll want to wait for a mouseUp before taking any action though.
MouseDown() methods also receive
the coordinates of the point where the mouse was released.
public boolean
mouseDown(Event e, int x, int y) {
theList.addItem("mouseDown event at (" + x + "," + y +
")");
return false;
}
- mouseDrag
mouseDrag() methods occur
when a user moves the mouse while holding down the mouse button. mouseDrag() methods receive the
coordinates of the point where the mouse is when the event occurs.
public boolean
mouseDrag(Event e, int x, int y) {
theList.addItem("mouseDrag event at (" + x + "," + y +
")");
return false;
}
- mouseMove
mouseMove methods occur when a user moves the mouse without holding down
the mouse button. mouseMove methods receive the coordinates of the point where
the mouse is when the event occurs.
public boolean
mouseMove(Event e, int x, int y) {
theList.addItem("mouseMove event at (" + x + "," + y +
")");
return false;
}
- mouseEnter
Your applet receives a mouseEnter event whenever the cursor enters your
applet from somewhere else. You'll also receive the coordinates of the point at
which the cursor entered your applet. After this happens its typically followed
by a Stream of mouseMoved events as the cursor continues through the applet so
it can be hard to see.
public boolean
mouseEnter(Event e, int x, int y) {
theList.addItem("mouseEnter event at " + x + "," + y +
")");
return false;
}
- mouseExit
Your applet receives a mouseExit event whenever the cursor leaves your
applet. You'll also receive the coordinates of the point at which the cursor
exited your applet.
public boolean mouseExit(Event e,
int x, int y) {
theList.addItem("mouseExit event at (" + x + "," + y +
")");
return false;
}
- getFocus
public void getFocus() {
theList.addItem("getFocus event");
}
- gotFocus
public void gotFocus() { theList.addItem("gotFocus
event"); }
- lostFocus
public void lostFocus() {
theList.addItem("lostFocus event");
}
- keyDown
A keydown event is generated whenever the user presses a key while your
applet is active. An integer keycode is returned indicating which key was
pressed. As a general rule you'll want to cast this to a char to get the actual
letter.
public boolean keyDown(Event
e, int x) {
theList.addItem("The " + (char) x + " key was pressed.");
return false;
}
· Next we'll write an applet that fills the
screen with lots of randomly sized and positioned rectangles in the style of
Piet Mondrian. We'll do this by parts.
In the first applet we'll just draw a rectangle on the screen. We'll get the
size of the applet as specified in the HTML file, and then we'll draw a
rectangle around the applet to frame it. Here's the code. Compile it and run
the applet.
//Draw a rectangle
import java.applet.*;
import java.awt.*;
public class Mondrian1 extends
Applet {
int height, width;
public void init() {
Dimension d =
size();
height =
d.height;
width =
d.width;
repaint();
}
public void paint(Graphics
g) {
g.drawRect(0,
0, width-1, height-1);
}
}
In Java the coordinate system for an applet begins in the upper left hand
corner and increases to the right and down. The upper left hand corner of the
applet starts at (0, 0), not at (1, 1). That is the reason for using width-1
and height-1 in g.drawRect. The line g.drawRect(0, 0, height-1, width-1) instructs the Graphics class
g to draw a rectangle beginning
at the point (0, 0) and ending at the point (299, 299).
· If we want to draw a filled rectangle
we use the fillRect method. Otherwise the syntax is identical. Here's the
code:
//Draw a rectangle
import java.applet.*;
import java.awt.*;
public class Mondrian2 extends
Applet {
int AppletHeight;
int AppletWidth;
int RectHeight;
int RectWidth;
int RectTop;
int RectLeft;
public void init() {
Dimension d =
size();
AppletHeight =
d.height;
AppletWidth =
d.width;
RectHeight =
AppletHeight/3;
RectWidth =
AppletWidth/3;
RectTop =
(AppletHeight - RectHeight)/2;
RectLeft=
(AppletWidth - RectWidth)/2;
repaint();
}
public void paint(Graphics
g) {
g.drawRect(0,
0, AppletWidth-1, AppletHeight-1);
g.fillRect(RectLeft, RectTop, RectWidth-1, RectHeight-1);
}
}
· Let's make life a little more exciting by
randomly selecting the position and size of the rectangle. To do this we'll
need the Math.random() method from java.lang.Math. This method
returns a double between 0.0 and 1.0 so we'll need to multiply the result by
the applet's height and width to get a reasonably sized rectangle that fits
into our applet space. To do this we'll create the following Randomize method:
private int Randomize( int range )
{
double rawResult;
rawResult = Math.random();
return (int) (rawResult *
range);
}
This method forces the result of Math.random into an int in the range we require. Pay special
attention to the last line. When you see a raw type in parentheses like (int) or (float)
it's a cast. Casts change one value type into another. Thus here we're changing
a double into an int. The cast rounds as necessary.
Here is the whole code.
//Draw a rectangle
import java.applet.Applet;
import java.awt.*;
public class Mondrian3 extends
Applet {
int RectHeight, RectWidth,
RectTop, RectLeft, AppletWidth, AppletHeight;
public void init() {
Dimension d =
size();
AppletHeight =
d.height;
AppletWidth =
d.width;
RectTop =
Randomize(AppletHeight);
RectLeft=
Randomize(AppletWidth);
RectHeight =
Randomize(AppletHeight - RectTop);
RectWidth =
Randomize(AppletWidth - RectLeft);
repaint();
}
public void paint(Graphics
g) {
g.drawRect(0,
0, AppletWidth-1, AppletHeight-1);
g.fillRect(RectLeft, RectTop, RectWidth-1, RectHeight-1);
}
private int Randomize(int
range)
{
double
rawResult;
rawResult =
Math.random();
return (int)
(rawResult * range);
}
}
Occasionally this applet does randomly produce a rectangle that's two small to
see so if you don't see anything, reload it. Reload it a few times. Each time
you'll see a rectangle of a different size appear in a different place.
Let's make our world a little more colorful. To do this we'll change the
rectangle color to red. To do this we'll use a new methods setColor(),
part of the Graphics class.
//Draw a colored rectangle
import java.applet.Applet;
import java.awt.*;
public class Mondrian4 extends
Applet {
int RectHeight, RectWidth,
RectTop, RectLeft, AppletWidth, AppletHeight;
public void init() {
Dimension d =
size();
AppletHeight =
d.height;
AppletWidth =
d.width;
RectTop =
Randomize(AppletHeight);
RectLeft=
Randomize(AppletWidth);
RectHeight =
Randomize(AppletHeight - RectTop);
RectWidth =
Randomize(AppletWidth - RectLeft);
repaint();
}
public void paint(Graphics
g) {
//
g.setBackground(Color.white);
g.setColor(Color.red);
g.drawRect(0,
0, AppletWidth-1, AppletHeight-1);
g.fillRect(RectLeft, RectTop, RectWidth-1, RectHeight-1);
}
private int Randomize(int
range)
{
double
rawResult;
rawResult =
Math.random();
return (int)
(rawResult * range);
}
}
· The awt predefines a number of colors including:
· If these aren't sufficient for your needs, you can define others using the same RGB triple that's used to set background colors on many web pages. You even get to use decimal numbers instead of the hex values you have to use for the bgcolor tag. For example to select a medium gray you'd use Color(127, 127, 127). Pure white would be Color(255, 255, 255). Pure red is (255, 0, 0) and so on.
· By using the color constructor we can expand
our program to select not only a random rectangle but also a random color for
the rectangle. Here's the code:
//Draw a randomly colored
rectangle
import java.applet.Applet;
import java.awt.*;
public class Mondrian5 extends
Applet {
int RectHeight, RectWidth,
RectTop, RectLeft, AppletWidth, AppletHeight;
Color RectColor;
public void init() {
Dimension d =
size();
AppletHeight =
d.height;
AppletWidth =
d.width;
RectTop =
Randomize(AppletHeight);
RectLeft=
Randomize(AppletWidth);
RectHeight =
Randomize(AppletHeight - RectTop);
RectWidth =
Randomize(AppletWidth - RectLeft);
RectColor = new
Color(Randomize(255),Randomize(255),Randomize(255));
repaint();
}
public void paint(Graphics
g) {
//
g.setBackground(Color.white);
g.setColor(RectColor);
g.drawRect(0,
0, AppletWidth-1, AppletHeight-1);
g.fillRect(RectLeft, RectTop, RectWidth-1, RectHeight-1);
}
private int Randomize(int
range)
{
double
rawResult;
rawResult =
Math.random();
return (int)
(rawResult * range);
}
}
In the next example we're going to draw multiple randomly sized, randomly
colored rectangles. Since we want each rectangle to be different we're going to
have to move the calculation of the rectangle's shape, position and color into
the paint() method. Here's the code:
//Draw many randomly colored
rectangles
import java.applet.Applet;
import java.awt.*;
public class Mondrian6 extends
Applet {
int RectHeight, RectWidth,
RectTop, RectLeft, AppletWidth, AppletHeight;
Color RectColor;
int numberRectangles = 100;
public void init() {
Dimension d =
size();
AppletHeight =
d.height;
AppletWidth =
d.width;
repaint();
}
public void paint(Graphics
g) {
g.setColor(Color.black);
g.drawRect(0,
0, AppletWidth-1, AppletHeight-1);
for (int i=0; i
< numberRectangles; i++) {
RectTop = Randomize(AppletHeight);
RectLeft= Randomize(AppletWidth);
RectHeight = Randomize(AppletHeight - RectTop);
RectWidth = Randomize(AppletWidth - RectLeft);
RectColor = new Color(Randomize(255),Randomize(255),Randomize(255));
g.setColor(RectColor);
g.fillRect(RectLeft, RectTop, RectWidth-1, RectHeight-1);
}
}
private int Randomize(int
range)
{
double
rawResult;
rawResult =
Math.random();
return (int)
(rawResult * range);
}
}
· Finally let's let the HTML specify the the
number of rectangles to be drawn in one pass. We'll keep the default value as
is and only replace it if the HTML includes a Number PARAM. In this case the
code above would have to be modified to include the lines just before the
repaint(); call
String s =
getParameter("Number");
if (s != null)
{
numberRectangles = Integer.valueOf(s).intValue();
}
· The awt contains several graphics
primitives. Rectangles are one. Lines are another. Within a graphics context
there is one key line drawing method, drawLine(int x1, int y1, int x2, int
y2). This method draws a straight line between the point (x1, y1)
and the point (x2, y2). Ex: Here's a simple applet that
draws a line diagonally across the applet frame:
import java.applet.Applet;
import java.awt.*;
public class SimpleLine extends
Applet {
int AppletHeight,
AppletWidth;
public void init() {
Dimension d =
size();
AppletHeight =
d.height;
AppletWidth =
d.width;
}
public void paint(Graphics
g) {
g.drawLine(0,
0, AppletWidth, AppletHeight);
}
}
· We're now going to demonstrate the use of the
drawLine() method to draw
considerably non-straight figures. We begin with the skeleton applet. We'll
need to add some code to the paint method of the applet to make it draw
something. Let's begin by drawing a sine wave from the left hand side of the
image to the right hand side. Here's the complete program:
import java.applet.*;
import java.awt.*;
public class GraphApplet extends
Applet {
int x0, xN, y0, yN;
public void init() {
// How big is
the applet?
Dimension d =
size();
x0 = 0;
xN = d.width-1;
y0=0;
yN=d.height-1;
}
public void paint(Graphics
g) {
for (int x =
x0; x < xN; x++) {
g.drawLine(x,(int) (yN*Math.sin(x)),x+1, (int) (yN*Math.sin(x+1)));
}
}
}
The for loop in the paint method loops across every x pixel of
the applet. At each one we calculate the sine of that pixel. We also calculate
the sine of the next pixel. This gives us two 2-D points and we draw a line
between them. Since the sine of a real number is always between one and
negative one, we scale the y value by yN. Finally we cast the y values to
ints since sines are fundamentally floating point values but drawLine
requires ints. One has other problems:
What we need is to separate the data from the display. We'll
need a method that will convert a point in the applet window into a point in
the Cartesian plane, and one that will convert it back. Here it is:
import java.applet.*;
import java.awt.*;
public class GraphApplet extends
Applet {
int x0, xN, y0, yN;
double xmin, xmax, ymin,
ymax;
int AppletHeight,
AppletWidth;
public void init() {
// How big is
the applet?
Dimension d =
size();
AppletHeight =
d.height;
AppletWidth = d.width;
x0 = 0;
xN =
AppletWidth-1;
y0=0;
yN=AppletHeight-1;
xmin = -10.0;
xmax =
10.0;
ymin = -1.0;
ymax =
1.0;
}
public void paint(Graphics
g) {
double
x1,y1,x2,y2;
int i, j1, j2;
j1 = yvalue(0);
for (i = 0; i
< AppletWidth; i++) {
j2
= yvalue(i+1);
g.drawLine(i, j1 ,i+1, j2);
j1
= j2;
}
}
private int yvalue(int
ivalue) {
// Given
the xpoint we're given calculate the Cartesian equivalent
double x, y;
int jvalue;
x = (ivalue *
(xmax - xmin)/(AppletWidth - 1)) + xmin;
// Take the
sine of that x
y =
Math.sin(x);
// Scale y into
window coordinates
jvalue = (int)
((y - ymin)*(AppletHeight - 1)/(ymax - ymin));
// Switch
jvalue from cartesian coordinates to computer graphics coordinates
jvalue =
AppletHeight - jvalue;
return jvalue;
}
}
· Run this applet. Isn't that a much nicer
looking sine wave? The following modification of the init and paint
methods looks for xmin, xmax, ymin, and ymax to be specified via
parameters. However for robustness if the author of the HTML forgets to specify
them we supply some reasonable default values.
import java.applet.*;
import java.awt.*;
public class GraphApplet extends
Applet {
int x0, xN, y0, yN;
double xmin, xmax, ymin,
ymax;
int AppletHeight,
AppletWidth;
public void init() {
String
ParamString;
// How big is
the applet?
Dimension d =
size();
AppletHeight =
d.height;
AppletWidth = d.width;
x0 = 0;
xN =
AppletWidth-1;
y0=0;
yN=AppletHeight-1;
ParamString =
getParameter("xmin");
if (ParamString
!= null) {
xmin = Double.valueOf(ParamString).doubleValue();
}
else {
xmin = -1.0;
}
ParamString =
getParameter("xmax");
if (ParamString
!= null) {
xmax = Double.valueOf(ParamString).doubleValue();
}
else {
xmax = 1.0;
}
ParamString =
getParameter("ymax");
if (ParamString
!= null) {
ymax = Double.valueOf(ParamString).doubleValue();
}
else {
ymax = 1.0;
}
ParamString =
getParameter("ymin");
if (ParamString
!= null) {
ymin = Double.valueOf(ParamString).doubleValue();
}
else {
ymin = -1.0;
}
}
public void paint(Graphics
g) {
double
x1,y1,x2,y2;
int i, j1, j2;
j1 = yvalue(0);
for (i = 0; i
< AppletWidth; i++) {
j2
= yvalue(i+1);
g.drawLine(i, j1 ,i+1, j2);
j1
= j2;
}
}
private int yvalue(int
ivalue) {
// Given
the xpoint we're given calculate the Cartesian equivalent
double x, y;
int jvalue;
x = (ivalue *
(xmax - xmin)/(AppletWidth - 1)) + xmin;
// Take the
sine of that x
y =
Math.sin(x);
// Scale y into
window coordinates
jvalue = (int) ((y
- ymin)*(AppletHeight - 1)/(ymax - ymin));
// Switch
jvalue from cartesian coordinates to computer graphics coordinates
jvalue =
AppletHeight - jvalue;
return jvalue;
}
}
Now we can adjust the range over which we graph without modifying our code!
· We're now going to use Java to implement some classic examples of fractal geometry.
· We begin with a one-dimensional set with an
infinite number of points that covers zero length. The middle third set is
defined by starting with all the real numbers between zero and one inclusive.
Then we cut out the middle third of that set (exclusive of the endpoints). i.e.
everything between one third and two thirds exclusive.
Next we cut the middle third of the two line segments that remain, i.e.
everything between one ninth and two ninths and between seven ninths and eight
ninths. We continue this process indefinitely. This Java program that draws
successive pictures to demonstrate the middle third set.
import java.applet.Applet;
import java.awt.*;
import java.util.Vector;
public class MiddleThird extends
Applet {
int AppletWidth;
int AppletHeight;
// The Vector class implements a growable array of
objects. Like an array,
// it contains components that can be accessed
using an integer index.
Vector endpoints = new Vector();
public void init() {
Dimension d =
size();
AppletHeight =
d.height;
AppletWidth =
d.width;
// addElement adds the specified component
to the end of this vector,
// increasing
its size by one.
endpoints.addElement(new Float(0.0f));
endpoints.addElement(new Float(1.0f));
}
public void paint(Graphics
g) {
float x1, x2;
Float tempFloat;
for (int i = 0; i
< AppletHeight; i+= 5) {
// draw
the lines
// size() returns the number of components
in this vector.
for (int
j=0; j < endpoints.size(); j += 2) {
// elementAt(j) returns the component at the
specified index.
tempFloat = (Float) endpoints.elementAt(j);
x1 = tempFloat.floatValue();
tempFloat = (Float) endpoints.elementAt(j+1);
x2 = tempFloat.floatValue();
g.drawLine( Math.round(x1*AppletWidth), i, Math.round(x2*AppletWidth), i);
}
//remove
the middle third of the lines
CutSegments();
// Now
check to see if we've exceeded the resolution of our screen
tempFloat
= (Float) endpoints.elementAt(0);
x1 =
tempFloat.floatValue();
tempFloat
= (Float) endpoints.elementAt(1);
x2 =
tempFloat.floatValue();
if
(Math.round(x1*AppletWidth) == Math.round(x2*AppletWidth)) break;
}
}
private void CutSegments() {
int index = 0;
float gap;
float x1, x2;
Float tempFloat1,
tempFloat2;
int stop =
endpoints.size();
for (int i=0; i <
stop; i+=2) {
CutMiddleThird(index, index+1);
index += 4;
}
}
private void
CutMiddleThird(int left, int right) {
float gap;
float x1, x2;
Float tempFloat1,
tempFloat2;
tempFloat1 = (Float)
endpoints.elementAt(left);
tempFloat2 = (Float)
endpoints.elementAt(right);
gap =
tempFloat2.floatValue() - tempFloat1.floatValue();
x1 = tempFloat1.floatValue()
+ gap/3.0f;
x2 =
tempFloat2.floatValue() - gap/3.0f;
endpoints.insertElementAt(new Float(x2), right);
endpoints.insertElementAt(new Float(x1), right);
}
}
The code above isn't a perfect representation of the middle third set since we
have to deal with points of finite size rather than with genuine mathematical
points. Depending on how large a window you give your applet, you will probably
only see about six to twelve iterations before we need to start working with
fractional pixels.
· Compile the following code, run it and then
look over the code to see if you can understand the algorithm.
//Bounce lines around in a box
import java.applet.Applet;
import java.awt.*;
public class FlyingLines extends Applet
{
int NUM_LINES = 25;
int gDeltaTop=3,
gDeltaBottom=3;
int gDeltaLeft=2,
gDeltaRight=6;
int AppletWidth,
AppletHeight;
int gLines[][] = new
int[NUM_LINES][4];
public void init() {
AppletWidth =
size().width;
AppletHeight =
size().height;
}
public void start() {
gLines[0][0] =
Randomize(AppletWidth);
gLines[0][1] =
Randomize(AppletHeight);
gLines[0][2] =
Randomize(AppletWidth);
gLines[0][3] =
Randomize(AppletHeight);
for (int i=1; i
< NUM_LINES; i++ ) {
LineCopy(i, i-1);
RecalcLine(i);
}
repaint();
}
public void paint(Graphics
g) {
while
(true) {
for
(int i=NUM_LINES - 1; i > 0; i--) {
LineCopy(i, i-1);
}
RecalcLine(0);
g.setColor(Color.black);
g.drawLine(gLines[0][0], gLines[0][1], gLines[0][2], gLines[0][3]);
g.setColor(getBackground());
g.drawLine(gLines[NUM_LINES-1][0], gLines[NUM_LINES-1][1],
gLines[NUM_LINES-1][2], gLines[NUM_LINES-1][3]);
}
}
private void LineCopy (int
to, int from) {
for (int i = 0;
i < 4; i++) {
gLines[to][i] = gLines[from][i];
}
}
public int Randomize( int
range ) {
double
rawResult;
rawResult =
Math.random();
return (int)
(rawResult * range);
}
private void RecalcLine(
int i ) {
gLines[i][1] +=
gDeltaTop;
if
((gLines[i][1] < 0) || (gLines[i][1] > AppletHeight)) {
gDeltaTop *= -1;
gLines[i][1] += 2*gDeltaTop;
}
gLines[i][3] +=
gDeltaBottom;
if (
(gLines[i][3] < 0) || (gLines[i][3] > AppletHeight) ) {
gDeltaBottom *= -1;
gLines[i][3] += 2*gDeltaBottom;
}
gLines[i][0] +=
gDeltaLeft;
if (
(gLines[i][0] < 0) || (gLines[i][0] > AppletWidth) ) {
gDeltaLeft *= -1;
gLines[i][0] += 2*gDeltaLeft;
}
gLines[i][2] +=
gDeltaRight;
if (
(gLines[i][2] < 0) || (gLines[i][2] > AppletWidth) ) {
gDeltaRight *= -1;
gLines[i][2] += 2*gDeltaRight;
}
} //RecalcLine ends
here
} // FlyingLines ends here
Depending on your operating system and Java-enabled browser
you may have noticed that the Mondrian and Flying Line programs
tended to hog your CPU. The paint loops in both Mondrian and FlyingLines
are ideal for a thread, a separate stream of execution that takes place
simultaneously and independently of everything else that might be happening.
Without threads an entire program can be held up by one CPU intensive task or,
as in Flying Lines, one infinite loop, intentional or otherwise.
Here's one way to do it.
//Draw infinitely many random
rectangles
import java.applet.Applet;
import java.awt.*;
public class ThreadedMondrian
extends Applet implements Runnable {
int RectHeight, RectWidth,
RectTop, RectLeft, AppletWidth, AppletHeight;
Color RectColor;
Thread kicker = null;
int pause;
public void init() {
Dimension d =
size();
AppletHeight =
d.height;
AppletWidth =
d.width;
repaint();
}
public void paint(Graphics
g) {
g.setColor(Color.black);
g.drawRect(0,
0, AppletWidth-1, AppletHeight-1);
for (int i=0; i
< 10; i++) {
RandomRect();
g.setColor(RectColor);
g.fillRect(RectLeft, RectTop, RectWidth-1, RectHeight-1);
}
}
public void run() {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
while (true)
{ // infinite loop
repaint();
try
{
Thread.sleep(100);
}
catch (Exception e) {
}
} // the VM
doesn't want us to sleep anymore,
} // so get
back to work
public void start() {
if (kicker ==
null) {
kicker = new Thread(this);
kicker.start();
}
}
public void stop() {
kicker = null;
}
public void
RandomRect() {
RectTop = Randomize(AppletHeight);
RectLeft = Randomize(AppletWidth);
RectHeight= Randomize(AppletHeight - RectTop);
RectWidth = Randomize(AppletWidth - RectLeft);
RectColor
= new Color(Randomize(255),Randomize(255),Randomize(255));
}
private int Randomize(int
range)
{
double
rawResult;
rawResult =
Math.random();
return (int)
(rawResult * range);
}
}
We added four key things to Mondrian to make it threaded and a lot more CPU
friendly.
· After the line kicker = new Thread(this); has been executed the ThreadedMondrian is in a New Thread state. When a thread is a New Thread, it is merely an empty Thread object; no system resources have been allocated for it yet. When a thread is in this state, you can only start the thread. The start method creates the system resources necessary to run the thread, schedules the thread to run, and calls the thread's run method.
· After the start method has returned, the thread is "running". Yet, many computers have a single processor, thus making it impossible to run all "running" threads at the same time. The Java runtime system must implement a scheduling scheme that shares the processor between all "running" threads. So at any given time, a "running" thread actually may be waiting for its turn in the CPU. Execution of multiple threads on a single CPU, in some order, is called scheduling. The Java runtime supports a very simple, deterministic scheduling algorithm known as fixed priority scheduling. This algorithm schedules threads based on their priority relative to other runnable threads.
· When a Java thread is created, it inherits its priority from the thread that created it. You can also modify a thread's priority at any time after its creation using the setPriority method. Thread priorities are integers ranging between MIN_PRIORITY and MAX_PRIORITY (constants defined in the Thread class). The higher the integer, the higher the priority. At any given time, when multiple threads are ready to be executed, the runtime system chooses the runnable thread with the highest priority for execution. Only when that thread stops, yields, or becomes not runnable for some reason will a lower priority thread start executing. If two threads of the same priority are waiting for the CPU, the scheduler chooses one of them to run in a round-robin fashion.
· ThreadedMondrian's run method runs forever (while(true) loop) until some action is taken and the applet is stopped. Within the loop, the applet repaints itself and then tells the thread to sleep for one second (1000 milliseconds). During the second that the ThreadedMondrian is asleep, the thread does not run, even if the processor becomes available. After the second has elapsed, the thread becomes Runnable again and, if the processor becomes available, the thread begins running again. An applet's repaint method ultimately calls the applet's paint method, which does the actual update of the applet's display area.
· The ThreadedMondrian paint method draws a rectangle around the applet and also draws random colored rectangles inside the applet area.
· A program doesn't stop a thread like it stops an applet (by calling a method). Rather, a thread arranges for its own death by having a run method that terminates naturally. The exit condition for this run method is the exit condition for the while loop because there is no code after the while loop while (true) {. This condition indicates that the loop will exit when the currently executing thread finds out that true is not true . When would this ever be the case?
· When you leave the page, the application in which the applet is running calls the applet's stop method. This method then sets the ThreadedMondrian to null, thereby telling the main loop in the run method to terminate.
· If you revisit the page, the start method is called again and ThreadedMondrian starts up again with a new thread.
· Here's a threaded version of Flying Lines.
//Bounce lines around in a box
import java.applet.Applet;
import java.awt.*;
public class FlyingLines extends
Applet implements Runnable {
int NUM_LINES = 25;
int gDeltaTop=3,
gDeltaBottom=3;
int gDeltaLeft=2,
gDeltaRight=6;
int AppletWidth,
AppletHeight;
int gLines[][] = new
int[NUM_LINES][4];
public void init() {
AppletWidth =
size().width;
AppletHeight =
size().height;
}
public void start() {
gLines[0][0] =
Randomize(AppletWidth);
gLines[0][1] =
Randomize(AppletHeight);
gLines[0][2] =
Randomize(AppletWidth);
gLines[0][3] =
Randomize(AppletHeight);
for (int i=1; i
< NUM_LINES; i++ ) {
LineCopy(i, i-1);
RecalcLine(i);
}
repaint();
Thread t = new
Thread(this);
t.start();
}
public void run () {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
while (true) {
for
(int i=NUM_LINES - 1; i > 0; i--) {
LineCopy(i, i-1);
}
RecalcLine(0);
System.out.println(gLines[0][0] + ", " + gLines[0][1] + ","
+ gLines[0][2] + ", " + gLines[0][3]);
repaint();
try
{
Thread.currentThread().sleep(10);
}
catch
(Exception e) {
}
}
}
public void paint(Graphics
g) {
for (int i=0; i
< NUM_LINES; i++) {
g.drawLine(gLines[i][0], gLines[i][1], gLines[i][2], gLines[i][3]);
}
}
private void LineCopy (int
to, int from) {
for (int i = 0;
i < 4; i++) {
gLines[to][i] = gLines[from][i];
}
}
public int Randomize( int
range ) {
double
rawResult;
rawResult =
Math.random();
return (int)
(rawResult * range);
}
private void RecalcLine(
int i ) {
gLines[i][1] +=
gDeltaTop;
if
((gLines[i][1] < 0) || (gLines[i][1] > AppletHeight)) {
gDeltaTop *= -1;
gLines[i][1] += 2*gDeltaTop;
}
gLines[i][3] +=
gDeltaBottom;
if (
(gLines[i][3] < 0) || (gLines[i][3] > AppletHeight) ) {
gDeltaBottom *= -1;
gLines[i][3] += 2*gDeltaBottom;
}
gLines[i][0] +=
gDeltaLeft;
if (
(gLines[i][0] < 0) || (gLines[i][0] > AppletWidth) ) {
gDeltaLeft *= -1;
gLines[i][0] += 2*gDeltaLeft;
}
gLines[i][2] +=
gDeltaRight;
if (
(gLines[i][2] < 0) || (gLines[i][2] > AppletWidth) ) {
gDeltaRight *= -1;
gLines[i][2] += 2*gDeltaRight;
}
} //RecalcLine ends
here
} // FlyingLines ends here
· Here's a simple applet that lets you doodle
with the mouse on an applet.
import java.applet.Applet;
import java.awt.*;
import java.util.Vector;
public class JavaDoodle extends
Applet {
Vector points = new
Vector();
public void paint(Graphics
g) {
int x1, y1, x2, y2;
Point tempPoint;
if
(points.size() > 1) {
tempPoint = (Point) points.elementAt(0);
x1 = tempPoint.x;
y1 = tempPoint.y;
for
(int i = 1; i < points.size(); i++) {
tempPoint = (Point) points.elementAt(i);
x2 = tempPoint.x;
y2 = tempPoint.y;
g.drawLine(x1, y1, x2, y2);
x1 = x2;
y1 = y2;
}
// end for
} // end if
}
public boolean
mouseDown(Event e, int x, int y) {
points.addElement(new Point(x, y));
return true;
}
public boolean
mouseDrag(Event e, int x, int y) {
points.addElement(new Point(x, y));
repaint();
return true;
}
public boolean
mouseUp(Event e, int x, int y) {
points.addElement(new Point(x, y));
repaint();
return true;
}
}
· Here's a simple applet that uses the keyDown
method to let you type some text.
import java.applet.Applet;
import java.awt.Event;
import java.awt.Graphics;
public class typewriter extends
Applet {
int numcols = 80;
int numrows = 25;
int row = 0;
int col = 0;
char page[][] = new
char[numrows][];
public void init() {
for (int i = 0; i < numrows; i++) {
page[i] = new char[numcols];
}
for (int i = 0;
i < numrows; i++) {
for (int j = 0; j < numcols; j++) {
page[i][j] = '\0';
}
}
}
public boolean
keyDown(Event e, int key) {
char c = (char)
key;
switch (key) {
case Event.HOME:
row = 0;
col = 0;
break;
case Event.END:
row = numrows-1;
col = numcols-1;
break;
case Event.UP:
if (row > 0) row--;
break;
case Event.DOWN:
if (row < numrows-1) row++;
break;
case Event.LEFT:
if (col > 0) col--;
else if (col == 0 && row > 0) {
row--;
col=numcols-1;
}
break;
case Event.RIGHT:
if (col < numcols-1) col++;
else if (col == numcols-1 && row < numrows-1) {
row++;
col=0;
}
break;
default:
if (c == '\n' || c == '\r') {
row++;
col = 0;
}
else if (row < numrows) {
if (col >= numcols) {
col = 0;
row++;
}
page[row][col] = c;
col++;
}
else { // row >= numrows
col++;
}
}
repaint();
return true;
}
public void paint(Graphics
g) {
for (int i=0; i < numrows; i++) {
String tempString = new String(page[i]);
g.drawString(tempString, 5, 15*(i+1));
}
}
}
· The Mandelbrot set is a classic application
of complex arithmetic. It is the example of a fractal set. Here and in
the future we are going to try to separate the mathematical definition of our
data from its display on the screen. In fact we won't even add the screen
display till the second iteration of the program.
Our data structure will be a two dimensional array, each element of which
represents a fixed point in the complex plane. The array is defined by the
value of the lower left point, the gap between points and the number of pixels
in the x and y directions. Thus given that element (0,0) of the array matches
the complex point (x0,y0), each point is separated by a value gap, we
know that element i,j of the array represents the point (x0 + i*gap, y0 +
j*gap) in the complex plane. Since we know this by position of the array
element alone we don't need to store this value in the array.
What will we store in each element of this array? We'll calculate a number to
go there in the following fashion. Let z = 0 + 0i and let c be the position of
that array element in complex space. calculate z = z*z + c and iterate as
follows up to 200 times:
for (i=0; i < 200; i++) {
z = z.Times(z) + c;
}
The Mandelbrot set is composed of those elements which, no matter how long we
do this, never approach infinity. Since infinity can take a mighty long time to
approach, it's fortunate that a fairly elementary theorem in complex variable
theory guarantees that any number whose magnitude ever exceeds two in this
iterative scheme will become as large as one might wish. (i.e. They
asymptotically approach infinity.) Therefore once a number exceeds two we can
break out of the loop and say definitively that this number is not in
the Mandelbrot set.
Unfortunately there's no guarantee that just because an element doesn't reach 2
in two hundred iterations it might not reach two on the two hundredth and first
iteration or the two thousandth or the two millionth. However most numbers that
don't prove they're not in the Mandelbrot Set by the two hundredth iteration
are reasonably likely to be in it.
Here's how the code will work. First we'll select the lower left hand corner of
our rectangle in complex space, the size of the gap between the points and the
number of points in each dimension. For a specific example we can choose the
square bordered on the lower left by (-2,-2) and on the upper right by (2,2).
To keep initial computations manageable we'll break this up into an array of
101 by 101 elements which implies a gap size of 0.05.
Once this array is created we'll loop through it and fill each element with a
Boolean value, true if the element is probably in the Mandelbrot Set (doesn't
pass two in two hundred iterations) and false if it's not (does pass two and
thus go to infinity). Here's the code:
class MandelApp {
public static void
main(String args[]) {
int xdim = 101;
int ydim = 101;
double xstart =
-2.0;
double ystart =
-2.0;
boolean
Mandel[][] = new boolean[xdim][ydim];
double gap =
0.05;
int
max_iterations = 200;
int i,j,k;
Complex z, c;
for (i=0; i
< xdim; i++) {
for
(j=0; j < ydim; j++) {
c = new Complex(xstart + i*gap ,ystart + j*gap);
z = new Complex(0.0, 0.0);
k=0;
while (z.Magnitude() < 2.0 && k < max_iterations) {
z = z.Times(z);
z = z.Plus(c);
k++;
}
if (z.Magnitude() < 2.0) {
Mandel[i][j] = true;
}
else Mandel[i][j] = false;
}
}
}
}
· To make this interesting we want to actually
draw pictures of the Mandelbrot Set. To do this we'll move the actual
calculation into a thread in an applet and then draw the results into a bitmap.
Here's the code:
import java.applet.Applet;
import java.awt.*;
public class Mandelbrot extends
Applet {
int xdim;
int ydim;
double xstart = -2.0;
double ystart = -1.25;
int Mandel[][];
double gap = 0.05;
int max_iterations = 256;
public void paint(Graphics
g) {
int i,j,k;
Complex z, c;
xdim =
size().width;
ydim =
size().height;
gap = 2.5/ydim;
Mandel = new
int[xdim][ydim];
for (i=0; i
< xdim; i++) {
for
(j=0; j < ydim; j++) {
c = new Complex(xstart + i*gap, ystart + j*gap);
z = new Complex(0.0, 0.0);
for (k = 0; z.Magnitude() < 2.0 && k < max_iterations; k++) {
z = z.Times(z);
z = z.Plus(c);
}
g.setColor(selectColor(k));
g.fillRect(i, j, 1, 1);
}
}
}
protected Color selectColor
(int num_iterations) {
if
(num_iterations > max_iterations) return Color.black;
else if
(num_iterations > 9*max_iterations/10) return Color.darkGray;
else if
(num_iterations > 8*max_iterations/10) return Color.gray;
else if
(num_iterations > 7*max_iterations/10) return Color.magenta;
else if
(num_iterations > 6*max_iterations/10) return Color.cyan;
else if
(num_iterations > 5*max_iterations/10) return Color.blue;
else if
(num_iterations > 4*max_iterations/10) return Color.green;
else if
(num_iterations > 3*max_iterations/10) return Color.yellow;
else if
(num_iterations > 2*max_iterations/10) return Color.orange;
else if
(num_iterations > 1*max_iterations/10) return Color.red;
else return
Color.white;
}
}
This program is minimal. It should really create an ImageProducer which draws the Mandelbrot set. There are
also a lot of additions that could be made to the parameters to allow for
zooming in on particular regions. In fact you could even implement this as a Canvas in an applet with various controls
to select the area of interest.
These Notes have benefited from materials adapted from several sources, e.g.,