Java 2 Guide

Share on:

Table of Contents

Read First

This document was converted from an old file format type and there are some evident proofing and formatting issues. As of 2020, Java 2 is legacy technology but the tutorial is left here for posterity only

Language Fundamentals

Source Files

Naming convention

Java source files shouldn’t be named using arbitrary extensions. All Java source files must end with the .java extension.

Examples:

1        PieGraph.java            OK
2        Bars.jav                 WRONG
3        StatCalc.jv              WRONG
4        DataFetch.java           OK

Public class file dependency

A source file should contain only one top-level public class definition. If a public class is present, its name should match the filename (without the extension). For example, a public class named PieGraph should be saved in a file named PieGraph.java

Only one public class definition is allowed per file, but there’s no limitation for non-public class definitions.

Example #1:

1// PieGraph.java
2public class PieGraph {}
1Action: javac PieGraph.java
2Result: OK - The file PieGraph.class is created

Example #2:

1// PieGraph.java
2public class PieGraph {}
3public class BarGraph {}
1Action: javac PieGraph.java
2
3Result: ERROR - class BarGraph is public and should be declared in a file
4named BarGraph.java

Example #3:

1// BarGraph.java
2public class BarGraph {}
3class BigGraph extends BarGraph {}
4class SmallGraph extends BarGraph {}
1     Action: javac BarGraph.java
2     Result: OK - The files BarGraph.class, BigGraph.class and SmallGraph.class are created from BarGraph.java

The 3 top-level elements

There are 3 top-level elements that may appear in a file. None of them is required but if they are used, they must appear in the following specific order:

  1. Package declaration
  2. Import statements
  3. Class definitions

Keywords and Identifiers

An identifier is a word used to name a variable, method, class or label. Java keywords are those used by the Java language itself, so you can’t use them for your own purposes. Some words are not part of the language but are reserved for future use and consequently, can’t also be used as identifiers.

List of Java keywords and reserved words:

 1     abstract              boolean               break
 2     byte                  case                  catch
 3     char                  class                 const
 4     continue              default               do
 5     double                else                  extends
 6     false                 final                 finally
 7     float                 for                   goto
 8     if                    implements            import
 9     instranceof           int                   interface
10     long                  native                new
11     null                  package               private
12     protected             public                return
13     short                 static                strictfp
14     super                 switch                synchronized
15     this                  throw                 throws
16     transient             true                  try
17     void                  volatile              while

Rules

An identifier must begin with one of the following characters:

  • A letter (a-Z)
  • A dollar sign ($)
  • An underscore (_)

Subsequent characters may be all of these plus digits (0-9). An identifier must never begin with a digit, they are valid only if placed after the three accepted characters.

Identifiers are always case sensitive.

1     int apples = 5;         // OK
2     int return = 0;         // WRONG
3     boolean 3cats = true;   // WRONG
4     char $perlvar = 'a';    // OK
5     int %bonus = 3000;      // WRONG
6     char _goto = 'b';       // OK

Primitive Data Types

Types and their effective sizes

Type Size (bits) Min (e) Max (e) Min (d) Max (d)
boolean 1 false true false true
byte* 8 -2^7 +2^7-1 -128 +127
Char 16 0 +2^16-1 0 +65535
short* 16 -2^15 +2^15-1 -32768 +32767
int* 32 -2^31 +2^31-1 -2147483648 +2147483647
Float 32 # # 1.4E-45 3.4028235E38
long* 64 -2^63 +2^63-1 # #
Double 64 # # 4.9E-324 #

* Signed integral data types
# Expression or number omitted because of size or impractical meaning.

Literals

A literal is a value specified in the program source by the programmer. Literals can represent primitive or string variables. They may appear only on the right side of assignments or in method calls. It is not possible to assign a value to a literal.

Boolean

The Boolean data type accepts only two literals:

  • true
  • false

Never try to assign a number, it won’t work.

Char

Java characters are in Unicode, as opposed to other languages in which characters and bytes are basically the same thing. If the most significant nine bits of a char are all 0, Java assumes the encoding to be 7-bits ASCII.

There are several ways to express char literals:

a) By enclosing the chosen character in single quotes:

char a = 'h';

b) By specifying the Unicode value using 4 hexadecimal digits, preceded by \u, with the entire expression in single quotes:

char a = '\\u4566';

c) By representing a special character using escape sequences:

'\n' = new line
'\r' = return
'\t' = tab
'\b' = backspace
'\f' = formfeed
'\'' = single quote
'\"' = double quote
'\\' = blackslah

Integral

Integral literals may be expressed in three ways:

a) In decimal, by writing the number as it is:

byte a = 28;

b) In octal, by prefixing the literal with 0 (zero):

byte a = 034;

c) In hexadecimal, by prefixing the literal with ‘0x’:

byte a = 0x1C;

Hexadecimal digits (abcdef) and the x from ‘0x’ may be upper or lower case.

All integral literals are 32-bits by default. To indicate a long literal (64-bits), you need append the suffix L to the literal expression.

long a = 0x1CL;

Floating Point

A floating-point literal expresses a floating-point number. In order to be interpreted as a floating-point literal, an expression must contain one of the following:

a) A decimal point:

float a = 1.414;

b) The letter E/e, indicating scientific notation:

double a = 4.23E+21;

c) The suffix F/f, indicating a float literal:

float a = 1.828f;

d) The suffix D/d, indicating a double literal:

double a = 1234d;

Java assumes literals without the d/f prefixes to be double.

Strings

A string literal is a sequence of characters enclosed in double quotes.

String s = "Hello World";

  • The characters in strings are also 16-bit Unicode.
  • You can use all literals associated with the char data type.

Arrays

A Java array is an ordered collection of:

  • primitives
  • object references
  • or other arrays

Arrays are homogeneous, all elements must be of the same type. There is only one exception: When the elements are instances of the array’s class or sub-classes of that class.

To set up an array, three steps must be followed:

  1. Declaration
  2. Construction
  3. Initialisation

Declaration

  • It tells what the array’s name will be
  • It tells the type of elements of the array

Examples:

1int[] winners;
2char[] alphabet;
3short[][] space;
4
5int loosers[];
6char character_set[];
7
8int [] winners;
9char alphabet [];

There is no difference between winners and loosers, both are arrays of the same type. Java allows to place the square brackets before or after the array variable name. Please note that white space is allowed alongside brackets.

You need to recognize both forms even if you use always one.

Construction

You construct an array when you determine its size or number of elements.

At the past declaration examples, we haven’t specified the arrays’ sizes, we had just declared them:

1int[] winners;

This approach allows us to define the array’s size in runtime, either using literals or variables.

1winners = new int[45];        // By using a literal
2
3int amount = 45;              
4winners = new int[amount];    // By using a variable

Commonly, we want to declare and construct an array at the same time:

1char[] alphabet = new char[26];

Please note:

  • The first element of an array is 0
  • The last one is the array’s size - 1

So, if you want to include the first and last letters of the alphabet, what would you do?

1alphabet[0] = 'a';      // OK
2alphabet[25] = 'z';     // OK
3alphabet[26] = '!';     // WRONG!

There is no element 26, alphabet[25] points to the 26th element of the array, because the first element of an array is always 0.

Initialisation

When arrays are constructed, Java initialises elements to their default values, depending of the type, as shown below:

Element type Initial value
byte 0
int 0
float 0.0f
char \u0000
obj. reference null
short 0
long 0L
double 0.0d
boolean false

Remember that:

  • Arrays are actually objects
  • You can often execute methods on them
  • The array class itself cannot be sub-classed

You can combine declaration, construction and initialisation by enclosing the desired elements using curly braces:

Examples:

1char letters[] = {'a', 'e', 'i', 'o', 'u'}; 
2ci_nums[] = {2.3E+5, 9.3E-2, 4E+6};

The number of elements is automatically gathered by Java by counting the number of elements within the curl braces. They are also placed in the right order, so letters[0] would be ‘a’, letters[1] = ‘e’, and so on.

You can also initialise specific elements:

1letters[0] = 'a';
2letters[2] = 'i';

If we want to set all elements to our own default values, a simple loop would be enough:

1int numbers[] = new int[3000];
2for (int i = 0 ; i < numbers.length; i++) {
3    numbers[i] = i;
4}

Please note that numbers.length = 3000 but numbers[2999] is the LAST element, not numbers[3000]. Numbers[0] is the FIRST element.

Class Fundamentals

The main() Method

  • The main() method is the normal entry point for Java applications.
  • An application usually has a class definition that includes a main() method
  • Such an application is executed by typing java followed by the name of the class containing the main() method.

The signature for main()

1public static void main(String[] args)
  • public: The main() method is declared public by convention
  • static: It is declared static so there’s no need to create an instance of the corresponding class.
  • args: The args array contains arguments that the user might have entered on the command line. The args variable is used by most Java books but any legal identifier may be employed.

Example:

1c:\test>java DeleteFiles myletter.txt love.txt
  • args[0] will contain “myletter.txt”
  • args[1] will contain “love.txt”

Variables and initialisation

Java supports variables of 2 different lifetimes:

A member variable of a class:

  • It is created when an instance is created.
  • It is destroyed when the object is destroyed.
  • It is subject to accessibility rules.
  • It is subject to the need for a reference to the object.
  • It is accessible as long as the enclosing object exists.

An automatic variable of a method (also known as method local):

  • It is created on entry to the method.
  • It exists only during execution of the method.
  • It is therefore only accessible during execution of that method.

Member variables are initialised if no value is provided according to this table:

Element type Initial value
byte 0
int 0
float 0.0f
char \u0000
obj. reference null
short 0
long 0L
double 0.0d
boolean false

Example of member variables:

1class MyClass {
2    int a = 50;
3    int b = 60;
4    int c;
5    static z = 3000;
6}
  • The non-static variables a, b and c are initialised just before the class constructor is executed.
  • The variable c is automatically initialised to its default value (0).
  • The static variable z is initialised when the class is loaded

Example of automatic variables:

1public int take_care () {
2    int a = 50;         // OK
3    int b = 60;         // OK
4    inc c;              // WRONG!
5    return c;
6}
  • Local variables are NOT initialised by the system. You need to explicitly initialise them yourself.
  • Therefore, c will cause an error.

Argument Passing

By default, when Java passes a primitive argument into a method call, it is actually a copy of the argument that gets passed, not a reference.

Example:

1int age = 24;
2add_a_year(age);
3System.out.println("My age is now " + age);
4
5static void add_a_year(int age) {
6    age++;
7}
1Result: My age is now 24

Explanation: Java passes a copy of 24 to a new variable also named age but only within the context of the add_a_year() method. The variable age within the add_a_year() method is destroyed when the method is exited.

Object references

You always deal with object references and not objects themselves. If you want to pass a single value by reference as in Perl (or by using pointers in C/C++), you must get your primitive value inside some sort of object.

Example:

1static public void main(String args[]) {
2    int age[] = { 24 };
3    add_a_year(age);
4    System.out.println("My age is now " + age[0]);
5}
6
7static void add_a_year(int obj_age[]) {
8    obj_age[0]++;
9}
1Result: My age is now 25

The identifier obj_age may also be just age. Arrays are objects, thus when you work with arrays, you actually work with references to them, not with array themselves. Be careful, references are really references.

Examine this example:

1static public void main(String args[]) {
2    TextField username = new TextField("Ernest");
3    change_user(username);
4    System.out.println("Welcome back " + username.getText());
5}
6
7static void change_user(TextField user) {
8    user.setText("Adriana");
9}
1Result: Welcome back Adriana

As shown in the previous age example, when you pass an object into a method, you actually pass a reference. So, within the change_user() method, the username object created within the main() method gets changed.

Now, take a look at this:

 1static public void main(String args[]) {
 2    TextField username = new TextField("Ernest");
 3    change_user(username);
 4    System.out.println("Welcome back " + username.getText());
 5}
 6
 7static void change_user(TextField user) {
 8    user = new TextField("Taffy");
 9    user.setText("Adriana");
10}
1Result: Welcome back Ernest

So, what happened here?. The change_user() method, receives the reference of the username object. At this time, user becomes a valid container for any reference to an object based on the TextField class. Then, we create a new TextField object:

1user = new TextField("Taffy");

The reference to the username object created within main() is replaced by a reference to a new object. So, when we change the text field, we change our newly created object:

1user.setText("Adriana");

According to this, we can also save our passed reference:

 1static public void main(String args[]) {
 2    TextField username = new TextField("Ernest");
 3    change_user(username);
 4    System.out.println("Welcome back " + username.getText());
 5}
 6
 7static void change_user(TextField user) {
 8    TextField old_user = user;
 9    user = new TextField("Taffy");
10    user = old_user;
11    user.setText("Adriana");
12}
1Result: Welcome back Adriana

Please note that the reference to the object initialised as Taffy is lost forever as its reference hasn’t be saved.

Garbage Collection

  • Objects are always allocated on the heap.
  • Method local variables and arguments are allocated on the stack.
  • In Java, you never explicitly free memory that you have allocated.
  • Memory is freed by the Garbage Collector.
  • The Garbage Collector works automatically, you CAN’T call it explicitly.

There are a couple of methods that suggest that is possible to run the Garbage Collector explicitly:

1System.gc()
2Runtime.gc()

But there’s no guarantee that they will actually free memory or actually “run the garbage collector”, these methods will be probably extended in the future or will be available for special JVM versions.

Be careful about memory leaks

If you leave objects that you no longer use hanging around, you can be in trouble. Of course, you can’t destroy your objects, but you have to program in a way that the Garbage Collector can realize when objects won’t be used anymore. One trick is to point object variables (or elements of an array) to null values when they aren’t needed anymore.

Puzzling issues

Be aware of double literals:

    a = 1.2456
    b = 1.2456f
  • a is a double literal
  • b is a float literal.

Operators

Table of Operators in Descending Order of Precedence

Category Operators
Unary ++ -- + - ! \~ ()
Arithmetic \* / %
+ -
Shift &lt;&lt; &gt;&gt; &gt;&gt;&gt;&gt;
Comparision &lt; &lt;= &gt; &gt;= instanceof
== !=
Bitwise & \^ |
Short-circuit && ||
Conditional ?:
Assingment = "op="

Evaluation Order

  • Operands are evaluated from left to right.
  • For Assignments, associability is from right to left.

Consider this example:

1int a[] = { 1,2,3 };
2int b = 1;
3a[b] = b = 0;
4System.out.println(a[0] + "-" + a[1] + "-" + a[2]);
1Result: 1-0-3

The program was interpreted as follows:

1a[b] = (b = 0);

At the moment of the assignment a[b]=, b is still 1, so it still points to the second element of the array. Then, the operation b = 0 takes place, where its result is assigned to a[1]. So, this is what happened:

1b = 0;
2a[1] = b;

What about this?

1int a[] = { 1,2,3 };
2int b = 1;
3int c = 2;
4int d = 0;
5a[b] = b = c = d = 3;
6System.out.println(a[0] + "-" + a[1] + "-" + a[2]);
1Result: 1-3-3

The assignment was interpreted this way:

1d = 3;
2c = d;
3b = c;
4a[1] = b;

The Unary Operators

  • Unary operators take just one single operand.
  • They are unlike others where operators take two (i.e. multiplication).

Java provides 7 unary operators:

Operator Description
++ Increment operator
-- Decrement operator
+ Plus operator
- Minus operator
~ Bitwise inversion operator
! Boolean complement operator
() Cast operator

The cast is not an operator strictly speaking. It is however included in this category for simplicity.

The Increment and Decrement Operators: ++ and –

  • These ones modify the value of an expression by adding or substracting 1.
  • The operands may be placed after or before the expression.

Examples:

1++x;
2x++;
3y--;
4--y;
5x--;

The use of operands before the identifier is called pre-increment or pre-decrement (depending of the operand used, ++ and -- respectively) and it changes the evaluation order.

Example:

1x = 1;
2y = x++;

Results:

1y = 1
2x = 2

These couple of sentences are interpreted this way:

1x = 1;
2y = x;
3x = x + 1;

A bit of pre-decrement now:

1x = 1;
2y = --x;

Results:

1y = 0
2x = 0

The example above was interpreted as follows:

1x = 1;
2x = x - 1;
3y = x;

The Unary Plus and Minus Operators: + and -

The unary operators + and - are distinct from the more common binary + and - operators used for subtraction and addition.

The unary + has no effect on the value of its operand, but the expression is promoted to at least int.

Examples:

1int x = +5;   // OK. The positive value 5 is assigned to x.
2
3short a = +7  // OK. The positive value 7 is assigned to a. 
4short b = +a  // Error. +a is promoted to int and there is
5              // a "loss of precision" error.

Unary - negates an expression. It is mostly applied to expressions.

Examples:

1    int y = -(5 * 6);
2    int x = -3 * -5     // Minus by minus produces a positive result.

It also promotes the expression to int, so be careful.

The Bitwise Inversion Operator: ~

  • The ~ operator performs bitwise inversion on integral types.
  • The ~ works by converting all the 1 bits in a binary value to 0s and all the 0 bits to 1s.
1int a = 0xFFFFFFFF;
2a = ~a;
3System.out.println(a);
1Result: 0

The Boolean Complement Operator: !

  • The ! operator inverts the value of a boolean expression.
  • !true gives false and !false gives true.

Example #1:

1boolean married = flase;
2if (!married) {
3     System.out.println("Why not?");
4}
1Result: Why not?

Example #2:

1boolean oxygen = false;
2boolean hydrogen = true;
3
4oxygen = !oxygen;
5
6if (!(oxygen && hydrogen)) {
7    System.out.println("No water today");
8} 
1Result: None

The Cast Operator: (type)

Casting is used for explicit conversion of the type of the expression. This is only possible for plausible target types. The compiler and the runtime system check for conformance with typing rules. There is obviously loss of precision when you cast a value like “2.3” into an int.

Examples:

1int a = (int)2.3;                 // Result: 2
2char b = (char)(1500 / 23);       // Result: Character 'A'

The Arithmetic Operators

Multiplication and Division Operators: * and /

If you multiply two integers, the result will be calculated using integer arithmetic in either int or long representation. It is very important to keep in mind the loss of precision and overflow problems that arise when you deal with integer values. Consider these two examples:

Example #1:

1int x = 6;
2int z = 15;
3
4int a = x * z / 3;
5int b = z / 3 * x;

Results:

1a = 30
2b = 30

Let’s change the value of z to 14 now:

Example #2:

1int x = 6;
2int z = 14;
3
4int a = x * z / 3;
5int b = z / 3 * x;

Results:

1a = 28
2b = 24

So why a and b aren’t equal? This is what happens:

Example #1:

1a = 6 * 15;             // Result = 90
2a = 90 / 3;             // Result = 30
3
4b = 15 / 3;             // Result = 5
5b = 5 * 6;              // Result = 30

Example #2:

1a = 6 * 14;             // Result = 84
2a = 84 / 3;             // Result = 28    (Right value)
3
4b = 14 / 3;             // Result = 4 (4,666...)
5b = 4 * 6;              // Result = 24

4,6666 got rounded to just “4” loosing a lot of precision, and of course, a very inaccurate result was produced. If you still want to work with integers, maybe you should handle your expressions under a safe floating-point environment:

 1int x = 6;
 2float z = 14;
 3
 4int a = (int)( x * z / 3);
 5int b = (int)( z / 3 * x);
 6
 7Results:
 8 
 9a = 28
10b = 28

The Modulo Operator

The modules operators gives the remainder of a division. It is generally applied to two integers, although it can be applied to floating-point numbers too.

Examples:

1int a = 14 % 5;         // Result = 4

Some problems arise when you apply the % operator to negative values. In such cases, try to do the modulo on the positive version of the number and then add the negative sign. The Addition and Subtraction Operators: + and -

The + and - operators are used for addition and subtraction but also on String to perform operations such as string concatenation.

Please note that Java does not allow the programmer to perform operator overloading, but the + is overloaded by the language itself in the case of strings.

For an expression with two operands involving the + operator, the result:

  • It is of primitive numeric type.

  • It is at least int, according to the default promotion.

    short x = 17;
    short y = 3;\

    short z = x + y; // Error. The result is an int
    short z = (short) (x + y); // Ok. Int result is cast to short.

  • It is of a type at least as wide as the wider of the two operands.

    int x = 17;
    int y = 3;
    float z = 1.0;\

    int a = x + y + z; // Error. The result is float because one of the operators is float
    int a = (int)(x+y+z); // Ok.

  • Has a value calculated by promoting the operands to the result type, then performing the addition using that type. This might result in overflow or loss of precision.

For a + expression with any operand that is not of primitive type:

  • At least one operand must be a String object or literal, otherwise the expression is illegal.

  • To convert primitive types to String, use the static class:

    Integer.toString();

  • To convert some unknown object to some sort of string representation, use the provided .toString() method. This method is defined in the root of the class hierarchy.

  • Any remaining non-String operands are converted to String, and the result of the expression is the concatenation of all operands.

    int a = 2;
    int b = 23;
    String s = " years old ";

    String my_string = "Taffy is " + a + s + "and I'm "+b;
    System.out.println(my_string);

    Result: Taffy is 2 years old and I'm 23

Arithmetic Error Conditions

  • ArithmeticException: Produced by integer division by zero (or modulo %)
  • No other arithmetic causes any exception. Instead, the operation proceeds to a result, even though that result might be arithmetically incorrect.
  • IEEE 754 infinity, minus infinity and Not a Number (NaN) values: Produced by floating-point calculations where the value is out-of-range.
  • Integer calculations that cause overflow or similar errors simply leave the final, typically truncated pattern in the result. The number representation might even be of bad sign.

Comparisons with Not a Number

To test for a NaN result you should use the static methods of the Float or Double classes.

1float a = Float.NaN;
2double b = Double.NaN;
3
4if (Float.isNaN(a)) {
5    System.out.println("It is Not a Number!");
6}
7if (!Double.isNaN(a)) {
8    System.out.println("Yes, this is a number!");
9}

Never try to compare the constants .NaN against other values, as the result won’t be what you expect:

1y > Float.NaN              // False 
2x == Double.NaN            // False
3x < Float.Nan              // False

The Shift Operators: <<, >> and >>>

  • Shifting involves taking the binary representation of a number and moving the bit pattern left or right.
  • The shift operators may be applied to arguments of integer type only.
  • As possible, they should be applied only to operands of either int or long type.

To know how to perform shift operations on integer types, you have to understand how negative and positive values are represented in bits. Although most bit manipulations are performed on ints, we will use bytes to reduce large-number-headache.

Bytes are signed. They can represent the minimum value of -128 and the maximum value of +127. But, a byte is still a byte, that means that it can hold 256 different bit patterns.

Consider this example:

1byte a = 127;  
2a++;
3
4// Result: a = -128

The maximum value a signed byte can represent is of +127. When we assign a positive number that exceeds the maximum allowed, the corresponding bit pattern is assigned. So, 128 = 10000000 = -128.

1byte a = 127;  
2a = (byte)(a + 128);  
3
4// Result: a = -1

You can’t do a = 255 directly as a value beyond 127 is assumed to be an int. A simple cast is all what you need:

1byte a = (byte)255;
2byte a = (byte)0xFF;
3
4// Result: In both cases, a = -1

So, how do negatives numbers match their binary and unsigned versions?. Check this table:

Signed value Unsigned value Binary value
0 0 00000000
1 1 00000001
2 2 00000010
3 3 00000011
. . .
. . .
126 126 01111110
127 127 01111111
-128 128 10000000
-127 129 10000001
-126 130 10000010
. . .
. . .
-3 253 11111101
-2 254 11111110
-1 255 11111111

You might get some conclusions by examining this table:

  • The minimum negative value of a type is equal to the unsigned value of the absolute value of that negative type.
  • All negative numbers have the MSB (Must Significant Bit) turned on.
  • -1 is always the maximum unsigned value of the type (-1 equals to turning on all the bits of the type)

Shifting Negative and Positive Numbers

Sadly, Java shifts numbers under a “signed” environment. When negative numbers are shifted, the missing spaces are filled with the MSB bit, that means 1. Of course, this only occurs on right shifting. Let’s take a look at some examples to get a better picture:

Original value = 01000000 (64)

Exp. Binary Decimal (signed)
None - Original value 01000000 64
Shifted left 1 bit 64 << 1 10000000 -128
Shifted right 1 bit 64 >> 1 00100000 32
Shifted left 2 bits 64 << 2 00000000 0
Shifted right 2 bits 64 >> 2 00010000 16

Original value = 10111111 (-65)

Exp. Binary Decimal (signed)
None - Original value 10111111 -65
Shifted left 1 bit 65 << 1 01111110 126
Shifted right 1 bit 65 >> 1 11011111 -33 (MSB carried)
Shifted left 2 bits 65 << 2 11111100 -4
Shifted right 2 bits 65 >> 2 11101111 -17 (MSB carried)

To avoid this (sometimes undesirable) effect, you can use the right >>> operator rather than the plain “>>” one.

Examine now an example using the >>> operator:

1int a = -65;
2int r = 0;
3
4r = a >> 1;   // Result: -33
5r = a >>> 1;  // Result: 2147483615

Special note: A byte-sized value such as -65 is promoted to int so shifting always takes place on 32-bit values. We use 8-bit examples just to explain the concept painlessly.

Be aware, really. For example, -65, shifted 1 bit to the left, as a byte, gives 126, but, as an int, gives -130. Why? Take a look:

1byte pattern:                              10111111                        
2int pattern:    11111111 11111111 11111111 10111111

Shifted 1 bit to the left:

1byte pattern:                              01111110 = 126                  
2int pattern:    11111111 11111111 11111111 01111110 = -130

Examine also this example:

1int a = -65;
2int r = 0;
3
4r = a << 1;               // Result: -130
5r = (int)(byte)(a << 1);  // Result: 126

When you cast an int into a byte, Java takes only the last byte, ignoring completely the information contained on the left three bytes.

The Comparison Operators

They are:

1        <  <=  >  >=  == !=

They only return a boolean result; true or false. They are commonly used to form conditions, such as if, while, etc.

There are 3 comparison types:

  1. Ordinal comparison: Test the relative value of numeric operands
  2. Object-type comparison: Determine whether the runtime type of an object is of a particular type or a subclass of that particular type.
  3. Equality comparison: Test whether two values are the same and may be applied to values of non-numeric types.

Ordinal Comparisons with <, <=, > and >=

1         < Less than
2        =< Less than or equal to
3         > Greater than
4        => Greater than or equal to

These ones are applicable to all numeric types, including char.

Example #1:

1char x = 'A';
2char y = 'Z';
3char z = 'G';
4
5if (z >= x && z <= y) {
6    System.out.println("It's part of the alphabet");
7}
8
9// Result: It's part of the alphabet

Example #2:

1float a = 2.999999f;
2int b = 3;
3
4if (b > a) {
5    System.out.println("Sorry, b is greater");
6}
7
8// Result: Sorry, b is greater

As you can see, it is b that gets promoted to 3.0f and not a to 3.

Ordinal comparisons operate satisfactorily on dissimilar numeric types, but they are not applicable to any non-numeric types. The instanceof Operator

  • The instanceof operator tests the class of an object at runtime.
  • The left argument can be any object reference expression, usually a variable or an array element.
  • The right operand must be a class, an interface or an array.
  • The instanceof operator returns true if the class of the left argument is the same as, or is some subclass, of the class specified by the right operand.

Example #1:

 1TextField a = new TextField();
 2
 3if (a instanceof TextField) {
 4    System.out.println("Yes, it is!");
 5} else {
 6    System.out.println("Sorry, it isn't");
 7}
 8
 9// Result: Yes, it is!   
10// a is an instace of the TextField class.

Example #2:

 1class SuperTextField extends TextField {}
 2
 3TextField a = new SuperTextField();
 4
 5if (a instanceof TextField) {
 6    System.out.println("Yes, it is!");
 7} else {
 8    System.out.println("Sorry, it isn't");
 9}
10
11// Result: Yes, it is!
12// a is an instance of a subclass of TextField

Example #2.b:

 1TextField a = new TextField();
 2
 3if (a instanceof SuperTextField) {
 4    System.out.println("Yes, it is!");
 5} else {
 6    System.out.println("Sorry, it isn't");
 7}
 8
 9// Result: Sorry, it isn't
10// a is an instance of the superclass of SuperTextField.

If you want to know wether a reference is pointing to an array, you can use the method isArray() of the Class.

The Equality Comparison Operators: == and !=

They test for equality or inequality respectively, returning a boolean value. Equality is subject to promotion rules so that, for example, a float value of 20.0 is considered equal to a byte of value 20. For variables of object type, the value is taken as the reference to the object, typically memory address.

For string values you should use the equals() method rather than the == or != operators.

Example:

1String var1 = "Hello"; 
2TextField field = new TextField("Hello");
3
4if (field.getText().equals(var1)) {
5    System.out.println("Equal!");
6} 
7
8// Result: Equal!

The Bitwise Operators: &, ^, and |

They perform AND, eXclusive-OR and OR operations as follows:

1        & AND
2        ^ XOR
3        | OR

These operands may be applied to integer types and also boolean types. When applied to an integer, the operation will take place considering the binary representation of integer. In the case of boolean types, a 0 result would produce false and 1 true.

The bitwise operations calculate each bit of their results by comparing the corresponding bits of the two operands on the basis of these rules:

AND (Op1 & Op2)

1        Op1 Op2 Result
2        0   0   0
3        0   1   0
4        1   0   0
5        1   1   1

Result is 1(true) only if both operands are 1(true), otherwise, it is 0(false).

Example:

1        10011100 = 156
2        00011001 = 25
3        -------- (AND)
4        00011000 = 24

XOR (Op1 ^ Op2)

1        Op1 Op2 Result
2        0   0   0
3        0   1   1
4        1   0   1
5        1   1   0

If both operands are the same (either 0 or 1) the result is 0(false). Otherwise, the result is 1(true).

Example:

1        10011100 = 156
2        00011001 = 25
3        -------- (XOR)
4        10000101 = 133

OR (Op1 | Op2)

1        Op1 Op2 Result
2        0   0   0
3        0   1   1
4        1   0   1
5        1   1   1

If one of the operands is 1(true), the result is 1(true).

Example:

1        10011100 = 156
2        00011001 = 25
3        -------- (OR)
4        10011101 = 157

Special note: Both operands must be of the same type. You can compare either booleans, where there will be only one answer (true or false) or other primitive types where the bit patterns will be modified accordingly.

Example:

1boolean her_status = true;
2int my_status = 1;
3
4if (her_status & my_status) {
5    // Do something
6}
7
8// Result: WRONG! this example doesn't compile.

The Short-Circuit Logical Operators

The short-circuit logical operators && and || provide logical AND and OR operations on boolean types. There is not XOR operation provided. So something like ^^ doesn’t exist.

The main difference between the & and && and between the | and the || operators is that the right operand might not be evaluated in the latter cases:

  • AND: If one operand is false, the result is false, without regard to the other operand.
  • OR: If one operand is true, the result is true, without regard to the other operand.

So, for any boolean value x:

  • false AND x = false
  • true OR x = true

What is this really all about? Consider you have this segment of code:

1if (s != null) {
2    if (s.length() > 5) {
3        System.out.println(s);
4    }
5}

You have to be sure that s is not null, otherwise it would be illegal to obtain the length of a null string. You can produce achieve the same goal like this:

1if (s != null && s.length() > 5) {
2    System.out.println(s);
3}

As described lately, if the first operand is false, Java would not try the rest of the operands. Java will try operand by operand until a false boolean value is produced. If the scanning is over and not false value was found, then the result is true.

Word of advice: Be really careful as common sense makes you think that all expressions will be evaluated first, and then each resulting operand will be subject to the desired boolean operation.

The Ternary or Conditional Operator: ?:

It is called the ternary operator because it take 3 operands. It provides a way to code simple conditions into a single expression.

1        a = x ? b : c;

It means that if x = true, then a = b, otherwise a = c:

1if (x) {
2    a = b;
3} else {
4    a = c;
5}

As you can see, x must be a valid boolean value (true or false);

Example:

 1int customer_age = 17;
 2
 3boolean adults_only = true;
 4int minimum_age = adults_only ? 18 : 3;
 5
 6if (customer_age < minimum_age) {
 7    System.out.println("Sorry, grow up a little bit");
 8} 
 9
10// Result: Sorry, grow up a little bit

The Assignment Operators

Assignment operators set the value of a variable or expression to a new value. Assignments are supported by many operators:

1        *=  /=  %=  +=  -=  <<=  >>=  >>>=  &= ^= |=

Examples:

1byte a = 16;
2x += 4;            // x = (byte)(x + 4);
3
4short b = 5;
5b *= 9;            // b = (short)(b * 9);

As you can see, the compound operators don’t promote the expression to int. “b * 9” alone is promoted to an int, so you need to cast the value to a short in order to assign it to a variable of that type.

When a string is part of the expression, the same + operator rules, apply:

1String s = "Hello ";
2s += "dude"; 
3System.out.println(s);
4
5// Result: Hello dude

Puzzling Issues

Shifting numbers

A number shifted by the size in bits of the primitive that contains it gives as result the same number:

1int a = 128;
2
3int b = a >>> 32;         // Result: b = 128
4int c = a << 32;          // Result: c = 128
5int d = a >> 32;          // Result: d = 128
6
7byte x = -1;              
8x >>= 8;                  // Result: x = -1

Primitives and Objects.

There are classes for each basic primitive, but the objects created from these classes are not primitives types, likewise, primitive types are not instances of these classes.

1int a = 15;
2Integer b = new Integer(15);

In the above case, a is a primitive type of value 15. b is a reference to an Integer object. So, this is a mistake:

1if (a == b) {
2    // Wrong.
3}

But this is ok:

1if (a == b.intValue()) {
2   // Fine.
3}

Be aware of strings, because they show a special behaviour. Consider this example:

 1String x = "Hello world";
 2String y = "Hello world";
 3String a = new String("Hello world");
 4String b = new String("Hello world");
 5    
 6if (x == y) {
 7    System.out.println("x == y");
 8} 
 9if (x == a) {
10    System.out.println("x == a");
11} 
12if (a == b) {
13    System.out.println("a == b");
14} 
15
16// Result: x == y

The variables x and y are assumed to be constants because “Hello world” is the same literal for both. The compiler thinks “x and y will be Hello World forever, so why waste space storing this stuff twice?".

You read forever, that’s right. Now, let’s modify the example a little bit:

 1String x = "Hello world";
 2String y = "Hello world";
 3String a = new String("Hello world");
 4String b = new String("Hello world");
 5
 6y += " dude!";
 7System.out.println("y = " + y);
 8y = y.substring(0,11);
 9System.out.println("y = " + y);
10
11if (x == y) {
12    System.out.println("x == y");
13} 
14if (x == a) {
15    System.out.println("x == a");
16} 
17if (a == b) {
18    System.out.println("a == b");
19}   
20
21// Result:
22// 
23// y = Hello World dude!
24// y = Hello World

We append " dude!” to y, and then we get rid of the extra word by using the substring() method, so we end up with the same initial “Hello world” string. Not quite the very same, at least as a reference. This time, the compiler detects that y won’t be immutable so “Hello World” for x and y are saved at different “memory addresses”. Hence, x == y is false, and is not printed.

The Ternary Operator

There is promotion to the wider type of the results when both are of different type.

For example:

1byte a = 5;
2float c = 2;
3boolean yes = true;
4
5System.out.println( yes ? a : c );
6
7// Result: 5.0

Even if you are sure that the condition will return one of the results, the compiler just doesn’t know and makes the appropriate promotion “just in case”.

Control Flow Statements

The while() loop

There are 2 ways to construct a while() loop:

Single form:

1while (boolean_condition) 
2    statement

Block form:

1while (boolean_condition) {
2    statement 1
3    statement 2
4    ...
5} 

Example #1:

 1int i = 0;
 2while (i++ < 3)
 3    System.out.println("Hello "+i);
 4System.out.println("Bye!"); 
 5
 6// Result:        
 7//
 8//   Hello 1
 9//   Hello 2
10//   Hello 3
11//   Bye!

Example #2:

1int i = 0;
2while (i > 3) {
3    i++;
4    System.out.println("Hello");
5}
6System.out.println("Bye!");
7
8// Result: Bye!

If the boolean condition is false, the code within the braces is not executed even once. Thus there is no chance to increment i.

The do loop

Single form:

1do
2    statement
3while (boolean_condition);

Block form:

1do {
2    statement 1
3    statement 2
4    ...
5} while (boolean_condition);

Example #1:

 1int i = 0;
 2do 
 3    System.out.println("Hello "+i);
 4while (i++ < 3);
 5
 6System.out.println("Bye!");
 7
 8// Result:
 9// 
10//     Hello 0
11//     Hello 1
12//     Hello 2
13//     Hello 3
14// Bye!

Example #2:

 1int i = 0;
 2do {
 3    System.out.println("Hello "+i);
 4    i++;
 5} while (i > 3);
 6System.out.println("Bye!");
 7
 8// Result:
 9// 
10//     Hello 0
11//     Bye!

The for() loop

Single form:

1for (statement ; condition ; expression)
2    loop_body

Block form:

1for (statement ; condition ; expression) {
2    statement 1
3    statement 2
4    ....
5}

Considerations:

  • The statement is executed immediately before the loop itself is started. It is often used to set up starting conditions. It can also contain variable declarations.
  • The condition must be a boolean expression and is treated exactly the same as in the while() loop. The body of the loop will be executed repeatedly until the condition ceases to be true. As with the while() loop, it is possible that the body of a for() loop might never be executed. This occurs if the condition is already false at the start of the loop.
  • The expression is executed immediately after the body of the loop, just before the test is performed again. Commonly, this is used to increment a loop counter.

Empty for() loops:

Any part of a for() loop’s control may be omitted. Omitting the test is equivalent to a perpetually true test, so the below construct creates a loop that repeats forever:

1for (;;) {}

The for() loop and the comma separator:

The statement and expression parts may contain a sequence of expressions rather than just a single one. Those expressions should be separated with a comma. In the case of first expression, you can’t use the comma to initialise variables of different types.

Example #1:

 1for (int j = 0, k = 0; k <= 5 ; k++, j = (k*2)) {
 2    System.out.println("2*" + k + " = " +j);
 3}
 4
 5// Result:
 6//
 7//    2*0 = 0
 8//    2*1 = 2
 9//    2*2 = 4
10//    2*3 = 6
11//    2*4 = 8
12//    2*5 = 10

Example #2:

1int j;
2for (j = 0, int k = 0; k <= 5 ; k++, j = (k*2)) {
3    System.out.println("2*" + k + " = " +j);
4}
5
6// WRONG: j = 0, int k = 0

Example #3:

1for (int j = 0, long k = 0, int k = 0; k <= 5 ; k++, j = (k*2)) {
2    System.out.println("2*" + k + " = " +j);
3}
4
5// WRONG: int j = 0, long k = 0

Basically, the first expression only allows the comma to initialise more than one variable but of the same type. This is a standard Java declaration and initialisation feature:

1int i, j, k;
2int i = 1, j = 3, k = 3;

The break and continue Statements in Loops

break and continue are used to explicitly abandon execution of the body of a loop (or nested loops).

continue

Standard form:

The standard form of continue ends the current cycle of the loop explicitly and starts immediately the next.

 1char a[] = {'a','b','c','d','e','7','f','g','h'};
 2
 3for (int i = 0 ; i < a.length ; i++) {
 4
 5    if (a[i] == '7') continue; 
 6
 7    System.out.print("Letter: " + a[i]);
 8    System.out.print(" = #");
 9    System.out.println((int)(a[i]));
10
11}

If a[i] == ‘7’, a number was found and we don’t want to say that 7 is a Letter, we just end the cycle and start the next where i is incremented and we get the next character of the array.

Label form:

The label form also ends the running cycle but it allows to specify where to begin the next cycle from in a nested loop.

 1MyLabel: for (int i = 10 ; i <= 13 ; i++) {
 2    for (int j = 2 ; j <= 6 ; j += 2) {
 3
 4        int r = i / j;
 5        if (r == 6) continue MyLabel;
 6        System.out.print(i + " / " + j);
 7        System.out.print(" = ");
 8        System.out.println(r);
 9    }
10
11    System.out.println("------------------");
12}
13
14// Result:
15// 
16//     10 / 2 = 5
17//     10 / 4 = 2
18//     10 / 6 = 1
19//     ------------------
20//     11 / 2 = 5
21//     11 / 4 = 2
22//     11 / 6 = 1
23//     ------------------

If the result of i / j is equal to 6 for “some reason” we don’t want to show any division for the current dividend (i). So we go straight to the main loop and process the next dividend.

break

The break statement is very similar to the continue statement, but rather than making the loop to go the next cycle, it causes the entire loop to be abandoned right away.

Example #1:

 1for (int i = 1 ; i < 10 ; i++) {
 2    if (i > 3) break;
 3    System.out.println(i);
 4}
 5
 6// Result: 
 7// 
 8// 1
 9// 2
10// 3

Example #2:

 1for (int i = 1 ; i < 10 ; i++) {
 2    int x = 0;
 3    System.out.print("Round " + (char)(i+64) + ": ");
 4    while (++x <= 3) {
 5        if (i >= 4 && i <= 8 ) break;
 6        System.out.print(x + ", ");
 7    }
 8    System.out.println("end.");
 9}
10System.out.println("Job done");
11
12// Result:
13// 
14//     Round A: 1, 2, 3, end.
15//     Round B: 1, 2, 3, end.
16//     Round C: 1, 2, 3, end.
17//     Round D: end.
18//     Round E: end.
19//     Round F: end.
20//     Round G: end.
21//     Round H: end.
22//     Round I: 1, 2, 3, end.
23//     Job done

We just wanted to make 10 rounds of counts from 1 to 3, we used a letter to name each round. If the round is between 4 and 8 (D and H), we skipped counting from 1 to 3.

What about if the computer gets bored at Round D after counting 1 and decides to stop there altogether? We use a Label and break the main for loop:

 1MyLabel: for (int i = 1 ; i < 10 ; i++) {
 2    int x = 0;
 3    System.out.print("Round " + (char)(i+64) + ": ");
 4    while (++x <= 3) {
 5        if (i >= 4 && x >= 2) break MyLabel;
 6        System.out.print(x + ", ");
 7    }
 8    System.out.println("end.");
 9}
10System.out.println("Job done");
11
12// Result:
13// 
14// Round A: 1, 2, 3, end.
15// Round B: 1, 2, 3, end.
16// Round C: 1, 2, 3, end.
17// Round D: 1, Job done

The Selection Statements

The if()/else construct

The if()/else construct takes a boolean argument as the basis of its choice.

1if (condition) {
2    statement 1
3    statement 2
4    ....
5}

Example #1:

1int x = 1;
2if (x > 1) {
3    System.out.println("It's greater than 1");
4} else {
5    System.out.println("It's lower than 1 :(");
6}
7
8// Result: It's lower than 1 :(

Example #2:

1int x = 1;
2if (x == 1) System.out.println("Equals to 1");
3if (x < 5) System.out.println("It's lower than 5");
4
5Result: 
6
7// Equals to 1
8// It's lower than 5

The switch () construct

The switch() construct allows to make a choice between multiple alternative execution paths if the choice can be based upon an int value.

Example #1:

 1byte x = 2;
 2switch (x) {
 3    case 1:
 4        System.out.println("You won!");
 5        break;
 6    case 2:
 7        System.out.println("2nd, great!");
 8        break;
 9    case 3:
10        System.out.println("3rd, well done");
11        break;
12    case 4:
13    case 5:
14    case 6:
15        System.out.println("Close enough!");
16    default:
17        System.out.println("You lost");
18        break;
19}
20
21// Result: 2nd, great!

Please note that x is promoted to int. switch() accepts any “assignment compatible” int value.

Objects and Classes

Overloading

Overloading is about reusing the same method name with different arguments and sometimes different return type. Some basic rules apply:

  • In the class that defines de original method, or a subclass of that class, the method name can be reused if the argument list differs in terms of the type of at least one argument.
  • A difference in return type alone is insufficient to constitute an overload and it is illegal.

Example #1:

 1public static void main(String args[]) {
 2
 3    System.out.println( DoubleIt("Hello world") );
 4    System.out.println( DoubleIt(15) );
 5}
 6
 7static String DoubleIt(String name) {
 8    name += " " + name;
 9    return name;
10}
11static String DoubleIt(int id) {
12    id *= 2;
13    return Integer.toString(id);
14}
15
16// Result:
17// 
18// Hello Hello
19// 30

Example #2:

1int MyMethod(int a) { }
2float MyMethod(int a) { } // ERROR!!!. Difference in return type alone
3                          // is insufficient to constitute an overload.

Example #3:

1int MyMethod(int value) { }
2float MyMethod(int number) { } // ERROR!!!. The types should be 
3                               // different, not the variable names.

Invoking Overloaded Methods

Method overloaded names are effectively independent methods. Using the same name is just a convenience to the programmer. Overloaded methods may call one another simply by providing a normal method call with an appropriately formed argument list.

Example:

 1public static void main(String args[]) {
 2    allStars(15);
 3}
 4
 5static void allStars(String name) {
 6    name = "*** " + name + " ***";
 7    System.out.println(name);
 8}
 9static void allStars(int id) {
10    String s = ""+id;
11    allStars(s);
12}

In this example, we have created a method to show names surrounded by stars, we also wanted to accept int values, so we added a method that receives ints but converts them to strings and passes the result to the original string method.

Overriding

When you extend a class to produce a new one, you inherit and have access to all the non-private methods of the original class. When you want to redefine the behaviour of one of these methods to suit your new class, you override the a method to define a new one.

An overriding method replaces the method it overrides. Each method in a parent class can be overridden at most once in any subclass. Overriding methods should have argument lists of identical type and order. The return type of an overriding method must be identical to that of the method it overrides.

Some other special rules apply to overriding methods:

  • The accessibility must not be more restricted than the original method
  • The method must not throw checked exceptions of classes that are not possible for the original method.

Example #1:

 1class Animal {
 2    void saySomething() {
 3        System.out.println("Our kind is unable to speak");
 4    }
 5}
 6
 7class Dog extends Animal {
 8    void saySomething() {
 9        System.out.println("Wof!");
10    }
11
12}
13
14Animal a[] = new Animal[2];
15a[0] = new Animal();
16a[1] = new Dog();
17
18a[0].saySomething();
19a[1].saySomething();
20
21// Result:
22// 
23// Our kind is unable to speak
24// Wof!

Invoking Overridden Methods

It is very useful to be able to invoke an overridden method from the method that overrides it. The keyword super allows to access features of a class “up” in the hierarchy.

Example:

 1class Animal {
 2    void saySomething() {
 3        System.out.println("Our kind is unable to speak");
 4    }
 5}
 6
 7class Dog extends Animal {
 8    void saySomething() {
 9        super.saySomething();
10        System.out.println("Wof!");
11    }
12}
13
14Dog d = new Dog();
15d.saySomething();
16
17// Result:
18// 
19// Our kind is unable to speak
20// Wof!

Constructors and Subclassing

Constructors are not inherited into subclasses; you must define each form of constructor that you require. A class constructor that has no constructors defined in the source is given exactly one constructor. This is the default constructor; it takes no arguments and it is of public accessibility

Java insists that the object is initialised from the top of the class hierarchy downward:

Example:

 1class Test {
 2
 3    static int i = 0;
 4
 5    public static void main(String args[]) {
 6
 7        Terrier t = new Terrier(15);
 8        System.out.println("Count: "+ i);
 9    }
10}
11
12class Animal {
13    Animal (int increment) {
14        Test.i += increment;
15        System.out.println("Animal constructor");
16    }
17}
18
19class Dog extends Animal {
20    Dog (int increment) {
21        super(increment);
22        Test.i += increment;
23        System.out.println("Dog constructor");
24    }
25}
26
27class Terrier extends Dog {
28    Terrier (int increment) {
29        super(increment);
30        Test.i += increment;
31        System.out.println("Terrier constructor");
32    }
33}
34
35// Result:
36// 
37//     Animal constructor
38//     Dog constructor
39//     Terrier constructor
40//     Count: 45

Calling Superclass Constructors

In the case of constructors you also use the keyword super() as with plain methods but you don’t actually need to supply the name of the constructor, just super(arguments).

Example:

 1class Test {
 2
 3    public static void main(String args[]) {
 4
 5        Dog d = new Dog();
 6        Animal a = new Animal(); //     ERROR. No constructor
 7        System.out.println("Legs: "+ d.legs);
 8        System.out.println("Mammal: "+ d.mammal);
 9        System.out.println("Vertebrate: "+ d.vertebrate);
10    }
11    
12}
13
14class Animal {
15
16    int legs = 0;
17    boolean mammal = false;
18    boolean vertebrate = false;
19
20    Animal (int l, boolean m, boolean v) {
21        legs = l;
22        mammal = m;
23        vertebrate = v;
24        System.out.println("Animal constructor");
25    }
26}
27
28class Dog extends Animal {
29    Dog () {
30        super(4,true,true);
31        System.out.println("Dog constructor");
32    }
33}
34
35// Result:
36// 
37//     Animal constructor
38//     Dog constructor
39//     Legs: 4
40//     Mammal: true
41//     Vertebrate: true

As you can see, if you provide a valid constructor, Java doesn’t generate a default constructor anymore. To make the erroneous line valid, you should add the constructor to the Animal class:

1Animal() {}

That also means that if you define a argument-based constructor in the superclass, you won’t be able to create non-argument objects out of any of the subclasses. For example, suppose you want to add a Bird class to the previous example:

1class Bird extends Animal {
2    Bird() {
3        System.out.println("Bird constructor");
4    }
5}

You have to define Animal() {} in the Animal if you want to use this new class like this:

1Bird b = new Bird();

This happens because Java will try to call Animal() before calling Bird(), and Animal() hasn’t been defined. You can specify which constructor of the superclass will be called by using super:

1class Bird extends Animal {
2    Bird() {
3        super(2,false,true);
4        System.out.println("Bird constructor");
5    }
6}

Please note that super() must be the first statement in the Bird() constructor in order to be recognized by the compiler.

Overloading Constructors

Overloading constructors behave just like overloading methods. When you have different constructors and want to call one version from another, you use the keyword this.

Example:

 1class Test {
 2
 3    public static void main(String args[]) {
 4
 5        Animal d = new Animal(true);
 6        System.out.println("Legs: "+ d.legs);
 7        System.out.println("Mammal: "+ d.mammal);
 8        System.out.println("Vertebrate: "+ d.vertebrate);
 9    }
10    
11}
12
13class Animal {
14
15    int legs = 0;
16    boolean mammal = false;
17    boolean vertebrate = false;
18
19
20    Animal (int l, boolean m, boolean v) {
21        legs = l;
22        mammal = m;
23        vertebrate = v;
24        System.out.println("Animal constructor - all arguments");
25    }
26
27    Animal (boolean m) {       
28        this(4,m,true);  
29        System.out.println("Animal constructor - mammal argument");
30    }
31}
32
33// Result:
34// 
35//     Animal constructor - all arguments
36//     Animal constructor - mammal argument
37//     Legs: 4
38//     Mammal: true
39//     Vertebrate: true

The keyword this must also be the first sentence of the constructor. Thus you can’t use super() and this() at the same time in the same constructor. However, super() is implicitly called even if you don’t use it because the parents of the class are always initialised first and before the body of the constructor.

Example #1:

 1class Test {
 2    public static void main(String args[]) {
 3        Reptile r = new Reptile(4);
 4    }
 5}
 6
 7class Animal {
 8    int legs = 0;
 9    boolean cold_blood = false;
10}
11
12class Reptile extends Animal {
13    Reptile (int l) {
14        legs = l;
15        System.out.println("Reptile constructor - leg argument");
16    }       
17}
18
19// Result:
20// 
21//     Reptile constructor - leg argument

The compiler calls the default constructor for Animal and then the argument version of Reptile. Let’s define our own default constructor just to be sure.

Example #2:

 1class Test {
 2    public static void main(String args[]) {
 3        Reptile r = new Reptile(4);
 4    }
 5}
 6
 7class Animal {
 8    int legs = 0;
 9    boolean cold_blood = false;
10    Animal() {
11        System.out.println("Animal constructor - default");
12    }
13}
14
15class Reptile extends Animal {
16    Reptile (int l) {
17        legs = l;
18        System.out.println("Reptile constructor - leg argument");
19    }       
20}
21
22// Result:
23// 
24//     Animal constructor - default
25//     Reptile constructor - leg argument

Oops! We should also be able to define the number of legs for all animals, not only reptiles.

Example #3:

 1class Test {
 2    public static void main(String args[]) {
 3        Reptile r = new Reptile(4);
 4    }
 5}
 6
 7class Animal {
 8    int legs = 0;
 9    boolean cold_blood = false;
10
11    Animal() {
12        System.out.println("Animal constructor - default");
13    }
14
15    Animal (int l) {
16        legs = l;
17        System.out.println("Animal constructor - leg argument");
18    }
19
20}
21
22class Reptile extends Animal {
23    Reptile (int l) {
24
25        legs = l;
26        System.out.println("Reptile constructor - leg argument");
27    }       
28}
29
30// Results:
31// 
32//     Animal constructor - default
33//     Reptile constructor - leg argument

Java keeps calling the default constructor. If we take it out maybe Java will try to use the remaining method Animal (int l):

Example #4:

 1class Test {
 2    public static void main(String args[]) {
 3        Reptile r = new Reptile(4);
 4    }
 5}
 6
 7class Animal {
 8    int legs = 0;
 9    boolean cold_blood = false;
10
11    Animal (int l) {
12        legs = l;
13        System.out.println("Animal constructor - leg argument");
14    }
15
16}
17
18class Reptile extends Animal {
19    Reptile (int l) {
20        legs = l;
21        System.out.println("Reptile constructor - leg argument");
22    }       
23}
24
25// Result:
26// 
27//     ERROR. constructor Animal () not found

So what happened?. We have said before that if at least one constructor is defined, Java doesn’t provide one by default. The error occurs because Reptile(int l) will call Animal() before the body of the constructor is executed as if a super() was there. We don’t need to get back to our Animal() constructor. We only need to customize super() which is currently implicit:

Example #5:

 1class Test {
 2    public static void main(String args[]) {
 3        Reptile r = new Reptile(4);
 4    }
 5}
 6
 7class Animal {
 8    int legs = 0;
 9    boolean cold_blood = false;
10
11    Animal (int l) {
12        legs = l;
13        System.out.println("Animal constructor - leg argument");
14    }
15
16}
17
18class Reptile extends Animal {
19    Reptile (int l) {
20        super(l);
21        System.out.println("Reptile constructor - leg argument");
22    }       
23}
24
25// Result:
26// 
27//     Animal constructor - leg argument
28//     Reptile constructor - leg argument

If the Reptile class hasn’t the Reptile (int l) constructor it wouldn’t be possible to initialise it with that argument as constructors are not inherited as methods. Something like this would also be invalid:

1Reptile r = new Reptile();
2Animal a = new Animal();

Animal() and Reptile() aren’t defined. Java doesn’t supply default constructors because one is already defined.

Because of this special behaviour you must take special care with this() because a super() call is produced automatically:

Example #6:

 1class Test {
 2    public static void main(String args[]) {
 3        Reptile r = new Reptile(4);
 4    }
 5}
 6
 7class Animal {
 8    int legs = 0;
 9    boolean cold_blood = false;
10
11    Animal (int l) {
12        legs = l;
13        System.out.println("Animal constructor - leg argument");
14    }
15    Animal (boolean b) {
16        cold_blood = b;
17        System.out.println("Animal constructor - blood argument");
18    }
19
20}
21
22class Reptile extends Animal {
23    Reptile (int l) {
24        this(true);     // WATCH OUT!!!
25        System.out.println("Reptile constructor - leg argument");
26    }       
27    Reptile (boolean b) {
28        cold_blood = b;
29        System.out.println("Reptile constructor - blood argument");
30    }
31}
32
33// Result:
34// 
35//     ERROR. constructor Animal () not found

There are two ways to solve this problem, we can add a Animal() constructor, or we can make sure that the proper Animal constructor gets called, like this:

Example #7:

 1class Test {
 2    public static void main(String args[]) {
 3        Reptile r = new Reptile(4);
 4    }
 5}
 6
 7class Animal {
 8    int legs = 0;
 9    boolean cold_blood = false;
10
11    Animal (int l) {
12        legs = l;
13        System.out.println("Animal constructor - leg argument");
14    }
15    Animal (boolean b) {
16        cold_blood = b;
17        System.out.println("Animal constructor - blood argument");
18    }
19
20}
21
22class Reptile extends Animal {
23    Reptile (int l) {
24        this(true);
25        System.out.println("Reptile constructor - leg argument");
26    }       
27    Reptile (boolean b) {
28        super(b);
29        System.out.println("Reptile constructor - blood argument");
30    }
31}

You have to understand that each parent class will be initialised somehow and you must tell Java which constructors should be used if you don’t want Java to insist on calling the default constructor all the time.

So, do not forget:

  • In a hierarchy of classes, Java calls the default constructor of each parent class before the body of the current constructor is executed. You cannot prevent Java from calling a constructor for each class but you can tell Java which constructor should be chosen by the parent class using the super() keyword.
  • Java provides a default constructor for each class if NO CONSTRUCTOR is defined by the programmer. If just one constructor is defined, Java doesn’t put up a default constructor anymore.
  • Constructors are not inherited, you must define all the constructors again or reuse the functionality of a parent constructor using super().

Inner Classes

An inner or nested class is the same as any other class but is declared inside some other class or method. When an instance of an inner class is created, there must normally be a pre-existing instance of the outer class acting as context. An inner class and an outer class belong together, the inner class is not just another member of the outer instance.

Example #1:

 1class Test {
 2    class Inner {
 3        Inner () {
 4            System.out.println("Hello World");
 5        }
 6    }
 7
 8    public static void main(String args[]) {
 9        Test.Inner i = new Test().new Inner();
10        
11    }
12}
13
14// Result:
15// 
16//    Hello World

Please note the special syntax used to reference the Inner class from a static context. This is just a shorter approach for this:

1        Test t = new Test();
2        Inner i = t.new Inner();

Example #2:

 1class Test {
 2    String h = "Hello World";
 3
 4    class Inner {
 5        Inner () {
 6            System.out.println(h);
 7            Bye();
 8        }
 9    }
10
11    public static void main(String args[]) {
12        Test.Inner i = new Test().new Inner();
13    }
14
15    void Bye() {
16        System.out.println("Good bye!");
17    }
18}
19
20// Result:
21
22//     Hello World
23//     Bye

Inner classes have access to all the features of the outer class including also methods.

Access modifiers and Static Inner Classes

Inner classes may be marked with standard access modifiers (private, public, protected) (or default if no modifier is specified). Static inner classes do not have any reference to the enclosing instance. Static methods of inner classes may not access non-static features of the outer class.

Example:

 1class Test {
 2    private static String h = "Hello World";
 3
 4    static class Inner {
 5        static void MyMethod () {
 6            System.out.println(h);
 7        }
 8    }
 9
10    public static void main(String args[]) {
11        Test.Inner.MyMethod();
12    }
13
14}
15
16// Result:
17// 
18//    Hello World

The Inner class is an extension of the outer class, so we have access to private members.

Classes defined inside Methods

Anything declared inside a method is not a member of the class but is local to the method. Therefore, classes declared in methods are private to the method and cannot be marked with any access modifier, neither can they be marked as static. However, an object created from an inner class within a method can have some access to the variables of the enclosing method if they declare the final modifier.

Since local variables and method arguments are conventionally destroyed when their method exits, these variables would be invalid for access by inner class methods after the enclosing method exists. By allowing access only to final variables, it becomes possible to copy the values of those variables into the object itself.

Example:

 1class Test {
 2
 3    public static void main(String args[]) {
 4        Hi("Ernest");
 5    }
 6
 7    static void Hi(String name) {
 8
 9        final String h = "Hello World " + name;
10        int j = 5;
11
12        class Inner {
13            void MyMethod () {
14                System.out.println(h);
15                // j++; // ERROR !!!
16            }
17        }
18
19        Inner i = new Inner();
20        i.MyMethod();
21    } 
22
23}

Although the variable name entered the method as normal variable, its contents were added to a final variable.

Anonymous Classes

Anonymous classes can be declared to extend another class or to implement a single interface. If you declare a class that implements a single explicit interface, then it is a direct subclass of java.lang.Object.

Anonymous classes give you a convenient way to avoid having to think up trivial names for classes. They should be small and easy to understand as they do not contain descriptive names.

You cannot define any specific constructor for an anonymous inner class. This is a direct consequence of the fact that you do not specify a name for the class, and therefore you cannot use that name to specify a constructor. Anonymous Class Declarations

1new Identifier() { /* class body */ }

Identifier is a class or interface name. The expression by itself isn’t of much use, it returns a reference to an object that you usually assign to a reference variable of the same type of the identifier:

1Identifier = new Identifier() { /* class body */ };

Of course you can also pass the resulting reference to a method:

1Method(new Identifier() { /* class body *} );

Example:

 1class Test {
 2
 3    public static void main(String args[]) {
 4
 5        Base o = new Base() { 
 6            public void Hi() {
 7                System.out.println("Hi!");
 8            }
 9            public void Bye() {
10                System.out.println("Bye");
11            }
12        };
13
14        o.Hi();
15    }
16
17}
18
19interface Base {
20    public void Hi();
21}
22
23// Result:
24// 
25//    Hi!

Anonymous classes should provide methods defined by the base class or the interface but not brand new methods although they can be declared without compilation errors.

Passing arguments

You can define a constructor inside a inner class, but if you provide arguments, the matching constructor of base class will be invoked:

 1class Test {
 2
 3    public static void main(String args[]) {
 4
 5        Base o = new Base(15) { 
 6            public void Hi() {
 7                System.out.println("Hi!");
 8            }
 9            public void Bye() {
10                System.out.println("Bye");
11            }
12        };
13
14        o.Hi();
15        // o.Bye(); // ERROR. 
16    }
17}
18
19class Base {
20    Base (int i) {
21        System.out.println("I've got "+i);
22    }
23    public void Hi() { }
24}
25
26// Result:
27// 
28//     I've got 15
29//     Hi!

Initialising Anonymous classes

You can’t define constructors for an anonymous class, but you can declare an initialisation block:

 1class Test {
 2
 3    public static void main(String args[]) {
 4        Object o = new Object() { 
 5            {
 6                System.out.println("Init");
 7            }
 8        };
 9    }
10}
11
12// Result:
13// 
14//     Init

This feature is available to all classes, not only anonymous ones.

Exceptions

An exception arises when something that shouldn’t occur under normal circumstances happens. For example, the user try to load a “.jpg” file but the file contents are those of a Win32 EXE file. The user might lost connection to the server because someone kicked the network hub, the user may type “0” as the divisor when running a educational math software, etc.

Using try{} and catch()

Example:

 1int x = (int)(Math.random()*5);
 2int z[] = new int[4];
 3int r = 0;
 4try {
 5    r = 15 / x;
 6    z[x] = r;
 7}
 8catch (ArithmeticException e) {
 9    System.out.println("Arithmetic error: "+e);
10}
11catch (ArrayIndexOutOfBoundsException e) {
12    System.out.println("Array error: "+e);
13}
14finally {
15    System.out.println("Done!");
16}   
17
18// Result if x is between 1 and 3:
19// 
20//     Done!
21// 
22// Result if x is 0:
23// 
24//     Arithmetic error: java.lang.ArithmeticException: / by zero
25//     Done!
26// 
27// Result if x is 4:
28// 
29//     Array error: java.lang.ArrayIndexOutOfBoundsException
30//     Done!

You put the code to deal with an exception that might arise during execution of the try block in a catch block. If there are multiple exception classes that might arise in the try block, then several catch blocks are allowed to handle them.

Using finally

Once execution enters the try block, the code in the finally block will definitely be executed whatever the circumstances. By looking again at the last example you will notice that the finally block is always executed, no matter if the execution of the try{} block is successful or not.

There are a very few circumstances that can prevent execution of the code in a finally block:

  • An exception arising in the finally block itself
  • The death of the thread
  • The use of System.exit()
  • Shutting down the power of the computer where java is running on.

An exception in a finally block can also be handled via a try/catch construct.

Catching Multiple Exceptions

A more specific catch block must precede a more general one in the source. Failure to meet this ordering requirement causes a compiler error. Only one catch block, that is, the first applicable one, will be executed.

Example:

 1try {
 2
 3    File inputFile = new File("d:\\workbench\\test.txt");
 4    FileReader in = new FileReader(inputFile);
 5    int c;
 6    while ((c = in.read()) != -1) {
 7        System.out.print((char)c);
 8    }
 9    in.close();
10    System.out.println();
11
12}
13catch (EOFException e) {   // More specific Exception
14    System.out.println(e);
15}
16catch (IOException e) {    // More general Exception    
17    System.out.println(e);
18}

If we put the catch (IOException e) block first, the compiler will complain because EOFException is a subclass of IOException and more specific Exceptions should precede more general ones.

Declaring your own Exceptions

Declaring user-defined Exceptions is easy but there are some steps that should be carefully observed.

We are going to construct an Exception mechanism for a Age-Counting software as an example. Our Exception will be called AgeException.

Step 1: Create a class that extends Throweable

1class AgeException extends Throwable {}

Step 2: Add 2 constructors. One should receive a String as a single argument.

1class AgeException extends Throwable {
2    String my_error_message;
3    public AgeException() { }
4    public AgeException(String s) {
5        my_error_message = s;
6    }
7}

Step 3: Override the Throwable getMessage() and return the saved String variable.

 1class AgeException extends Throwable {
 2    String my_error_message;
 3    public AgeException() { }
 4    public AgeException(String s) {
 5        my_error_message = s;
 6    }
 7    public String getMessage() {
 8        return my_error_message;
 9    }
10}

Step 4: Declare that your method may throw an Exception:

1static void ageCounter(int age) throws AgeException {}

Step 5: Throw the Exception as needed using this syntax:

1static void ageCounter(int age) throws AgeException {}
2    throw new AgeException("Error message");
3}

Our complete Exception-test program looks like this:

 1class Test {
 2    public static void main(String args[]) {
 3    
 4        try {
 5            ageCounter(250);
 6        }
 7        catch (AgeException e) {
 8            System.out.println();
 9            System.out.println(e);
10        }
11    }
12
13    static void ageCounter(int age) throws AgeException {
14
15        if (age > 200) {    
16            throw new AgeException("Nobody is that old");
17        }
18        for (int i = 0 ; i < age ; i++) {
19            System.out.print(".");
20        }       
21    }
22}
23
24class AgeException extends Throwable {
25    String my_error_message;
26    public AgeException() { }
27    public AgeException(String s) {
28        my_error_message = s;
29    }
30    public String getMessage() {
31        return my_error_message;
32    }
33}

If you want to use a more specific Exception than AgeException, you need to extend AgeException and implement the same functionality. If a method will “throw” more than a single Exception, you must declare each of the Exceptions by separating each one with a comma.

Example:

1static void ageCounter(int age) throws AgeException, IOException {}

Categories of Exceptions

1java.lang.Throwable > java.lang.Exception 
2java.lang.Throwable > java.lang.Exception > java.lang.RunTimeException
3java.lang.Throwable > java.lang.Error

The Checked Exceptions (java.lang.Exception):

They describe difficulties that arise in a correct program, typically those with the environment such as user mistakes or I/O problems. Neither of these problems indicates a programming error. The programmer must write code to handle and recover from these problems.

The Runtime Exceptions (java.lang.Error):

They typically describe program bugs. Because runtime exceptions should never arise in a correct program, you are not required to handle them.

If a Runtime Exception occurs inside a try{} block, the try block will be exited and the application will continue running. If there is a finally{} block, it will also be executed.

Errors:

Errors describe problems that are very unusual and sufficiently difficult to recover from, that you are not required to handle them.

Exceptions and Methods

A method may delegate Exception handling to its caller.

Example:

 1class Care {
 2    public static void main(String args[]) {
 3        try {
 4            readIt("c:\\workbench\\test.txt");
 5        }
 6        catch (IOException e) {
 7            System.out.println(e);
 8        }
 9    }
10
11    public static void readIt(String my_file) throws IOException {
12
13        File inputFile = new File(my_file);
14        FileReader in = new FileReader(inputFile);
15        int c;
16
17        while ((c = in.read()) != -1) {
18            System.out.print((char)c);
19        }
20        in.close();
21        System.out.println();
22    }
23}

As you can see, you don’t need to handle Exceptions in the readIt() method, you just need to declare that the method throws IOException so the caller can handle it.

Exceptions and Overriding

When you extend and override a method, Java insists that the new method cannot be declared as throwing checked exceptions of classes other than those that were declared by the original method.

Example:

1class Disk {
2    void readFile() throws EOFException {}
3}
4
5class FloppyDisk extends Disk {            // ERROR !!!
6    void readFile() throws IOException {}
7}

The new method may only throw the same Exception declared by the overridden method (or a subclass Exception).

Then:

1class Disk {
2    void readFile() throws IOException {}
3}
4
5class FloppyDisk extends Disk {            // OK.
6    void readFile() throws EOFException {}
7}

Modifiers

Modifier Class Variable Method Constructor Free-floating block
public yes yes yes yes no
protected no yes yes yes no
(default)* yes yes yes yes yes
private no yes yes yes no
final yes yes yes no no
abstract yes no yes no no
static no yes yes no yes
native no no yes no no
transient no yes no no no
volatile no yes no no no
synchronized no no yes no yes

* default is not a modifier; it is just the name of the access if no modifier is specified.

Modifiers are Java keywords that give the compiler information about its nature of code, data, or classes.

The Access Modifiers

  • public
  • protected
  • private
  • (default) access level assumed when no modifier is used.

At most 1 access modifier may be used at a time.

Methods may not be overridden to be more private. For example, a protected method can’t be overridden by a default one. But you can override a default method to make it protected or public.

public

It’s the most generous access modifier. A public class, variable, or method may be used in any Java Program without restriction. For example, an Applet is public so that it may be instantiated by browsers. A main() method is also declared public so that it may be invoked from any Java runtime environment.

private

It’s the least generous access modifier. A top level-class may not be declared private. A private variable or method may be used by an instance of the class that declares that variable or method. You can’t invoke methods or variables declared private from outside. Methods or variables that the user of a class won’t use, should be declared private.

Default

Default is not a Java keyword, it is simply the name that is given to the access level that results from non specifying an access modifier.

Example #1:

 1class Test {
 2
 3    public static void main(String args[]) {
 4
 5        Dog terrier = new Dog();
 6        terrier.a = 5;            // Ok.
 7        terrier.b = 5;            // Error. b is private.
 8    }
 9
10
11    class Dog {
12        public int a = 1;   
13        private int b = 1;
14    }
15}

Example #2:

1private class Dog {     /// Error. A top-level class may not be private
2    public int a = 1;
3    private int b = 1;
4}

Example #3:

 1class Canine {
 2
 3    public static void main(String args[]) {
 4
 5        Dog     dog1 = new Dog (); 
 6        Terrier dog2 = new Terrier("taffy", 3);
 7
 8        System.out.println(dog1.info()); // Error. info() is private
 9        System.out.println(dog2.info()); // Error. info() is private
10    }
11}
12
13class Dog {
14    private int age = 2;        
15    String name = "homeless dog";
16
17    private String info() {
18        return name + " is " + age + " years old";
19    }
20}
21
22class Terrier extends Dog {
23    Terrier(String dogs_name, int dogs_age) {
24        age = dogs_age;    // Error. age is private.
25        name = dogs_name;
26    }
27}

If we get rid of the access modifier private at the int and method declaration, the result will be:

1homeless dog is 2 years old
2taffy is 3 years old

Protected

Only variables and methods may be declared protected. A protected feature of a class is available to all classes in the same package, just like a default feature. However, a protected feature of a class is available to all subclasses of the class that owns the protected package.

Example #1:

Consider these two source files:

1// Canine.java
2import my_dog_package.Dog;
3
4class Canine {
5
6    public static void main(String args[]) {
7
8    }
9}
 1// my_dog_package/Dog.java
 2package my_dog_package;
 3
 4class Dog {                   // Error. Must be declared public
 5    int age = 0;        
 6    String name = "homeless dog";
 7
 8    private String info() {
 9        return name + " is " + age + " years old";
10    }
11}

All java source files are related to a “package”, when you work with files within a single working directory, you work under a “default” package, that is distinct from other packages, such as my_dog_package in this case.

If the class Dog was within the Canine.java source file, the class Dog would belong to the “default” package. In order for you to import the Dog class from a “foreign” package, you must declare the class public. But that’s not all, let’s see a more complex example:

Example #2:

We have two files in the current working directory:

  • Canine.java
  • Terrier.java

And other two as part of the my_dog_package:

  • Dog.java
  • Bulldog.java

These ones are related as classes, but let’s just assume their source code versions for simplicity.

Canine.java is our entry point-file, where the main() is declared Terrier and Bulldog are subclasses of Dog, but Bulldog is within the same package of the superclass; Dog. Terrier is within the default package where Canine is contained.

 1// Canine.java
 2import my_dog_package.Dog;
 3import my_dog_package.Bulldog;
 4
 5class Canine {
 6
 7    public static void main(String args[]) {
 8
 9            Dog     anydog  = new Dog (); 
10            Bulldog spike = new Bulldog("Spike",5); 
11            Terrier taffy = new Terrier("Taffy",3);
12
13            System.out.println(anydog.info()); 
14            System.out.println(spike.info());
15            System.out.println(taffy.getTerrierAge());
16            // System.out.println(spike.getAge()); // WRONG!!
17
18    }
19}

getAge() defined within the Dog class is protected so it can only be invoked from the Dog class itself or from subclasses of Dog, within or outside my_dog_package.

 1// my_dog_package/Dog.java
 2
 3package my_dog_package;
 4
 5public class Dog {
 6
 7    protected int age = 0;      
 8    protected String name = "homeless dog";
 9
10    public String info() {
11        return name + " is " + age + " years old";
12    }
13
14    protected int getAge() {  // SEE INFO !!
15        return age;
16    }
17
18}

By declaring getAge() protected, we mean:

  • Allow access from the class itself.
  • Allow access from subclasses within the package
  • Allow access from subclasses outside the package

If no modifier keyword was used for the getAge() declaration, then the access level would be “default” which means:

  • Allow access from the class itself.
  • Allow access from subclasses within the package.
  • DISALLOW access from subclasses outside the package.

If getAge() was default, we would have no problem using this method on Bulldog.java as it is contained in the my_dog_package. But Terrier is outside the my_dog_package, and default access level doesn’t allow access to getAge().

If getAge() was public, there wouldn’t be any problem at all and the spike.getAge() will compile at Canine.java. However, the programmer may want that each subclass of Dog declares its method for age fetching disallowing the user of the class from invoking the inherited version of the getAge() method.

 1// my_dog_package/Bulldog.java
 2package my_dog_package;
 3
 4public class Bulldog extends Dog {
 5    public Bulldog(String dogs_name, int dogs_age) {
 6        age = dogs_age;    
 7        name = dogs_name;
 8    }
 9    public int getBulldogAge() {  // TAKE AT LOOK AT THIS!!
10        return (getAge());
11    }
12}

If you see the above version of getBulldogAge() and the homologous version at Terrier.java you will notice that here we have declared it public. The Terrier’s version has no access modifiers, it is just default. Why is that so?. If getBulldogAge() had default access, it wouldn’t be accessible from the Canine class, as both are in different packages.

 1// Terrier.java
 2import my_dog_package.Dog;
 3
 4public class Terrier extends Dog {
 5    Terrier(String dogs_name, int dogs_age) {
 6        age = dogs_age;    
 7        name = dogs_name;
 8    }
 9    int getTerrierAge() {   // TAKE AT LOOK AT THIS!
10        return (getAge());
11    }
12}

In this case, getTerrierAge() has default access and as it is within the “default” package where Canine is also contained it is no necessary to provide a higher level of access. getTerrierAge() could be also be defined as protected, that will allow its invoking from packages outside the “default one”.

Special Modifiers

These are the special modifiers in Java:

  • final
  • abstract
  • native
  • transient
  • synchronized
  • volatile

final

Final features may not be changed. A final class may not be subclassed, a final variable may not be modified and a final method can’t be overridden. Thus, final features only apply to:

  • methods (except the constructor method)
  • variables
  • classes

Example #1:

1final int x = 15;
2x++;                // WRONG. You can't modify a final variable

Example #2:

 1class Dog {
 2    int age = 0;
 3    final void getAge() {
 4    }
 5}
 6
 7class Terrier extends Dog{
 8    void getAge() {  // WRONG. Final method can't be overridden
 9    }
10}

Example #3:

1class Dog {
2    final Dog() {    // WRONG. Constructors may not be defined 
3    }                // final.
4}

Example #4:

1final TextField my_tf = new TextField();
2
3my_tf.setText("Felix");             // LEGAL
4my_tf = new TextField("Richard");   // WRONG. Can't modify a final variable

Examine the example above carefully. mt_tf is declared final, so it can’t be modified. When you invoke the method setText() you are not modifying the variable itself but the object that is being referenced by that variable. If we try to create a new object, and save its reference to my_tf, the compiler will show an error message because your are trying to change the contents of a final variable.

abstract

This modifier may be applied only to:

  • Classes
  • Methods

An abstract class may not be instantiated. An abstract method can’t be invoked, a subclass must override it and provide a non-abstract version of it. The compiler insists that a class must be abstract if:

  • The class has one or more abstract methods.
  • The class inherits one or more abstract method (from an abstract parent) for which it does not provide implementations.
  • The class declares that it implements an interface but does not provide implementations for every method of that interface.

Basic rules for static methods:

  • A static method may only access the static data of its class; it may not access non-static data.
  • A static method may only call the static methods of its class; it may not call non-static methods.
  • A static method has no this
  • A static method may not be overridden to be non-static.

Abstract is the opposite of final. A final class, for example, may not be subclassed; an abstract class must be subclassed.

Example #1:

 1class Test {
 2    public static void main(String args[]) {
 3        Car compact = new Car();  // WRONG. Car is abstract. 
 4        Ford focus = new Ford();  // OK.                 
 5    }
 6}
 7
 8abstract class Car {}
 9
10class Ford extends Car {}

Example #2:

 1class Car {  // WRONG. The class should also be abstract
 2    String color = "white";
 3    abstract void setColor(String car_color);
 4    abstract String getColor() {  // WRONG. Abstract methods 
 5        // Do nothing           // should have no body.
 6    }
 7}
 8
 9class Ford extends Car {
10    void setColor(String car_color) {
11        color = car_color;
12    }
13    String getColor() {
14        return color;
15    }
16}

Basically, when abstract features are used, the only case where the class doesn’t need to be abstract is when a non-abstract method has been declared for each abstract method defined in the superclass.

static

The static features belong to a class and are not associated with the instance of the given class. They may applied to:

  • methods
  • variables
  • free-floating blocks

Example #1:

 1class Test {
 2    public static void main(String args[]) {
 3        Car honda = new Car();
 4        honda.wheels++;
 5        Car bmw = new Car();
 6        bmw.wheels++;
 7        System.out.println(bmw.wheels);
 8    }
 9}
10
11class Car {
12    static int wheels = 0;
13}
14
15// Result: 2

The variable wheels is initialised when the class Car is loaded. No matter how many instances of Car we create, wheels is always the same, so when refering to honda.wheels or bmw.wheels, it is same “static” variable.

Example #2:

 1class Care {
 2
 3    public static void main(String args[]) {
 4        Car honda = new Car();
 5        honda.setWheels(4);
 6        honda.getWheels();
 7
 8        Car.getWheels();
 9        Car.setWheels(3);    // A non-static method cannot be 
10                             // referenced from a static context.       
11    }
12}
13
14class Car {
15    static int wheels = 0;
16    static void getWheels() {
17        System.out.println(wheels);
18    }
19    void setWheels(int car_wheels) {
20        wheels = car_wheels;
21    }
22}

Example #3:

 1class Vehicle {
 2    static int cars = 5;
 3    int bikes = 8;
 4
 5    public static void main(String args[]) {
 6        cars++;
 7        bikes++ // Error. bikes is a non-static variable 
 8        Vehicle var = new Vehicle();
 9        var.bikes++;
10        System.out.println("cars = "+cars+", bikes = "+var.bikes);
11    }
12
13    static {
14        cars++;
15        // bikes++; // Error. bikes is a non-static variable
16    }
17}
18
19// Result: cars = 7, bikes = 9

The static {} free-floating block is executed when the class is loaded in order of appearance. It is executed only once, no matter how many instances you create of the class that contains the free-floating code. Non-static features are available to static contexts when you instantiate the class that contains them. Hence, “bikes++” is illegal, but if we create an instance of Vehicle, we can access bikes++ without trouble, but that “bikes” belongs to that object that we have just created. If we create another instance of Vehicle, we will have other different bikes variable. A static feature basically behaves like a variable or a function of a conventional procedural programming language. Java is just so object oriented that the static features look odd and we have a hard time trying to understand something that is the rule among most of the other programming languages.

Example #4:

1class Car {
2    static void getColor() {
3    }
4}
5
6class Ford extends Car {
7    void getColor() { // Error. static methods can't be overridden
8    }
9}

A class acts like a prototype for objects that you will create in the future. Most of the time, you work with variables and methods that are “described” in the class but that actually EXIST for real in objects (instances of classes). When you declare a static method, you are saying, “this is no prototype for anything, this is THE METHOD itself”. Therefore, you can’t override a static method, because overriding is an object-oriented feature. When you override a non-static method, you actually override the definition for a method that will only exist once instances of the class that contain them are created. You can naturally access a static variable or method contained in a class that also has non-static features, but those static features aren’t replicated for every instance.

transient

This modifier applies only to variables. A transient variable is not stored as part of its object’s persistent state. Many objects can have their state saved to files or sent through the network. The variable state is part of the transmitted/saved object. The transient modifier disallows the variable contents from being “serialized” or included as a part of the object.

Example:

1class Car implements Serializable {
2    private String model;
3    private String customer_name;
4    private transient int customer_credit_Card;
5}

synchronized

It is used in multi-threaded programs

volatile

Allows variables to be modified in an asynchronously way. This modifier is of interest in multiprocessor environments.

Converting and Casting

Primitives

Conversion of primitive types may occur in these contexts:

  • Assignment
  • Method call
  • Arithmetic promotion
  • Explicit casting

Widening conversions

Assignment

General rules for primitive assignment conversions:

  • A boolean may not be converted to any other type.
  • A non-boolean may be converted to another non-boolean type, provided the conversion is a widening conversion.
  • A non-boolean may not be converted to another non-boolean type, if the conversion would be a narrowing conversion.

Example #1:

1int i = 5;
2float j = i;

Method call

Widening conversion takes place on method calls as on assignments. You can pass to a method any primitive narrower than the expected one. Implicit casting will naturally occur.

For instance:

 1public static void main(String args[]) {
 2    byte x = 126;
 3    System.out.println( DoIt(x) );
 4}
 5
 6static String DoIt(int a) {
 7    return "I've received an int of value "+a;
 8}
 9
10// Result: I've received an int value of 126

The method DoIt(int a) excpects an int, but you can throw a char, byte or short there, the value will be promoted to int and the method will IN FACT receive an int.

This special behavior ocurs if a method to handle a narrower type hasn’t been declared. If you declare a method to handle bytes, then that method will “catch” the call. This is an OOP feature called overloading.

Example:

 1public static void main(String args[]) {
 2    byte x = 126;
 3    System.out.println( DoIt(x) );
 4}
 5
 6static String DoIt(int a) {
 7    return "I've received an int of value "+a;
 8}
 9static String DoIt(byte a) {
10    return "I've received a byte of value "+a;
11}
12
13// Result: I've received a byte value of 126

If the argument type if wider than expected, no implicit casting will occur and you will need to perform an explicit cast:

 1public static void main(String args[]) {
 2    float x = 1.26f;
 3    System.out.println( DoIt( (int)x ) );
 4}
 5
 6static String DoIt(int a) {
 7    return "I've received an int of value "+a;
 8}
 9
10// Result: I've received an int value of 1

Last example:

 1public static void main(String args[]) {
 2    char x = 'A';
 3    System.out.println( DoIt(x) );
 4}
 5
 6static String DoIt(int a) {
 7    return "I've received an int of value "+a;
 8}
 9static String DoIt(byte a) {
10    return "I've received a byte of value "+a;
11}
12
13// Result: I've received an int value of 65

As you can see, there’s no method to catch char types so the value is promoted to int and caught by DoIt(int a).

Arithmetic Promotion

Arithmetic promotion happens when narrower types need to be promoted to wider types in order to make sense in an operation among other wider types.

Basic rules for binary operators and most of the unary operators:

  • All arithmetic expressions are promoted to the wider of the operands.
  • The promotion is at least to int, even if no int operand appears.

These rules don’t apply to the unary operators: ++ and – and assignment operators.

Example #1:

1byte x = 1;
2x++;          // Ok. x is now equal to 2.
3x = x + 1;    // Error. Expression x + 1 is promoted to int.

Example #2:

1byte a = 1;
2byte x = 23;
3x <<= a;      // Ok. x is now equal to 46.
4x = x << a;   // Error. Expression x << a is promoted to int.

Example #3:

1char a = 5;
2short x = 3;
3x *= a;       // Ok. x is now equal to 15.
4x = x * a;    // Error. Expression x = x * a is promoted to int.

Example #4:

1byte a = 15;
2a = -a;       // Error. -a is promoted to int.
3a = ~a;       // Error. ~a is promoted to int.

Example #5:

1float a = 1.0f;
2int b = 15;
3int x = a * b;       // Error. Expression is promoted to float.
4int x = (int)(a*b);  // Ok. We cast the float result back to int.

Primitives and Casting

Casting means explicitly telling Java to make a conversion. A casting operation may widen or narrow its argument. To cast a value, you need to precede it with the name of the desired type enclosed within parentheses:

1byte x = 15;
2int r = (int)(x * 3);

Booleans cannot be casted. Don’t bother with them, stuff like this doesn’t work:

1byte a = 1;
2boolean status = a;

Narrowing runs the risk of loosing information, in fact, many times you know that you are going to loose information and it is important to know which part information is going to be loosen and naturally, what information will be kept.

Casting to (byte)

Within the range:

A byte is signed and can hold a range of numbers from -128 to 127. If the value of the widest type is within this range, conversion won’t produce unexpected results.

Example:

1int a = -128;
2byte x = (byte)a;
3
4float a = -128.0f;
5byte x = (byte)a;
6
7// Result in both cases: -128

Outside the range but within the signed byte range:

If the value is between 128 and 255, it will be converted to binary and then to the byte decimal representation of that binary pattern. In fact, this bit-level interpretation always occurs, but you have to be conscious about it for this special case.

Example:

1int a = 128;
2byte x = (byte)a;
3
4Result: -128

The bit pattern for 128 is 10000000, but 10000000 is considered to be a signed byte. Thus 10000000 is equal to -128. The next binary number, 10000001, equals to -127. If byte was unsigned, as in C/C++, the decimal value of 1000001 would be 129.

Example:

1int a = 129;
2byte x = (byte)a;
3
4// Result: -127

Outside the signed byte range:

If the value is greater than 255 or lower than -128, the lower byte of the value is kept and the rest is just thrown away.

Example #1:

 1int a = 257;
 2byte x = (byte)a;
 3
 4// Result: 1
 5// 
 6// 257 = [00000000] [00000000] [00000001] [00000001]
 7//                  32-bits int value      
 8// 
 9//                                    1 = [00000001]
10//                                   8-bits byte value 

Example #2:

 1int a = -135;
 2byte x = (byte)a;
 3
 4// Result: 121
 5//             
 6//         -135 = [11111111] [11111111] [11111111] [01111001]
 7//                          32-bits int value      
 8// 
 9//                                           121 = [01111001]
10//                                           8-bits byte value
11// 

Casting to (char)

Within the range:

A char is 16-bits wide unsigned type that holds values between 0 and 65535. Conversion will perform as expected if the value is within the valid range.

Example:

1int a = 65535;
2char x = (char)a;
3
4// Result: 65535

Outside the range or negative:

If the value is outside the range because it is lower than 0 or greater than 65535, then the lower 2 bytes will be kept.

Example #1:

 1int a = 65539;
 2char x = (char)a;
 3
 4// Result: 3
 5// 
 6//   65539 = [00000000] [00000001] [00000000] [00000011]
 7//                  32-bits int value      
 8// 
 9//                           3 = [00000000] [00000011]
10//                                16-bits char value

Example #2:

 1int a = -1;
 2char x = (char)a;
 3
 4// Result: 65535
 5// 
 6//      -1 = [11111111] [11111111] [11111111] [11111111]
 7//                    32-bits int value      
 8// 
 9//                       65535 = [11111111] [11111111]
10//                                16-bits char value

Casting to (short) and other signed integer values

Values between -32768 and 32767 are converted flawlessly as they are within the valid range. If the value is lower or greater, the lower 2 bytes of the value will be kept to conform a short value. The behaviour is the same as byte casting.

Other integer values will also behave as expected according to what we’ve seen at the byte examples.

Casting floats or doubles to narrower types

On some programming languages, conversion from floating-point numbers to decimals “round” the value. This is not the Java case, the integer part is kept and the rest is thrown away.

Example:

1double a = 1.9999999;
2int x = (int)a;
3
4Result: 1

Object Reference Conversion

Object reference conversion takes place in:

  • Assignment
  • Method call
  • Casting

(There is no arithmetic promotion)

Assignment

Object reference assignment conversion happens when you assign an object reference value to a variable of a different type. There are 3 general kinds of object reference type:

  • A class type, such as Button or TextField
  • An interface type, such as Clonable or LayoutManager
  • An array type, such as int[][] or TextArea[]

Conversion rules for implicit casting on this context:

1OLD_Type a = new OLD_Type;
2NEW_Type b = a;     
OLD_Type = Class OLD_Type = Interface OLD_Type = Array
NEW_Type = Class OLD_Type must be a subclass of NEW_Type. NEW_Type must be Object. NEW_Type must be Object.
NEW_Type = Interface OLD-Type must implement interface NEW_Type OLD_Type must be a subinterface of NEW_Type NEW_Type must be Cloneable or Serializable
NEW_Type = Array Compiler ERROR Compiler ERROR OLD_Type must be an array of some object reference type that can be converted to what- ever NEW-Type is an array of

Conversions are usually permitted when NEW_Type is a “up” in the inheritance hierarchy.

Example #1, Subclass / Class implicit casting:

 1class Animal {}
 2class Dog extends Animal {}
 3
 4Dog a = new Dog();         
 5Animal b = a;              // Ok. Animal is the superclass of Dog.
 6
 7Animal x = new Animal();
 8Dog y = x;                 // Error. Dog is a subclass of Animal.
 9
10Animal l = new Animal();   // Ok. Object is above Animal in the Class
11Object m = l;              // hierarchy.

A class may be converted to a class type or to an interface type. If converting to a class type, the new type must be a superclass of the old type.

Example #2, Class / Interface implicit casting:

 1interface Eatable {}
 2
 3class Animal {}
 4class Dog extends Animal implements Eatable {}
 5
 6Eatable e;
 7Animal a;
 8Object o;
 9
10e = new Dog();      // Ok. Dog implements Eatable 
11e = new Animal();   // Error. Animal doesn't implements Eatable
12
13a = e;     // Error. An interface may not be converted to a Class 
14o = e;     // Ok. An interface may be converted to an Object.

A class may be converted to a class type or an interface type. If converting to a interface type, the old class must implement the interface.

An interface type may only be converted to an interface type or to Object. If the new type is an interface, it must be a superinterface of the old type.

Example #3, Arrays:

 1class Animal {}
 2class Dog extends Animal {}
 3
 4Animal canines[] = new Animal[25];
 5for (int i = 0 ; i < canines.length ; i++) {
 6    canines[i] = new Animal();
 7}
 8
 9Object some_canines[] = new Object[25];
10Animal dangerous_canines[] = new Animal[25];
11Dog friendly_canines[] = new Dog[25];
12    
13some_canines = canines;
14dangerous_canines = canines;
15some_canines[5] = canines [5];
16friendly_canines = canines;       // Error. 
17friendly_canines[5] = canines[5]; // Error. Incompatible types  

An array me be converted to the class Object, to the interface Clonable or Serializable, or to an array. Only an array of object references may be converted to an array, and the old element type must be convertible to the new element type.

Method-call

The rules for method-call conversion are the same as the rules for assignment conversion.

Object Reference Casting

Compile-time rules for object reference casting:

OLD_Type = Non-final class OLD_Type = A final class OLD_Type = Interface OLD_Type = Array
NEW_Type = Non-Final Class OLD_Type must extend NEW_Type or vice versa OLD_Type must extend NEW_Type Always OK NEW_Type must be Object
NEW_Type = Final Class NEW_Type must extend OLD_Type OLD_Type and NEW_Type must be the same class NEW_Type must implement Cloneable or Serializable Compiler ERROR
NEW_Type = Interface Always OK OLD_Type must implement interface NEW_Type Always OK Compiler ERROR
NEW_Type = Array OLD_Type must be Object not NEW_Type Compiler ERROR Compiler ERROR OLD_Type must be array of some type that can be cast to whatever NEW_Type is an array

Example #1, Class / Subclass casting:

 1class Animal {}
 2class Dog extends Animal {}
 3class Cat extends Animal {}
 4
 5Animal a = new Animal();
 6Dog d = new Dog();
 7Cat c = new Cat();
 8
 9d = (Dog)a;    // Ok. Compiles but produces a runtime Exception.
10d = (Dog)c;    // Error. Won't compile.
11a = (Animal)c; // Ok. Compiles and RUNS.

When both OLD_Type and NEW_Type are classes, one class must be a subclass of the other (it doesn’t matter which one).

Example #2, Interface / Class casting:

 1class Animal implements Eatable {}
 2class Dog extends Animal {}
 3interface Eatable {}
 4
 5Eatable e;
 6Animal a; 
 7Dog d = new Dog();
 8
 9e = d;       // Ok. Eatable is implemented by Animal and hence by Dog. 
10d = e;       // Error. An interface cannot be converted to a class.
11d = (Dog)e;  // Ok. Compiles/RUNS The class held by e is convertible.

Implicitly converting an interface to a class is never allowed. You need to use an explicit cast to force the compiler to do something where unexpected results may arise.

Example #3, Arrays:

1class Animal {}
2class Dog extends Animal {}
3
4Dog terriers[] = new Dog[25];
5Animal canines[] = new Animal[25];
6
7canines = terriers;        // Ok. Animal is more general than Dog
8terriers = (Dog[])canines; // Ok. Tell the compiler it's Ok.

An array cast is legal if casting the array element types is legal (and if the element types are references, not primitives). If we remove the line canines = terriers; the program will compile but an Exception will be raised at runtime because when we say that terriers = (Dog[])canines; we tell the compiler: “don’t worry, they will be compatible types”. They certainty won’t, as terriers belong to the Dog class and canines to the Animal class. The Dog class is a subclass of Animal.

Threads

A Thread is a lightweight approach for running different pieces of code at the same time without needing to call the operating system to start new processes.

Threads are commonly set up by subclassing the Thread class or by implementing the Runnable interface and then passing the object reference to a Thread object. In both cases, a run() method should be provided by the programmer. To make a Thread object eligible to run, you must invoke the start() method. Once the processor has time, the Java’s thread scheduler will execute the code contained in run().

Please note that run() must be public as it cannot be overridden with weaker access level.

Subclassing Thread

 1class Test {
 2
 3    public static void main(String args[]) {
 4        
 5        MyThread t = new MyThread();
 6        t.start();
 7        for (int i = 0 ; i < 10 ; i++) {
 8            Test.Wait(1);
 9            System.out.print(".");
10        }
11
12    }
13
14    static void Wait(int s) {
15        s *= 1000;
16        long time = System.currentTimeMillis(); 
17        while (System.currentTimeMillis()-time < s) { } 
18    }
19
20}
21
22class MyThread extends Thread {
23    public void run() {
24        Test.Wait(5);   
25        System.out.println(" A threat is bothering!");
26        Test.Wait(2);
27        System.out.println(" Here I am again!");
28    }
29}
30
31// Result:
32// 
33//     .... A threat is bothering!
34//     .. Here I am again!
35//     ....

Implementing Runnable

 1class Test {
 2
 3    public static void main(String args[]) {
 4        
 5        MyObject or = new MyObject();
 6        Thread t = new Thread(or);
 7        t.start();
 8        for (int i = 0 ; i < 10 ; i++) {
 9            Test.Wait(1);
10            System.out.print(".");
11        }
12    }
13
14    static void Wait(int s) {
15        s *= 1000;
16        long time = System.currentTimeMillis(); 
17        while (System.currentTimeMillis()-time < s) { } 
18    }
19}
20
21class MyObject implements Runnable {
22    public void run() {
23        Test.Wait(5);   
24        System.out.println(" A threat is bothering!");
25        Test.Wait(2);
26        System.out.println(" Here I am again!");
27    }
28}
29
30// Result:
31// 
32//     .... A threat is bothering!
33//     .. Here I am again!
34//     ....

The end of a Thread

When the run() methods returns, the thread has finished its task and is considered dead. Once a thread is dead, it may not be started again. However, as a Thread is basically an Object, you still may call its other methods.

Thread States

A thread may show many states:

  • Running
  • Ready
  • Suspended, Sleeping or Blocked (Waiting states)
  • Dead

Running

A thread that is running is in execution of the code contained in the run() method. A loop or some “waiting routine” as shown in the previous examples don’t prevent threads from being in the running state.

Ready

In this state, the thread is “ready” to execute. As soon as the Java’s thread scheduler finds a chance, the thread in this state will be executed (or will continue execution). For example, when a Thread is started via start(), the Ready state is set. Once conditions allow the scheduler to execute the thread, run() will be invoked. This may not happen immediately, if the CPU is busy or there are some heavy Threads already running that don’t free the CPU up.

Suspended (Deprecated)

Sleeping

A sleeping thread passes time without doing anything and without using the CPU. A sleeping Thread is PUT TO SLEEP via the sleep() method. A loop that is expecting some value to continue program flow doesn’t put a thread in the sleeping state.

Example:

 1class Test {
 2
 3    public static void main(String args[]) {
 4
 5        Thread t = new MyThread();
 6        t.start();
 7        
 8        for (int i = 0 ; i < 50 ; i++) {
 9            try {
10                Thread.sleep(50);
11            }
12            catch (InterrupedException e) { }
13            System.out.print(".");
14        }
15    }
16}
17
18class MyThread extends Thread {
19    public void run() {
20        try {
21            sleep(1000);
22        }
23        catch (InterruptedException e) { }
24        System.out.println(" I ran!");
25    }
26}
27
28// Result:
29// 
30// 
31//     ................... I ran!
32//     ...............................

The method stop() is static and causes the thread from where it is called to be suspended for a given amount of time:

1public static void sleep(long milliseconds) throws InterruptedException
2
3public static void sleep(long milliseconds, int nanoseconds) throws InterruptedException

When the thread stops sleeping, it moves to the Ready state. It is also possible to move a sleeping thread to the Ready status implicitly via the interrput() method. This action raises a “InterruptedException”. Never forget that as sleep() is static, the current object upon it is invoked, is not affected.

 1class Test {
 2
 3    public static void main(String args[]) {
 4
 5        Thread t = new MyThread();
 6
 7        t.start();
 8        try {
 9            Thread.sleep(1000);
10        } catch (InterruptedException e) {}
11        t.interrupt();
12    }
13}
14
15class MyThread extends Thread {
16    public void run() {
17        System.out.println("I've started.");
18        try {
19            System.out.println("Now, I'll sleep");
20            sleep(8000);
21            System.out.println("I'll wake up now");
22        }
23        catch (InterruptedException e) { 
24            System.out.println("You woke me up!");
25        }
26        System.out.println("All done!");
27    }
28}
29
30// Result:
31// 
32//     I've started.
33//     Now, I'll sleep
34//     You woke me up!
35//     All done!

The message “I’ll wake up now” is never shown as the sleeping thread is interrupted and an Exception is raised.

Blocked

Methods that usually perform input or output have to wait for some occurrence in the outside world before they can proceed; this behaviour is known as blocking. Consider this example remembering that we have used the API we have studied so far:

 1class Test {
 2
 3    static boolean done = false;
 4
 5    public static void main(String args[]) {
 6        Thread t = new MyThread();
 7        t.start();
 8        System.out.print("Type your name: ");
 9        try {
10            Thread.sleep(5000);
11            while (!done) {
12                System.out.print("\nWhat are you waiting");
13                System.out.print(" for?\nChristmas?");
14                System.out.print("\nType your name: ");
15                Thread.sleep(5000);
16            }
17        } catch (InterruptedException e) {}
18        System.out.println("All Done!");
19    }
20}
21
22class MyThread extends Thread {
23    public void run() {
24
25        try {
26            int c = System.in.read();
27        }
28        catch (IOException e) { 
29            System.out.println("I/O Trouble!");
30        }
31        System.out.println("Ok, wait up...");
32        Test.done = true;
33    }
34}
35
36// Result: (I try to write my name but I seem to be too slow)
37// 
38//     C:\temp>java Test
39//     Type your name: Er
40//     What are you waiting for?
41//     Christmas?
42//     Type your name: Erne
43//     What are you waiting for?
44//     Christmas?
45//     Type your name: Ernest
46//     Ok, wait up...
47//     All Done!

When a thread exist a blocking method, it moves to the Ready state.

Ready

In this state, the thread is “ready” to execute. As soon as the Java’s thread scheduler finds a chance, the thread in this state will be executed (or will continue execution). For example, when a Thread is started via start(), the Ready state is set. Once conditions allow the scheduler to execute the thread, run() will be invoked. This may not happen immediately, if the CPU is busy or there are some heavy Threads already running that don’t free the CPU up.

Dead

The run() method returned. There’s no way to step back from this state.

Thread Priorities and Yielding

Thread Priorities

Every thread has a priority, an integer from 0 to 10; threads with higher priority get preference over threads with lower priority. There’s no guarantee about how this priority is implemented. Sometimes, the highest priority thread may consume all the CPU time until it finishes, and then the CPU will execute the next available thread in order of priority. On some Java implementations and/or operating systems, the highest priority thread is executed “more often”, and from time to time, other lower priority threads also get a chance to execute. Threads that take too long to execute or do very intensive CPU tasks, should “free” the CPU from time to time using the yield() method.

Examples:

1Thread t = new MyThread();
2t.setPriority(9);
3t.setPriority(Thread.MAX_PRIORITY);
4t.setPriority(Thread.MIN_PRIORITY);
5t.setPriority(Thread.NORM_PRIORITY);
6int c = t.getPriority();

Yielding

A call to the yield() method causes the currently executing thread to move to the Ready state if the scheduler is willing to run any other thread in place of the yielding thread.

Example:

1for (int i = 0 ; i < 640 ; i++) {
2    for (int j = 0 ; j < 480 ; j++) {
3        // CalculatePixel(i,j);
4        yield();
5    }
6}

Most schedulers do not stop the yielding thread from running in favour of a thread of lower priority, so a thread of MAX_PRIORITY may not give a chance of execution to a thread of NORM_PRIORITY just because of yield().

Object Locking and Synchronization

Every object has a lock. A lock is controlled by, at most, one single thread. The lock controls access to the object’s synchronized code. A thread that wants to execute an object’s synchronized code must first attempt to acquire that object’s lock. If the lock is available -that is, if it is not already controlled by another thread- then all is well. If the lock is under another thread’s control, then the attempting thread goes into the Seeking Lock state and only becomes ready when the lock becomes available. When a thread that owns the lock passes out of the synchronized code, the thread automatically gives up the lock.

The synchronized keyword

Synchronization is useful when you have a single object that is used by more than one thread. When you apply the synchronized keyword to a block of code (usually a method) you will be sure that once a thread starts to execute that synchronized block, the object containing that block will remind blocked for all other threads until the synchronized block is exited or some other condition specified by the programmer arises.

Consider this class:

 1class CarDealer {
 2
 3    int available_cars = 1;
 4
 5    synchronized boolean buyCar(int mIncome, String car) {
 6        boolean sell = false;
 7        if (available_cars > 0) {
 8            available_cars--;
 9
10            if (mIncome > 1500) sell = true;
11            // Consult banks, validate credit and so on...
12            // ...
13            if (car.equals("jaguar") && mIncome < 5000) 
14                sell = false;
15        }
16        if (!sell) available_cars++;
17        return sell;
18    }
19}

A buyCar() method sells cars to customers based on their monthly income and the kind of car they want. The method just confirms if it was possible or not to sell the card returning a boolean value. It also keeps track of the available cars there are in stock.

Suppose that one customer thread wants to buy a jaguar:

1car_dealer_refernce.buyCar(2000,"jaguar");

Our polite BuyCar() method first reserves a car (subtracting a car from the stock) and then verifies the monthly income and checks this information with banks, etc. Let’s just imagine that the operation takes a while. So what about if a second customer thread wants to buy a car?. At that moment, there wouldn’t be more available cars and a false answer will be returned. The first buyer didn’t have enough money to buy a jaguar so a false answer was given to him too. So the BuyCar() method couldn’t sell its last car to anyone. BuyCar() had to tell the second customer that there was a first customer interested in a car, but that he could wait to see if the sale was completed or not.

The synchronized keyword helps us to step out of this problem. When the first customer asks for a car, he locks the object. When the second customer asks for a car, he doesn’t receive an answer right away, instead, he waits until the first customer finishes his transaction. This is the same as waiting for a String to come out of a Socket stream. The call just stays there, waiting, until there is an answer.

When a thread reaches a synchronized block of a class:

  • All synchronized features of an object based on the class that has a synchronized block become blocked to all other threads.
  • When the thread exists the synchronized block, the object becomes unblocked for any other thread including the current one.

To illustrate the last example, we have produced a complete working program:

 1class Test {
 2
 3    static boolean done = false;
 4
 5    public static void main(String args[]) {
 6
 7        CarDealer cd = new CarDealer();
 8
 9        OtherCustomer oc = new OtherCustomer(cd);
10        Thread t = new Thread(oc);
11        t.start();
12
13        try {
14            // Thinking, should I sell or not?
15            Thread.sleep(1*1000);
16        } catch (InterruptedException e) { }
17
18        System.out.print("I am looking ");
19        System.out.print("for a car...\n");
20
21        if (cd.buyCar(6000,"ford")) {
22            System.out.println("Me: I've got a new car");
23        } else {
24            System.out.println("Me: I'll keep walking");
25        }
26    }
27
28
29}
30
31class OtherCustomer implements Runnable {
32    CarDealer cd;
33
34    OtherCustomer(CarDealer cd) {
35        this.cd = cd;
36    }
37
38    public void run () {
39
40        System.out.print("Other customer is looking ");
41        System.out.print("for a car...\n");
42        if (cd.buyCar(500,"jaguar")) {
43            System.out.println("OC: I've got a new car");
44        } else {
45            System.out.println("OC: I'll keep walking");
46        }
47    }
48}
49
50class CarDealer {
51
52    int available_cars = 1;
53
54    synchronized boolean buyCar(int mIncome, String car) {
55        boolean sell = false;
56        if (available_cars > 0) {
57            available_cars--;
58
59            if (mIncome > 1500) sell = true;
60            if (car.equals("jaguar") && mIncome < 5000) 
61                sell = false;
62            try {
63                // Thinking, should I sell or not?
64                Thread.sleep(5*1000);
65            } catch (InterruptedException e) { }
66        }
67        if (!sell) available_cars++;
68        return sell;
69    }
70
71}
72
73// Result as is:
74// 
75//     Other customer is looking for a car...
76//     I am looking for a car...
77//     OC: I'll keep walking
78//     Me: I've got a new car
79// 
80// Result without the synchronized keyword:
81// 
82//     Other customer is looking for a car...
83//     I am looking for a car...
84//     Me: I'll keep walking
85//     OC: I'll keep walking

Classes also have locks, they apply to static features of a class and not to instances of a class.

wait() and notify()

Sometimes you want to have more control of the synchronized feature. For example, you may only block until a condition is met. These two keywords should only be used under synchronized blocks of code.

wait()

When wait() appears, the calling thread enters into a monitoring state, and unblocks the object without needing to exit the complete synchronized block. This state can last a fixed amount of time and not until notify() is called by using: wait(long milliseconds). wait() may throw a “InterruptedException” that you must catch.

Example:

 1class CarDealer {
 2
 3    int available_cars = 1;
 4
 5    synchronized boolean buyCar(int mIncome, String car) {
 6        boolean sell = false;
 7        if (available_cars > 0) {
 8            available_cars--;
 9
10            if (mIncome > 1500) sell = true;
11            if (car.equals("jaguar") && mIncome < 5000) 
12                sell = false;
13            try {
14                wait(5*1000);   
15            } catch (InterruptedException e) {}
16        }
17        if (!sell) available_cars++;
18        return sell;
19    }
20
21}
22
23// Result:
24// 
25//     Other customer is looking for a car...
26//     I am looking for a car...
27//     Me: I'll keep walking
28//     OC: I'll keep walking

This is a modified version of the last example. We have only changed the sleep call by wait(). wait(), unlike sleep(), releases the lock of the object so the BuyCar() method doesn’t block for the other thread.

notify()

One arbitrary chosen thread gets moved out of the monitor’s waiting pool and into the Seeking Lock state. To cause the same effect on all running threads, you can use notifyAll().

Example:

 1class Test {
 2    public static void main(String args[]) {
 3        
 4        BeerDealer bd = new BeerDealer();
 5        Timer t = new Timer();      
 6        t.schedule(new TTask(bd), 2000);    
 7
 8        if (bd.buyBeer()) {
 9            System.out.println("I'm allowed to drink");
10        } else {
11            System.out.println("I'm not allowed to drink");
12        }
13        System.out.println("Execution ended");
14        System.exit(0);
15    }
16}
17
18class TTask extends TimerTask {
19    BeerDealer bd;
20    TTask(BeerDealer bd) {
21        this.bd = bd;
22    }
23    public void run() {
24        bd.setAge(18);
25    }
26}
27
28class BeerDealer {
29    public static int age = 17;
30
31    synchronized boolean buyBeer() {
32        while (age < 18) {
33            try {
34                wait(); 
35            } catch (InterruptedException e) {}
36        }
37        return true;
38    }
39
40    synchronized void setAge(int age) {
41        this.age = age;
42        notify();
43    }
44}
45
46// Result:
47// 
48//     I'm allowed to drink
49//     Execution ended

Finally, wait() and notify() are usually used together to avoid data corruption under some circumstances. In this example, we simulate an answer machine.

Example:

  1class Test {
  2
  3    static boolean done = false;
  4
  5    public static void main(String args[]) {
  6
  7        Mailbox answerMachine = new Mailbox(); 
  8        ThreadOBJ to = new ThreadOBJ(answerMachine);
  9        Thread t = new Thread(to);
 10        
 11        t.start();
 12
 13        System.out.println("Welcome!");
 14        System.out.println("What kind of message do you");
 15        System.out.println("want to leave?\n");
 16        System.out.println("1 - Hi, call me back please!");
 17        System.out.println("2 - Please return my calls!");
 18        System.out.println("3 - Exit\n");
 19        System.out.print("Select (1-3) and press Enter: ");
 20
 21        int c;
 22
 23        try {
 24        exit:   while ((c = System.in.read()) != -1) {
 25                switch(Character.getNumericValue((char)c)) {
 26                    case 1:
 27                        answerMachine.storeMessage
 28                        ("Hi, call me back please!");   
 29                        break;
 30                    case 2:
 31                        answerMachine.storeMessage
 32                        ("Please return my calls!");
 33                        break;
 34                    case 3:
 35                        break exit;
 36                }
 37
 38            }
 39        } catch (IOException e) {}
 40
 41        answerMachine.hungup();
 42        System.out.println("Good bye!");
 43
 44    }
 45}
 46
 47class ThreadOBJ implements Runnable {
 48    Mailbox mb;
 49
 50    public ThreadOBJ (Mailbox mb) {
 51        this.mb = mb;
 52    }
 53    
 54    public void run() {
 55        String s;
 56
 57        while (!((s = mb.retriveMessage()).equals("<>"))){
 58            System.out.println(s);
 59        }
 60        System.out.println("Thread canceled");
 61    }
 62}
 63
 64class Mailbox {
 65    private boolean request;
 66    private String message;
 67
 68    public synchronized void storeMessage(String m) {
 69        while (request) {
 70            try {
 71                wait();
 72            } catch (InterruptedException e) { }
 73        System.out.println("Waiting input");
 74        }
 75        request = true;
 76        message = m;
 77        notify();
 78    }
 79
 80    public synchronized String retriveMessage() {
 81        while (!request) {
 82            try {
 83                wait();
 84            } catch (InterruptedException e) { }
 85        }
 86        request = false;
 87        notify();
 88        return message;
 89    }
 90
 91    public void hungup () {
 92        request = false;
 93        storeMessage("<>");
 94    }
 95}
 96
 97// Result:
 98// 
 99//     C:\temp>java Test
100//     Welcome!
101//     What kind of message do you
102//     want to leave?
103// 
104//     1 - Hi, call me back please!
105//     2 - Please return my calls!
106//     3 - Exit
107// 
108//     Select (1-3) and press Enter: 1
109//     Hi, call me back please!
110//     2
111//     Please return my calls!
112//     3
113//     Good bye!
114//     Thread canceled

The java.lang and java.util Packages

The Object Class

The object class is the ultimate ancestor of all Java classes. If a class does not contain the extends keyword in its declaration, the compiler builds a class that extends directly from Object. You should pay attention to some methods that are inherited by every class:

equals()

Allows to compare two objects. Strings for example, may be compared using this method. If you create your own objects, you should also override this method in order to supply your own equality algorithm.

toString()

The purpose of the toString() method is to provide a string representation of an object’s state.

The Math Class

This class contains a collection of methods and two constants that support mathematical computation. Some important information about the Math Class:

  • The class is final so it cannot be extended.
  • The constructor is private so no instances can be created.
  • The methods and constants are static and may be accessed directly.

Math constants

  • Math.PI
  • Math.E

They are declared to be public, static, final, and double.

Methods of the Math Class

int abs(int i)

Returns the absolute value of i:

1abs(-5) = 5
2abs(5)  = 5

Other methods: long abs(long l), float abs(float f), double abs(double d)

double ceil(double d)

Returns the smallest integer that is not less than d.

1ceil(4.00001)  = 5.0
2ceil(4.99999)  = 5.0
3
4ceil(-4.00001) = -4.0
5ceil(-4.99999) = -4.0

double floor(double d)

Returns the largest integer that is not greater than d.

1floor(4.00001)  = 4.0
2floor(4.99999)  = 4.0
3
4floor(-4.00001) = -5.0
5floor(-4.99999) = -5.0

Other methods: double floor(double d)

int max(int i1, int i2)

Returns the greater of i1 and i2

1max(1,3) = 3

Other methods: long max(long l1, long l2), float max(float f1, float f2), double max(double d1, double d2).

int min(int i1, int i2)

Returns the smaller of i1 and i2

1min(1,3) = 1

Other methods: long min(long l1, long l2), float min(float f1, float f2), double min(double d1, double d2).

double random()

Returns a random number equal or greater than 0 but lower than 1.0

1random() = 0.012288873641244202
2random() = 0.8962267714328847
3...

int round(float f)

Returns the closest int to f.

1round(4.00001)  = 4
2round(4.49999)  = 4
3round(4.5)      = 5
4round(4.99999)  = 5
5
6round(-4.5)     = -4
7round(-4.40001) = -5

Other method: long round(double d)

double sin(double d)

Returns the sine of d

double cos(double d)

Returns the cosine of d

double tan(double d)

Returns the tangent of d

double sqrt(double d)

Returns the square root of d

The Wrapper Classes

Each Java primitive data type has a corresponding wrapper class. A wrapper class is simply a class that encapsulates a single, immutable value.

Primitive Data Type Wrapper Class
boolean Boolean
byte Byte
char Character
short Short
int Integer
long Long
float Float
double Double

Constructing instances of wrapper types

All wrapper classes can be constructed by passing the value to be wrapped into the appropriate constructor.

Examples:

1int a = 567;
2Integer i = new Integer(a);
3
4Character c = new Character('Z');

There is another way to construct classes, with the exception of Character. You can pass into the constructor a string that represents the value to be wrapped. Most of these constructors throw NumberFormatException, because there is always the possibility that the string will not represent a valid value. Only Boolean does not throw this exception.

Example:

 1Boolean wboolean = new Boolean("true");
 2try {
 3    Byte wbyte = new Byte("124");
 4    Short wshort = new Short("25943");
 5    Integer wint = new Integer("532342");
 6    Long wlong = new Long("78888900000");
 7    Float wfloat = new Float("5.4f");
 8    Double wdouble = new Double("1.1000004");   
 9
10} catch (NumberFormatException e) { }

Getting values that have been wrapped

The value of any wrapped number can be retrieved as any numeric type. Each class has a method to do that:

  • public byte byteValue()
  • public short shortValue()
  • public long longValue()
  • public float floatValue()
  • public double doubleValue()
  • public char charValue()

The wrapper classes are useful whenever it would be convenient to treat a piece of primitive data as if it were an object.

Summary:

  • Every primitive type has a corresponding wrapper class type.
  • All wrapper types can be constructed from primitives; all except Character that can also be constructed from strings.
  • Wrapped values can be tested for equality with the equals() method.
  • Wrapped values can be extracted with various Value() methods.
  • Wrapper classes provide various utility methods, including the static valueOf() methods, which parse an input string.
  • Wrapped values cannot be modified.

Strings

Java uses 16-bits Unicode characters rather than 8-bits byte characters.

The String class

The String class contains an immutable string. Once an instance is created, the string it contains cannot be changed.

Construction:

1String s1 = new String("Hello World");
2String s2 = new String("Hello World");
3String s3 = "Hello World";
4String s4 = "Hello World";

As strings cannot be changed, when construction strings using plain literals, the equal strings are stored only once because the compiler assumes they are constants. So (s3 == s4) is true, because the string “Hello World” is stored at the same JVM address. The first variables should be tested using the standard equals() method: (s1.equals(s2)) = true

Most useful String methods

char charAt(int index)

Returns the indexed character of a string, where the index of the initial character is 0

1"Ernest".charAt(2) = 'n'

String concat(String addThis)

This returns a new string consisting of the old string followed by addThis

1"Ernest".concat(" Garbarino") = "Ernesto Garbarino"

int compareTo(String otherString)

This performs a lexical comparison, it returns an int that is:

  • less than 0: if the current string is less than otherString.
  • equal to 0: if the strings are identical.
  • greatar than 0: if the current string is greater than otherString.
1"Ernest".compareTo("Ricardo") = -13
2"Ernest".compareTo("Ernest") = 0
3"Ernest".compareTo("Felix") = -1

boolean endsWith(String suffix)

This returns true if the current string ends with suffix; otherwise it returns false.

1"Ernest".endsWith("est") = true
2"Ernest".endsWith("lix") = false

boolean equals(Object ob)

This returns true if ob instanceof String and the string encapsulated by ob matches the string encapsulated by the executing object.

1"Ernest".equals(new String("Ernest")) = true
2"Ernest".equals(new String("ERNEST")) = false

boolean equalsIgnoreCase(String s)

This is like equals(), but the argument is a String and the comparision ignores case.

1"Ernest".equalsIgnoreCase("Ernest") = true
2"Ernest".equalsIgnoreCase("ERNEST") = true

int indexOf(int ch)

This returns the index within the current string of the first occurence of ch. Alternative forms return the index of a string and begin searching from a specified offset.

1"Garbarino".indexOf('a') = 1

int lastIndexOf(int ch)

This returns the index within the current string of the last ocurrence of ch. Alternative forms return the index of a string and end searching at a specified offset from the end of the string.

1"Garbarino".indexOf('a') = 4

int length()

This returns the number of characters in the current string.

1"Ernest".length() = 6

replace(char oldChar, char newChar)

This returns a new string, generated by replacing every occurence of oldChar with newChar

1"Garbarino".replace('a','e') = "Gerberino"

boolean startsWith(String prefix)

This returns true if the current string begins with prefix; otherwise it returns false. Alternate forms begin searching from a specified offset.

1"Ernest".startsWith("Ern") = true
2"Ernest".startsWith("est") = false

String substring(int startIndex)

This returns the substring, beginning at startIndex of the current string and extending to the end of the current string. An alternate form specifies starting and ending offsets.

1"Ernest".substring(2) = "nest"

String toLowerCase()

This converts the executing object to lowercase and returns a new string.

1"Ernest".toLowerCase() = "ernest"

String toUpperCase()

This converts the executing object to uppercase and returns a new string.

1"Ernest".toUpperCase() = "ERNEST"

String toString()

This returns the executing object.

1"Ernest".toString() = "Ernest"

String trim()

This returns the string that results from removing whitespace characters from the beginning and ending of the current string.

1"   Ernest ".trim() = "Ernest"

The StringBuffer Class

A instance of Java’s StringBuffer class represents a string that can be dynamically modified. The most commonly used constructor takes a String instance as input. You can also construct an empty string buffer.

StringBuffer constructors

StringBuffer()

This constructs an empty string buffer

StringBuffer(int capacity)

This constructs an empty string buffer with the specified initial capacity. You may eventually set an initial capacity just to aviod memory re-allocation each time that the StringBuffer grows.

StringBuffer(String initialString)

This constructs a string buffer that initially contains the specified string.

Most useful StringBuffer methods

StringBuffer append(String str)

This appends str to the current string buffer. Alternative forms support appending primitives and character arrays; these are converted to strings before appending.

1new StringBuffer("Ernesto ").append("Garbarino") = "Ernesto Garbarino"
2
3char[] word = {'h','e','l','l','o'};
4new StringBuffer().append(word) = "hello"

StringBuffer append(Object obj)

This calls toString() on obj and appends the result to the current string buffer.

StringBuffer insert(int offset, String str)

This inserts str into the current string buffer at position offset. There are numerous alternative forms.

1new StringBuffer("Ernest").insert(3,"hi") = "Ernhiest"

StringBuffer reverse()

This reverses the characters of the current string buffer.

1new StringBuffer("Ernest").reverse() = "tsenrE" 

void setCharAt(int offset, char newChar)

This replaces the character at position offset with newChar

1StringBuffer sb = new StringBuffer("Ernest");
2sb.setCharAt(1,'l');
3
4sb = "Elnest"

void setLength(int newLength)

This sets the length of the string buffer to newLength. If newLength is less than the current length, the string is truncated. If newLength is greater than the current length, the string is padded with null characters.

1StringBuffer sb = new StringBuffer("Ernest");
2sb.setLength(3);
3
4sb = "Ern"

The Collections API

The Collections API is a mechanism for manipulating object references. While arrays are capable of storing primitives or references, collections are not. To work with Collections using your own objects, you should:

  • Implement the equals() method
  • Implement the java.lang.Comparable interface and the compareTo() method
  • You may also require to define the hashCode() method if you use Map.

Collections impose no order, nor restrictions, on content duplication.

Some concepts:

  • Lists maintain an order (possibly inherent in the data, possibly externally imposed).
  • Sets reject duplicate entries.
  • Maps use unique keys to facilitate lookup of their contents

Let’s see the most common collection storage approaches now:

Array storage

Tends to be fast to access, but it is relatively inefficient as the number of elements in the collections grows or if elements need to be inserted or deleted in the middle of the set.

A linked list

Alows elements to be added to, or removed from, the collection at any location in the container, and allows the size of the collection to grow arbitrarily without the penalities associated with array copying. Indexed access is slower.

A tree

Allows easy addition and deletion of elements and arbitrary growth of the collection. Unlike a list, trees insist on a means of ordering. Indexed access is slow, but searching is faster.

A hash table

It requires that some unique identifying key can be associated with each data iten, which in turn provides efficient searching. Indexed access is slow, but searching is particulary fast.

Collection Implementations in the API

HashMap/Hashtable

These two classes are very similar, using hashbased storage to implement a map. Hashtable does not allow the null value to be stored.

HashSet

This is a set, so it does not permit duplicates and it uses hashing for storage

LinkedList

This is an implementation of a list, based on a linked-list storage.

TreeMap

This class provides an ordered map. The elements must be orderable, either by implementing the Comparable interface or by providing a Comparator class to perform the comparisions.

TreeSet

This class provides an ordered set, using a tree for storage. As with the TreeMap, the elements must have an order associated with them.

Vector

This class implements a list using an array internally for storage. The array is dynamically reallocated as necessary, as the number of items in the vector grows.

Input and Output

Character Encodings

Various constructors and methods in Java accept string arguments that specify the character encoding to be used when converting between raw eight-bit bytes and sixteen-bit Unicode characters.

Every implementation of the Java platform is required to support the following character encodings:

  • US-ASCII: Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set
  • ISO-8859-1: ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1
  • UTF-8: Eight-bit Unicode Transformation Format
  • UTF-16BE: Sixteen-bit Unicode Transformation Format, big-endian byte order
  • UTF-16LE: Sixteen-bit Unicode Transformation Format, little-endian byte order
  • UTF-16: Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order accepted on input, big-endian used on output)

Main Classes

  • File: allows to navigate the file system, move and delete files, etc.
  • RandomAccessFile: low-level byte random file access.
  • File[Input/Output]Stream: low-level byte sequential file access.
  • Data[Input/Output]Stream: high-level datatype sequential file access.
  • Buffered[Input/Output]Stream: allows to read/write large blocks of bytes.

Classes to manipulate Unicode characters:

  • File[Reader/Writer]: standard file access.
  • CharArray[Reader/Writer]: reads and writes char arrays.
  • Piped[Reader/Writer]: provides a mechanism for thread communication.
  • String[Reader/Writer]: reads and writes strings.
  • Buffered[Reader/Writer]: alllos to read/write data in large blocks.
  • InputStreamReader and OutputStreamWriter: sequential char access
  • LineNumberReader: file access as a sequence of lines of text.

The File Class

The java.io.File class represents the name of a file or directory that might exist on the host machine’s file system. File does not create a file on the local file system.

Common constructors:

1File(String pathname);
2File(String dir, String subpath);
3File(File dir, String subpath);

Partial method list:

boolean exists(): returns true if the file/dir exists

String getAbsolutePath(): returns the absolute path of the file/dir

 1class Test {
 2    public static void main(String args[]) {
 3        File f = new File("Test.java");
 4        System.out.println(f.getAbsolutePath());
 5    }
 6}
 7
 8// Result:
 9// 
10// c:tempTest.java

String getCanonicalPath(): gets the canonical path of the file/dir

 1class Test {
 2    public static void main(String args[]) throws IOException {
 3        File f = new File("..tempTest.java");
 4        System.out.println(f.getCanonicalPath());
 5    }
 6}
 7
 8// Result:
 9// 
10//     c:tempTest.java

The getAbsolutePath() method doesn’t resolve the dot symbols (.) and (..)

String getName(): returns the name of the file/dir. The name is the last element of the path

 1File f = new File(System.getProperty("user.dir"));
 2System.out.println(f.getName());
 3
 4// Result:
 5// 
 6//     temp
 7``
 8
 9**String getParent():** returns the name of the directory that contains the file
10
11``` java
12File f = new File(System.getProperty("user.dir"),"Test.java");
13System.out.println(f.getParent());
14
15// Result:
16// 
17//     c:temp

boolean isDirectory(): returns true if the File object describes a dir

boolean isFile(): returns true if the File object describes a valid file

String[] list(): returns an array containing the names of the files and directories within the File. The File must describe a directory, not a file

boolean canRead(): returns true if the file or directory may be read

boolean canWrite(): returns true if the file or directory may be modified

boolean delete(): attempts to delete the file or directory (directories must be empty)

long length(): returns the length of the file

boolean mkdir(): attempts to create a directory whoise path is described by the File.

boolean renameTo(File newname): renames the file or directory.

A Java dir utility

 1class Dir {
 2    public static void main(String args[]) throws IOException {
 3        File dir = new File(System.getProperty("user.dir"));
 4        System.out.println("Contents of: "+dir.getAbsolutePath());
 5        String files[];
 6        files = dir.list();
 7        for (int i = 0 ; i < files.length ; i++) {
 8            File current = new File(files[i]);
 9            if (current.isDirectory()) {
10                System.out.print("  ");
11            } else {
12                System.out.print("       ");
13            }
14            if (current.canRead()) {
15                System.out.print("r");
16            } else {
17                System.out.print("-");
18            }
19            if (current.canWrite()) {
20                System.out.print("w");
21            } else {
22                System.out.print("-");
23            }
24            System.out.println(" " + current);
25        }
26    }
27}
28
29// Result:
30// 
31//     Contents of c:temp
32// 
33//            r- autorun.inf 
34//            rw Dir.java
35//            rw Dir.class
36//       rw InstallShield
37//            rw Xfiles.jpg

The RandomAccessFile Class

This class takes advantage of a particular behavior of files that is not found in general I/O devices. With a random-access file, it is possible to seek to a position of a file and then read or write a desired amount of data.

Main constructors:

  • RandomAccessFile(String file, String mode)
  • RandomAccessFile(File file, String mode)

The mode string should be either “r” or “rw”.

Seeking methods:

  • long getFilePointer() throws IOException: returns the current position within the file, in bytes. Subsequent reading and writing will take place starting at this position.
  • long length() throws IOExeception: returns the length of the file, in bytes.
  • void seek(long position) throws IOException: sets the current position within the file, in bytes. Subsequent reading and writing will take place starting at this position. Files start at position 0.

More common methods that support byte reading and writing:

  • int read() throws IOException: returns the next byte from the file. Returns -1 if reaches the end of the file.
  • int read(byte dest[]) throws IOException: attempts to read enough bytes to fill array dest[]. It returns the number of bytes read, or -1 if the the file was at end of file.
  • int read(byte dest[], int offset, int len) throws IOException: attempts to read len bytes into array dest[], starting at offset. It returns the number of bytes read, or -1 if the file was at end of file.
  • void write(int b) throws IOException: writes the low-order byte of b
  • void write(byte b[]) throws IOException: writes all bytes of array b[]
  • void write(byte b[], int offset, int len) throws IOException: writes len bytes from byte array b[], starting at position offset.

Several methods for primitive data types are also provided. When a random-access file is no longer needed, it should be closed:

1void close() throws IOException

Low-level Streams

Low-level input streams have methods that read input and return the input in bytes.

FileInputStream

Main constructors:

  • FileInputStream(String pathname)
  • FileInputStream(File file)

Mose useful methods:

  • int read() throws IOException: returns the next byte from the file or -1 if the file was at the end of the file.
  • int read(byte dest[]) throws IOException: attempts to read enough bytes to fill array dest[]. It returns the number of bytes read or -1 if the file was at the end of file.
  • int read(byte dest[], int offset, int len) throws IOException: attempts to read len bytes into array dest[], starting at offset. It returns the number of bytes read or -1 if the file was at the end of file.
  • int available() throws IOException: returns the number of bytes that can be read without blocking.
  • void close() throws IOException: releases non-memory system resources associated with the file. A file input stream should always be closed when no longer needed.
  • long skip(long nbyes) throws IOException: attempts to read and discard nbytes bytes. Returns the number of bytes actually skipped.

FileOutputStream

Main consturctors:

  • FileOutputStream(String pathname)
  • FileOutputStream(File file)

Most useful methods:

  • void write(int b) throws IOException: This writes the low-order byte of b.
  • void write(byte bytes[]) throws IOException: This writes all members of array bytes[].
  • void close() throws IOException: releases non-memory system resources associated with the file.

Example:

 1class Test {
 2    public static void main(String args[]) throws IOException {
 3        FileInputStream srcFile = new FileInputStream("tale.txt");
 4        FileOutputStream dstFile = new FileOutputStream("tale.bak");            
 5        int b;
 6        while ((b = srcFile.read()) != -1) {
 7            dstFile.write(b);   
 8        }
 9        dstFile.close();
10        srcFile.close();
11    }
12}
13// Result:
14// 
15//     A backup of tale.txt is performed.

High-Level Filter Streams

Java supports high-level I/O with high-level streams. High-level input streams do not read from input devices such as files or sockets; rather, they read from other streams. High-level streams do not write to output devices, but to other streams.

DataInputStream

Constructor:

1DataInputStream(InputStream instream)

The constructor requires you to pass in an input stream. This instance might be a file input stream, an input stream from a socket, or any other kind of input stream.

Most useful methods:

  • boolean readBoolean() throws IOException
  • byte readByte() throws IOException
  • char readChar() throws IOException
  • double readDouble() throws IOException
  • float readFloat() throws IOException
  • int readInt() throws IOException
  • long readLong() throws IOException
  • short readShort() throws IOException
  • String readUTF() throws IOException
  • void close() throws IOException

DataOutputStream

Constructor:

1DataOutputStream(OutputStream oststream)

Most useful methods:

  • void writeBoolean(boolean b) throws IOException
  • void writeByte(int b) throws IOException
  • void writeBytes(String s) throws IOException
  • void writeChar(int c) throws IOException
  • void writeDouble(double d) throws IOException
  • void writeFloat(float b) throws IOException
  • void writeInt(int i) throws IOException
  • void writeLong(long l) throws IOException
  • void writeShort(int s) throws IOException
  • void writeUTF(String s) throws IOException

Example:

 1class Test {
 2    public static void main(String args[]) throws IOException {
 3        String name = "Ernest";
 4        int age = 24;
 5        float weight = 94.5f;
 6        char gate = 'A';
 7
 8        FileOutputStream fos = new FileOutputStream("record.dat");
 9        DataOutputStream dst = new DataOutputStream(fos);
10        dst.writeUTF(name);
11        dst.writeInt(age);
12        dst.writeFloat(weight);
13        dst.writeChar(gate);
14        dst.close();
15        fos.close();
16        
17        FileInputStream fis = new FileInputStream("record.dat");
18        DataInputStream src = new DataInputStream(fis);
19        System.out.println("  Name: " + src.readUTF());
20        System.out.println("   Age: " + src.readInt());
21        System.out.println("Weight: " + src.readFloat());
22        System.out.println("  Gate: " + src.readChar());
23        src.close();
24        fis.close();
25    }
26}
27
28// Result:
29// 
30//       Name: Ernest
31//        Age: 24
32//     Weight: 94.5
33//       Gate: A       

Readers and Writers

Readers and writers are like input and output streams. The low-level varieties communicate with I/O devices, while the high level varieties communicate with low-level varieties. What makes readers and writers different is that they are exclusively oriented to Unicode characters. FileReader

Constructors:

  • FileReader(String pathname)
  • FileReader(file)

FileWriter

Constructors:

  • FileWriter(String pathname)
  • FileReader(File file)

FileReader and FileWriter extends the Reader and Writer classes, some other useful classes are:

  • CharArrayReader and CharArrayWriter: These classes read and write char arrays.
  • StringReader and StringWriter: These classes read and write strings.

Reader, most useful methods:

  • int read() throws IOException: returns the next char (stored in the low order 16 bits of the int return value) or -1 if at end of input.
  • int read(char dest[]) throws IOException: attempts to read enough chars to fill array dest[]. It returns the number of chars read or -1 if at end of input.
  • int read(char dest[], int offset, int len) throws IOException: attempts to read len chars into array dest[], starting at offset. It returns the number of chars read or -1 if at the end of input

Writer, most useful methods:

  • write(int ch) throws IOException: writes the char that appears in the low-order 16 bits of ch
  • void write(String str) throws IOException: writes the string called str
  • void write(String str, int offset, int len) throws IOException: writes the substring of str that begins at offset and has length len
  • void write(char chars[]) throws IOException: writes the char array chars[]
  • void write(char chars[], int offset, int len) throws IOException: writes len chars from array chars[], beginning at offset.

Other high-level classes:

  • Buffered[Reader/Writer]: minimize I/O overhead
  • [Input/Output]Stream[Reader/Writer]: convert between streams of bytes and sequences of Unicode characters. By default, the classes assume that the stream use the plataform’s default character encoding; alternative constructors provide any desired encoding
  • LineNumberReader: views its input as a sequence of lines of text. A method called readLine() returns the next line, and the class keeps track of the current line number

Example:

 1class Test {
 2    public static void main(String args[]) throws IOException {
 3        FileInputStream fis = new FileInputStream("tale.txt");
 4        InputStreamReader src = new InputStreamReader(fis, "ISO-8859-1");
 5        FileOutputStream fos = new FileOutputStream("tale.utf");
 6        OutputStreamWriter dst = new OutputStreamWriter(fos, "UTF-16");
 7        int c;
 8        while ((c = src.read()) != -1) {
 9            dst.write(c);
10        }
11        dst.close();
12        fos.close();
13        src.close();
14        fis.close();
15    }
16}
17
18// Result:
19// 
20//     tale.utf becomes a file encoded in the UTF-16 format.

Object Streams and Serialization

Serialization is the process of breaking down a Java object and writing it out somewhere.

Example of a simple student database:

 1class Test {
 2    public static void main(String args[]) throws IOException, ClassNotFoundException {
 3        int fields = 3;
 4        String database = "students.db";
 5
 6        Record student[] = new Record[3];
 7        student[0] = new Record("Ernesto",24);
 8        student[1] = new Record("John",14);
 9        student[2] = new Record("Mike",29);
10        FileOutputStream fos = new FileOutputStream(database);
11        ObjectOutputStream dst = new ObjectOutputStream(fos);
12        dst.writeInt(fields);
13        for (int i = 0 ; i < student.length ; i++) {
14            dst.writeObject(student[i]);            
15            student[i] = null;
16        }
17        dst.close();
18        fos.close();
19
20        Record current_student;
21
22        FileInputStream fis = new FileInputStream(database);
23        ObjectInputStream src = new ObjectInputStream(fis);
24        int db_fields = src.readInt();
25        for (int i = 0 ; i < db_fields ; i++) {
26            current_student = (Record)src.readObject();
27            System.out.println(current_student);
28        }
29        src.close();
30        fis.close();
31    }
32}
33
34class Record implements Serializable {
35    private String name;    
36    private int age;
37    Record(String name, int age) {
38        this.name = name;
39        this.age = age;
40    }
41    public String toString() {
42        return name + " is " + age + " years old."; 
43    }
44}
45
46// Result:
47// 
48//     Ernesto is 24 years old.
49//     John is 14 years old.
50//     Mike is 29 years old.   

AWT - Layout Managers

Default layout managers:

  • Applets and Panels: FlowLayout
  • Frames: BorderLayout

The Flow Layout Manager

The flow layout manager arranges components in horizontal rows.

 1Frame f = new Frame();
 2f.setSize(400,200);
 3Button b1 = new Button("Button #1");
 4Button b2 = new Button("Button #2");
 5Button b3 = new Button("Button #3");
 6f.setLayout(new FlowLayout(FlowLayout.CENTER)); // HERE
 7f.add(b1);
 8f.add(b2);
 9f.add(b3);
10f.setVisible(true);

Most useful constructors

  • FlowLayout(int align)
  • FlowLayout(int align, int hgap, int vgap)

Align is usually one of these constants:

  • FlowLayout.LEFT
  • FlowLayout.RIGHT
  • FlowLayout.CENTER
  • FlowLayout.LEADING
  • FlowLayout.TRAILING

The hgap and vgap parameters set the horizontal and vertical gap of the components affected by the Layout Manager.

The Grid Layout Manager

The Grid layout manager subdivides its territory into a matrix of rows and columns. The number of rows and number of columns are specified as parameters to the manager’s constructor:

1GridLayout(int nRows, int nColumns)

Each row and each column in a grid layout will be the same size; the overall area available to the layout is divided equally between the number of rows and the number of columns. Most components will be expanded to the size of the cells.

Example:

1Frame f = new Frame();
2f.setSize(400,200);
3f.setLayout(new GridLayout(3,2));
4for (int i = 0 ; i < 6 ; i++) {
5    f.add(new Button("Button #"+ i));
6}
7f.setVisible(true);

The Border Layout Manager

It is the default manager for frames. The Border layout manager divides its territory into five regions. The names of these regions are North, South, East, West, and Center. The manager forces the North and South components to be as wide as the container. The North and South regions are useful for toolbars, status lines, and any other controls that ought to be as wide as possible. You can put only a single component in each region.

The constructor is usually used without parameters:

1BorderLayout()

However, each time you add a component you have to use the overloaded version of add():

1add(Component comp, int index)

Where index may be one of these constants:

  • BorderLayout.NORTH
  • BorderLayout.SOUTH
  • BorderLayout.EAST
  • BorderLayout.WEST
  • BorderLayout.CENTER

Example:

 1Frame f = new Frame();
 2f.setSize(400,200);
 3f.setLayout(new BorderLayout());
 4Button b1 = new Button("North");
 5Button b2 = new Button("South");
 6Button b3 = new Button("West");
 7Button b4 = new Button("East");
 8Button b5 = new Button("CENTER");
 9
10f.add(b1, BorderLayout.NORTH);
11f.add(b2, BorderLayout.SOUTH);
12f.add(b3, BorderLayout.WEST);
13f.add(b4, BorderLayout.EAST);
14f.add(b5, BorderLayout.CENTER);
15
16f.setVisible(true);

The Card Layout Manager

The Card layout manager allows to overlap components and to choose which ones to show. The Card layout gives the components that it manages a sequence, and you can ask it to display the first or last component in that sequence explicity. In addition, you can ask for the next or previous component in that sequence. In this way, you can cycle through the components very easily.

The second way to control component display is to give each component a name. If you take this approach, the Card layout allows you to select the component to be displayed using that name.

To add a component with a name, simply use the String object that represents that name in the second argument of the add method, like this:

1Panel p = new Panel(); 
2cl = new CardLayout();
3p.setLayout(cl);
4Button b = new Button("A Component");
5p.add(b, "Button-A");
6cl.show(cl, "Button-A");

Methods:

  • void first(Container)
  • void last(Container)
  • void next(Container)
  • void previous(Container)
  • void show(Container, String)

Example:

 1Frame f = new Frame();
 2f.setSize(500,300);
 3CardLayout cl = new CardLayout();
 4f.setLayout(cl);
 5for (int i = 0 ; i < 4 ; i++) {
 6    f.add(new Button("Button #"+i), "Button-"+i);
 7}
 8f.add(new Button("String button"),"mybutton");
 9
10cl.show(f, "mybutton");
11f.setVisible(true);
12
13for (int w = 0 ; w < 4 ; w++) {
14    try {
15        Thread.sleep(1000);
16    } catch (InterruptedException e) {}
17    cl.next(f);
18}
19
20// Result:
21// 
22//     1. A big button called "String button" is shown
23//     2. 1s pause
24//     3. A big button called button#0 is shown
25//     4. 1s pause
26//     5. A big button called button#1 is shown
27//     6. 1s pause
28//     7. A big button called button#2 is shown
29//     8. 1s pause
30//     9. A big button called button#3 is shown

The Grid Bag Layout Manager

The GridBag layout divides its container into an array of cells, but (unlike the cells of a Grid layout manager) different cell rows can have different heights, and different cell columns can have different widths. A component can occupy part or all of a region that is based on either a single cell or a rectangle made up of multiple cells.

Common use:

1Frame f = new Frame();
2f.setSize(400,200);
3GridBagConstraints 
4gbc = new GridBagConstraints();    // Holds layout position info
5f.setLayout(new GridBagLayout());
6gbc.gridx = 0;                     // You specify which cell you
7gbc.gridy = 0;                     // want to place the component in.
8f.add(new Button("Top left"), gbc);

Choosing a cell: gridx and gridy

A grid is created according to the cells that you add to it. With gridx and gridy you choose where you want to place a given component.

1gbc.gridx = 3;
2gbc.gridx = 2;

Chooses this cell (x):

Allowing rows and columns to stretch: weightx and weighty

This value applies only to rows and columns, not to individual cells. You can define the column (weightx) or the row (weighty) to stay still using a value of 0.

1gbc.weightx = 0;

If all columns are of value 0 and one is of value 1.0, that one will stretch to the maximum available space. Otherwise, you may consider the values to be percentages, for example:

1gbc.weightx = 20;
2gbc.gridx++;
3gbc.weightx = 80;   

Controlling component position: anchor

You can tell which corner of the cell you want to place the component in. anchor is an int value that can use any of these constants:

  • GridBagConstraints.CENTER
  • GridBagConstraints.NORTH
  • GridBagConstraints.NORTHEAST
  • GridBagConstraints.EAST
  • GridBagConstraints.SOUTHEAST
  • GridBagConstraints.SOUTH
  • GridBagConstraints.SOUTHWEST
  • GridBagConstraints.WEST
  • GridBagConstraints.NORTHWEST

Example:

1gbc.anchor = GridBagConstraints.SOUTHEAST;

Controlling stretch in cell: fill

Using a feature called fill, you can determine whether a component to fill the avaialable space, either horizontally, vertically or both.

Constants:

  • GridBagConstraints.HORIZONTAL
  • GridBagConstraints.VERTICAL
  • GridBagConstraints.BOTH
  • GridBagConstraints.NONE

Example:

1gbc.fill = GridBagConstraints.HORIZONTAL;

Controlling the size of a component: gridwidth and gridheight

A component may use one or more cells, not just one. This way, you can create very interesting layouts, not just chessboard-alike ones.

1gbc.gridx = 1;
2gbc.gridy = 0;
3gbc.gridwidth = 2;
4gbc.gridheight = 3;

A component placed using these parameters would take 6 cells (2x3).

Short-hands:

  • GridBagConstraints.RELATIVE: Specify that this component is the next-to-last component in its column or row (gridwidth, gridheight), or that this component be placed next to the previously added component (gridx, gridy).
  • GridBagConstraints.REMAINDER: Specify that this component is the last component in its column or row.

Complete example:

 1Frame f = new Frame();
 2f.setSize(250,80);
 3GridBagConstraints gbc = new GridBagConstraints();
 4GridBagLayout gbl = new GridBagLayout();
 5f.setLayout(gbl);
 6gbc.gridx = 0;
 7gbc.gridy = 0;
 8gbc.weightx = 80;
 9gbc.fill = GridBagConstraints.HORIZONTAL;
10f.add(new Button("Top left"), gbc);
11gbc.fill = GridBagConstraints.BOTH;
12gbc.gridheight = 2;
13gbc.weightx = 20;
14gbc.gridx++;
15f.add(new Scrollbar(Scrollbar.VERTICAL), gbc);
16gbc.fill = GridBagConstraints.NONE;
17gbc.gridy = 1;
18gbc.gridx = 0;
19gbc.anchor = GridBagConstraints.WEST;
20f.add(new Button("Bottom Left"), gbc);
21f.setVisible(true);

AWT - Components

Components are Java’s building blocks for creating graphical user interfaces.

Components in General

getSize()

The getSize() method returns the size of a component. The return type is Dimension, which has public data members height and width.

1Frame f = new Frame();
2f.setSize(400,200);
3System.out.println(f.getSize().width);
4System.out.println(f.getSize().height);
5
6// Result:
7// 
8//     400
9//     200

setForeground() and setBackground()

These methods receive a Color object as a parameter.

1Frame f = new Frame();
2f.setSize(400,200);
3f.setLayout(new FlowLayout());
4f.setBackground(new Color(255,255,0));
5f.setForeground(new Color(0,255,0));
6f.add(new Label("Hello World"));
7f.setVisible(true);

setFont()

This method determines the font that a component will use for rendering any text that it needs to display. This method takes a single argument, which is an instance of java.awt.Font.

1Frame f = new Frame();
2f.setSize(400,200);
3f.setLayout(new FlowLayout());
4f.setFont(new Font("Serif",Font.BOLD,66));
5f.add(new Label("Hello World"));
6f.setVisible(true);

setEnabled()

This method enables or disables an object from producing events and also changes its visual appearance. It receives a single boolean argument.

1Frame f = new Frame();
2f.setSize(400,200);
3f.setLayout(new FlowLayout());
4Button b = new Button("I'm disabled");
5b.setEnabled(false);
6f.add(b);
7f.setVisible(true);

setSize() and setBounds()

These methods attempt to set component’s geometry, they take these arguments:

  • public void setSize(int width, int height)
  • public void setBounds (int x, int y, int width, int height)

There are also other versions for these methods

1Frame f = new Frame();
2f.setBounds(0,0,400,200);
3f.setVisible(true);
4
5// Result:
6// 
7// Shows a 400x200 window at the top-left corner of the screen

setVisible()

This method takes a boolean argument that dictates whether the component is to be seen on the screen. This method is generally used for frames.

Visual Components

Button

The Button class implements a push button

Common construction:

1Button b = new Button("Button name");

Canvas

A canvas is a component that has no default appearance or behavior. It is usually subclassed to create custom drawing regions, work areas, and so on.

Common construction:

1Canvas c = new Canvas();

Checkbox

A check box is a two-state button. The two states are true (checked) and false (not checked).

Common construction:

1Checkbox cb = new Checkbox("Check e-mail every 5 minutes");
2Checkbox cb = new Checkbox("Check e-mail every 5 minutes", true);

The default state is false. You can handle state with the following methods:

  • boolean getState()
  • void setState(boolean state)

Radio buttons

Check boxes can be grouped together into check-box groups, which have radio behavior.

Example:

1CheckboxGroup cg = new CheckboxGroup();
2add(new Checkbox("English",true, cg));
3add(new Checkbox("Spanish",false, cg));
4add(new Checkbox("Italian",false, cg));

Two methods of the CheckboxGroup class support getting and setting currently selected member of the group:

  • Checkbox getSelectedCheckbox()
  • void setSelectedCheckbox(Checkbox newSelection)

Choice

A choice is a pull-down list (also known as Comobox).

Example:

1Choice c = new Choice();
2c.addItem("China");
3c.addItem("Korea");
4c.addItem("Japan");
5add(c);

FileDialog

This class presents a file open or file save dialog. Since it is a modal dialog, when the application calls its setVisible() method to display the dialog, it blocks the rest of the application until the user has chosen a file.

Common constructor:

1FileDialog(Frame parent, String title, int mode)

Example:

1Frame f = new Frame();
2FileDialog fd = new FileDialog(f, "Choose!", FileDialog.LOAD);
3fd.setVisible(true);

Modes:

  • FileDialog.LOAD
  • FileDialog.SAVE

Partial method list:

  • public String getFile()
  • public void setFile(String file)
  • public String getDirectory()
  • public void setDirectory(String dir)

Label

Labels are used to display simple but dynamic text information

Common construction:

1Label l = new Label();
2Label l = new Label("Hello");
3Label l = new Label("Hello", Label.LEFT);   

Alignments:

(int constants)

  • Label.LEFT
  • Label.CENTER
  • Label.RIGHT

Partial method list:

  • String getText()
  • void setText(String newText)

List

A list is a collection of text items, arranged vertically. If a list contains more items that it can display, it automatically acquires a vertical scroll bar.

Partial constructor list:

  • List()
  • List(int VisibleRows)
  • List(int VisibleRows, boolean multiSelectOK)

Example:

1List l = new List(10, true);
2l.add("English");
3l.add("Spanish");
4l.add("Italian");
5add(l);

Partial method list:

  • void add(String text)
  • void add(String text, int index)
  • String getItem(int index)
  • int getItemCount()
  • int getRows()
  • int getSelectedIndex()
  • int[] getSelectedIndexes()
  • String getSelectedItem()
  • String[] getSelectedItems()

ScrollPane

The ScrollPane component may contain a single component, which may be taller or wider than the scroll pane itself. If the contained component is larger than the scroll pane, then the default behavior of the scroll pane is to acquire horiziontal and/or vertical sscroll bars as needed.

Constructors:

  • ScrollPane(): constructs a scroll pane with default scroll bar hehavior
  • ScrollPane(int scrollbarPolicy): construct a scroll pane with the specified scroll bar behavior

Constants:

  • ScrollPane.SCROLLBARS_AS_NEEDED
  • ScrollPane.SCROLLBARS_ALWAYS
  • ScrollPane.SCROLLBARS_NEVER

Example:

 1Frame f = new Frame();
 2f.setSize(220,140);
 3f.setLayout(new FlowLayout(FlowLayout.LEFT));
 4ScrollPane sp = new ScrollPane(ScrollPane.SCROLLBARS_ALWAYS);
 5Button b = new Button("BIG WORDS");
 6b.setFont(new Font("Serif", Font.BOLD, 90));
 7sp.setSize(200,100);
 8sp.add(b);
 9f.add(sp);          
10f.setVisible(true);

Scrollbar

Scrollbars are usually called “sliders” on other environments.

Constructors:

  • Scrollbar(): constructs a vertical scroll bar
  • Scrollbar(int orientation): consturcts a scroll bar with the specified orientation.
  • Scrollbar(int orientation, int initialValue, int sliderSize, int minValue, int maxValue): constructs a scroll bar with the specified parameters.

Description:

  • initialValue: It is the value that the slider represents. It should be represented by the range of numbers between minValue and maxValue.
  • sliderSize: It’s the size relative to the range of numbers between minValue and maxValue. For example, if minValue = 300 and maxValue = 350, a sliderSize of 75 would extend to a half of the scrollbar.
  • minValue and maxValue: These are the values for the range of numbers you want to represent using a scroll bar.

Orientation constants:

  • Scrollbar.HORIZONTAL
  • Scrollbar.VERTICAL

Partial method list:

  • public int getValue()
  • public void setValue(int newValue)

Example:

 1Frame f = new Frame();
 2f.setSize(200,40);
 3Scrollbar sc = new Scrollbar
 4(Scrollbar.HORIZONTAL, // Type
 51,    // Initial Value
 625,   // Size (1/4 of the total sliding size)
 71,    // Min. Value
 8100); // Max. Value
 9f.add(sc);          
10f.setVisible(true); 

TextField and TextArea

The TextField and TextArea classes implement one-dimensional and two-dimensional components for text input, display, and editing. Both classes extend from the TextComponent superclass.

TextField constructors:

  • TextField(): empty text field
  • TextField(int Cols): empty text field with the specified number of columns
  • TextField(String text): text field whose initial constent is text
  • TextField(String text, int nCols): text field whose initial constent is text, with the specified number of columns.

TextArea constructors:

  • TextArea(): empty text area
  • TextArea(int Rows, int Cols): empty text area with the specified number of rows and columns.
  • TextArea(String text): a text area whose initial content is text
  • TextArea(String text, int Rows, int Cols): a text area whose initial content is text, with the specified number of rows and columns.
  • TextArea(String text, int Rows, int Cols, int scrollbarPolicy): Same as above but the scroll bar placement policy is determinated by the last parameter, which should be one of the following:
    • TextArea.SCROLLBARS_BOTH
    • TextArea.SCROLLBARS_NONE
    • TextArea.SCROLLBARS_HORIZONTAL_ONLY
    • TextArea.SCROLLBARS_VERTICAL_ONLY

Partial method list:

  • String getSelectedText()
  • String getText(): returns the text contents of the component
  • void setEditable(boolean editable): if editable is true, permits the user to edit the component; if false, the user can still navigate using the arrow keys, but cannot alter the text.

Example:

1Frame f = new Frame();
2f.setSize(500,300);
3TextArea ta = new TextArea("Ernesto",6,3);
4f.add(ta);
5f.setVisible(true);

The Container Components

Applet

Applets typically exist in browsers. Changing the size of an applet might be permited or forbidden by the applet’s browser. The browser calls the init() method of the Applet class to start the application.

Frame

A frame is an independent window, decorated by the underlying window system and capable of beign moved around on the screen independent of other GUI windows. Any application that requires a GUI must use one or more frames to contain the desired components.

Common construction:

1        Frame f = new Frame()
2        Frame f = new Frame("Title");

Useful partial method list:

  • void setSize(int width, int height)
  • void setBounds(int pos_x, int pos_y, int width, int height)
  • void setVisible(boolean)

Panel

Applets and frames serve as top-level or outermost GUI components. Panels provide an intermediate level of spatial organization for GUIs. You are free to add all components of a GUI directly into an applet or a frame but you can provide additional levels of grouping by adding components to panels and adding panels to a top-level appplet or frame.

Dialog

A dialog is a pop-up window that typically accepts user input in response to a specific question. The window manager often uses slighty different decorations for dialogs. Dialogs may optionally be made modal, in which case input cannot be directed at the application’s main frame while the dialog is visible. The Dialog class is the superclass of the FileDialog class. The default layout manager for a dialog is Border layout.

Complete constructor:

1Dialog(Frame f, String s, boolean is_modal);

Common construction:

1Dialog d = new Dialog();
2Dialog d = new Dialog(f, "Smash your hard disk?");
3Dialog d = new Dialog(f, "Smash your hard disk?", true);

Example:

1Frame f = new Frame("Ernesto");
2f.setSize(500,300);
3Dialog d = new Dialog(f, "Smash your hard disk?", true);
4d.setSize(200,100);
5d.setLayout(new FlowLayout());
6d.add(new Button("Ok"));
7d.add(new Button("Cancel"));
8f.setVisible(true);
9d.setVisible(true);

The Menu Components

Pull-down menus

Pull-down menus are accessed via a menu bar, which may contain multiple menus. Menu bars may only appear in frames.

Steps to create a frame with a menu bar containing a pull-down menu:

  1. Create a menu bar and attach it to the frame
  2. Create and populate the menu
  3. Attach the menu to the menu bar

There are four kinds of elements that can be mixed and matched to populate a menu:

  • Menu items:

    • Constructor: MenuItem(String text)
  • Check-box menu items:

    • Constructor: CheckboxMenuItem(String text)
    • Methods: getState(boolean checked_state), boolean setState()
  • Separators

    • Menu’s method: addSeparator()
  • Menus

    • When you add a menu to another menu, the first menu’s label appears in the second menu, with a pull-right icon.

Frames have a special method called setHelpMenu() to set the usual last menu of an application.

Example:

 1Frame f = new Frame("Ernesto");
 2f.setSize(500,300);
 3
 4MenuBar mb = new MenuBar();
 5f.setMenuBar(mb);
 6
 7Menu filemenu = new Menu("File");
 8filemenu.add(new MenuItem("New"));
 9filemenu.add(new MenuItem("Open"));
10filemenu.addSeparator();
11filemenu.add(new MenuItem("Save"));
12
13Menu filemenu_sa = new Menu("Save As");
14filemenu_sa.add(new MenuItem(".doc"));
15filemenu_sa.add(new MenuItem(".txt"));
16filemenu.add(filemenu_sa);
17
18filemenu.addSeparator();
19filemenu.add(new MenuItem("Close"));
20mb.add(filemenu);
21
22Menu editmenu = new Menu("Edit");
23editmenu.add(new MenuItem("Cut"));
24editmenu.add(new MenuItem("Copy"));
25editmenu.add(new MenuItem("Paste"));
26editmenu.addSeparator();
27editmenu.add(new CheckboxMenuItem("Insert Mode Enabled"));
28mb.add(editmenu);
29
30Menu helpmenu = new Menu("Help");
31helpmenu.add(new MenuItem("Help Topics"));
32helpmenu.add(new MenuItem("About..."));
33mb.setHelpMenu(helpmenu);
34
35f.setVisible(true);

These can be dynamically popped up at a specified position within a component

Example:

 1import java.awt.*;
 2import java.awt.event.*;
 3
 4class Test {
 5    public static void main(String args[]) {
 6        
 7        Frame f = new Frame("Ernesto");
 8        f.setSize(500,300);
 9        f.setLayout(new FlowLayout());
10        PopupMenu editmenu = new PopupMenu();
11        Button b = new Button("Click for options");
12
13        b.addActionListener(new MyAL(b,editmenu));
14        f.add(b);
15
16        editmenu.add(new MenuItem("Cut"));
17        editmenu.add(new MenuItem("Copy"));
18        editmenu.add(new MenuItem("Paste"));
19        f.add(editmenu);
20
21        f.setVisible(true);
22    }
23}
24
25
26class MyAL implements ActionListener {
27    Button b;
28    PopupMenu m;
29
30    MyAL (Button b, PopupMenu m) {
31        this.b = b;
32        this.m = m;
33    }
34    public void actionPerformed(ActionEvent ae) {
35        m.show(b,20,20);
36    }
37}

AWT - Painting

Painting on a component is accomplished by making calls to a graphics context which is an instance of the Graphics class. A graphics context know how to render onto a single target. The three media graphics context can render onto are:

  • Components
  • Images
  • Printers

There are four classes of “blank” components that have no default appearance and will show up as empty rectangles, unless they are subclassed and given paint() methods. These four component classes are:

  • Applet
  • Canvas
  • Frame
  • Panel

Selecting a Color

You can choose one of these color constants:

  • Color.black
  • Color.blue
  • Color.cyan
  • Color.darkGray
  • Color.gray
  • Color.green
  • Color.lightGray
  • Color.magenta
  • Color.orange
  • Color.pink
  • Color.red
  • Color.white
  • Color.yellow

You can also create your own colors using the basic RGB constructor:

1Color(int Red, int Green, int Blue)

Selecting a Font

The constructor for the Font class look like this:

1Font(String fontname, int style, int size)

fontname: there are only three plataform independent fonts:

  • Serif
  • Sansserif
  • Monospaced

style: some of these constants are commonly used:

  • Font.PLAIN
  • Font.BOLD
  • Font.ITALIC

These ones may be combined:

1new Font("Monospaced", Font.PLAIN + Font.BOLD, 14);

Drawing and Filling

All the rendering methods of the Graphics class specify pixel coordinate positions for the shapes they render. Every component has its own coordinate space, with the origin in the component’s upper-left corner.

drawLine()

This method draws a line from point(x0,y0) to point (x1,y1)

1public void drawLine(int x0, inty0, int x1, int y1);

Example:

1g.drawLine(10, 30, 100, 100);

drawRect() and fillRect()

The drawRect() and fillRect() methods respectively, draw and fill rectangles.

1public void drawRect(int x, int y, int width, int height);
2public void fillRect(int x, int y, int width, int height);

The starting coordinates are x and y. The arguments width and height define the rectangle extension from that point, so only positive values are accepted.

Example:

1g.fillRect(10, 10, 100, 50);
2g.drawRect(20, 20, 100, 50);

drawOval() and fillOval()

An oval is specified by a rectangular bounding box. The two oval-drawing methods require you to specify a bounding box exactly the same way you specified a rectangle in the drawRect() and fillRect() methods:

1public void drawOval(int , int y, int width, int height);
2public void fillOval(int , int y, int width, int height);

Example:

1g.fillOval(10, 10, 100, 50);
2g.drawOval(20, 20, 100, 50);

drawArc() and fillArc()

An arc is a segment of an oval. To specify an arc, you first specify the oval’s bouding box, just as you do with drawOval() and fillOval(). You also need to specify the the starting and ending points of the arc, which you do by specifying a staring angle and the angle swept out by the arc. Angles are measured in degrees. 90 degress is upward and so on, increasing counterclockwise.

The method signatures are:

1public void drawArc(int x, int y, int w, int h, int sDeg, int aDeg)
2public void drawArc(int x, int y, int w, int h, int sDeg, int aDeg)
1                    90º
2                     *
3                     *
4                     *
5            180º ********* 0º
6                     *
7                     *
8                     *
9                    279º

Example:

1g.fillArc(50,50,200,200,45,(360)-90);

drawPolygon() and fillPolygon()

A polygon is a closed figure with an aribtrary number of vertices. The vertices are passed to the drawPolygon() and fillPolygon() methods as two int arrays. The first array contains the x coordiantes of the vertices; the second array contains the y coordinates. A third parameter specifies the number of vertices. The last vertex is connected to the first so there is no need to define the same vertex twice.

The method signatures are:

1public void drawPolygon(int xs[], int ys[], int Points);
2public void fillPolygon(int xs[], int ys[], int Points);

Example:

1int xs[] = {10,110, 110,  10, 60};
2int ys[] = {10, 10, 110, 110, 60};
3g.drawPolygon(xs, ys, 5);

drawPolyline()

A polyline is similar to a polygon, but it is open rather than closed. There is no line segment connecting the last vertex to the first.

1public void drawPolyline(int xs[], int ys[], int Points)

Example:

1int xs[] = {10,110, 110,  10, 60};
2int ys[] = {10, 10, 110, 110, 60};
3g.drawPolyline(xs, ys, 5);

drawString()

The drawString() method paints a string of text. The signature is:

1public void drawString(String s, int x, int y)

The x and y parameters specify the left edge of the baseline of the string. Characters with descenders (g, j, p, q, and y in most fonts) extend below the baseline.

Example:

1g.drawString("Hello World", 10, 30);

drawImage()

An Image is an off-screen representation of a rectangular collection of pixel values:

1boolean drawImage(Image im, int x, int y, ImageObserver observer)

The image observer must be an object that implements the ImageObserver interface.

Clipping

Every graphics context has a clip region, which defines all or part of the associated component. The default clip region for a graphics context is the entire visible region of the associated component. there are methods that retrive and modify the clip region:

1setClip(x, y, width, height)

Example:

1g.setClip(20,20,100,100);
2for (int i = 10 ; i < 300 ; i += 20) {
3    for (int j = 10 ; j < 300 ; j += 20) {
4        g.fillOval(i, j, 15, 15);
5    }
6}

Painting a Contained Component

If an applet or a frame contains components that have their own paint() methods, then all the paint() methods will be called by the environment when necessary.

Example:

1class MyCanvas extends Canvas {
2    public void paint(Graphics g) { }
3} 

Repairing components

If a window overlaps overlaps a Java component and then the window is removed, the paint() method of that component will be invoked to repair the damaged area. In this case the clip region is not the size of the component but the size of the damaged area.

Repainting

To excplicity tell the GUI thread that you want to repaint a component, you call the repaint() method of that component.

The update() method

Components also have an update() method that usually clears the screen before paint() is called:

Illustrative behavior:

1public void update(Graphics g) {
2    g.clearRect(0,0,width,height);
3    paint(g);
4}

If you don’t want the screen to be cleared you can override this method:

1paint void update(Graphics g) {
2    paint(g);
3}

Animation / Double Buffering example

 1import java.awt.event.*;
 2import java.awt.*;
 3
 4class Test {
 5
 6    public static void main(String args[]) {
 7        Frame f = new Frame();
 8        f.setSize(200,200);
 9        MyCanvas c = new MyCanvas();
10        f.add(c);
11        f.setVisible(true);
12
13        for (int i = 0 ; i < 360*4 ; i += 2) {
14            try {
15                Thread.sleep(20);
16            } catch (InterruptedException e) {}
17            c.setArc(i);
18            c.repaint();
19        }
20        System.exit(0);
21    }
22}
23
24class MyCanvas extends Canvas {
25    Image im;
26    Graphics ig;
27    int arc = 0;
28
29    public void addNotify() {
30        super.addNotify();
31        im = createImage(640,480);
32        ig = im.getGraphics();
33    }
34
35    public void update(Graphics g) {
36        paint(g);
37    }
38
39    public void setArc(int arc) {
40        this.arc = arc;
41    }
42
43    public synchronized void paint(Graphics g) {
44        ig.setColor(Color.white);
45        ig.fillRect(0,0,this.getWidth(),this.getHeight());
46        ig.setColor(Color.black);
47
48        ig.setColor(Color.yellow);
49        ig.fillOval(0,0,this.getWidth(),this.getHeight());
50        ig.setColor(Color.black);
51        ig.fillArc(0,0,this.getWidth(),this.getHeight(),arc,10);
52
53        g.drawImage(im,0,0,this);
54
55    } 
56}

A radar animation is shown. The window may be resized and the radar will grow to the new size.

AWT - Events

Java uses since the 1.1 version an event delegation models that allows you to designate any object as a listener for component’s event. A component may have multple listeners for any event type. All listener must implement the appropiate interface. If the interface defines more than one method, the listener may extend the appropiate adaptar class.

There are two types of events; low-level events and semantic events. The AWT’s semantic events are:

  • ActionEvent
  • AdjustmentEvent
  • ItemEvent
  • TextEvent

Event Class Hierarchy

 1        java.util.EventObject
 2          |
 3          |
 4        java.awt.AWTEvent --|
 5          |                 |- ActionEvent 
 6          |                 |- AdjustmentEvent
 7          |                 |- ItemEvent
 8          |                 |- TextEvent
 9          |
10        ComponentEvent --|
11          |              |- ContainerEvent
12          |              |- FocusEvent
13          |              |- PaintEvent
14          |              |- WindowEvent
15          |
16        InputEvent --|
17                     |- KeyEvent
18                     |- MouseEvent

Event details:

  • ActionEvent: generated by activation of components.
  • AdjustmentEvent: generated by adjustment of adjustable components such as scroll bars.
  • ContainerEvent: generated when components are added to or removed from a container.
  • FocusEvent: generated when a component receives or loses input focus.
  • ItemEvent: generated when an item is selected from a list, choice, or check box.
  • KeyEvent: generated by keyboard activity.
  • MouseEvent: generated by mouse activity.
  • PaintEvent: generated when a component is painted.
  • TextEvent: generated when a text component is modified.
  • WindowEvent: generated by window activity (such as iconifying or decoinfying).

Listener Interfaces and their methods

Interface Interface Methods Add Method
ActionListener actionPerformed(ActionEvent) addActionListener()
AdjustmentListener adjustmentValueChanged (AdjustmentEvent) addAdjustmentListener()
ComponentListener componentHidden(ComponentEvent) addComponentListener()
componentMoved(ComponentEvent)
componentResized(ComponentEvent)
componentShown(ComponentEvent)
ContainerListener componentAdded(ContainerEvent) addContainerListener()
componentRemoved(ContainerEvent)
FocusListener focusGained(FocusEvent) addFocusListener()
focusLost(focusEvent)
ItemListener itemStateChanged(ItemEvent) addItemListener()
KeyListener keyPressed(keyEvent) addKeyListener()
keyReleased(keyEvent)
keyTyped(keyEvent)
MouseListener mouseClicked(MouseEvent) addMouseListener()
mouseEntered(MouseEvent)
mouseExited(MouseEvent)
mousePressed(MouseEvent)
mouseReleased(MouseEvent)
MouseMotionListener mouseDragged(MouseEvent) addMouseMotionListener()
mouseMoved(MouseEvent)
TextListener textValueChanged(TextEvent) addTextListener()
WindowListener windowActivated(WindowEvent) addWindowListener()
windowClosed(WindowEvent)
windowClosing(WindowEvent)
windowDeactivated(WindowEvent)
windowDeiconified(WindowEvent)
windowIconified(WindowEvent)
windowOpened(WindowEvent)

Delegation of Event Handling to Listener Objects

An event listener is an object to which a component has delegated the task of handling a particular kind of event. When the component experiences input, an event of the appropriate type is constructed; the event is then passed as the parameter to a method call on the listener. A listener must implement the interface that contains the event-handling method.

The steps to support event handling using this model goes as follows:

  1. Create a listener class that implements the desired listener interface.
  2. Construct the component.
  3. Construct an instance of the listener class.
  4. Call addXXXListener() on the component, passing in the listener object.

Example:

 1import java.awt.event.ActionListener;
 2import java.awt.event.ActionEvent;
 3import java.awt.*;
 4
 5class Test {
 6
 7    public static void main(String args[]) {
 8        Frame f = new Frame();
 9        f.setSize(400,200);
10        f.setLayout(new FlowLayout());
11        Button b = new Button("Please click me");
12        b.addActionListener( new AL() );
13        f.add(b);
14        f.setVisible(true);
15    }
16
17}
18
19class AL implements ActionListener {
20    public void actionPerformed(ActionEvent ae) {
21        System.out.println("Action Performed: "+ae);
22    }
23}
24
25// Result:
26// When the button clicked, the following message is shown:
27//   Action Performed: java.awt.event.ActionEvent[ACTION_PERFORMED,
28//                     cmd=Please click me] on button0

Explicit Event Handling

It is possible to subclass a component and override the method that receives events and dispatches them to listeners. You have to enable the component to process events by calling the enableEvents(long MASK) method.

MASK may be one of these constants:

Mask Method
AWTEvent.ACTION_EVENT_MASK processActionEvent()
AWTEvent.ADJUSTMENT_EVENT_MASK processAdjustmentEvent()
AWTEvent.COMPONENT_EVENT_MASK processComponentEvent()
AWTEvent.CONTAINER_EVENT_MASK processContainerEvent()
AWTEvent.FOCUS_EVENT_MASK processFocusEvent()
AWTEvent.ITEM_EVENT_MASK processItemEvent()
AWTEvent.KEY_EVENT_MASK processKeyEvent()
AWTEvent.MOUSE_MOTION_EVENT_MASK processMouseMotionEvent()
AWTEvent.TEXT_EVENT_MASK processTextEvent()
AWTEvent.WINDOW_EVENT_MASK processWindowEvent()

The steps to handle events using this model goes as follows:

  • Create a subclass of a component
  • In the subclass constructor, call enableEvents(AWTEvent.XXX_EVENT_MASK)
  • Provide the subclass with a processXXXEvent() method; this method should call the superclass’ version before returning.

Example:

 1import java.awt.event.ActionListener;
 2import java.awt.event.ActionEvent;
 3import java.awt.*;
 4
 5class Test {
 6
 7    public static void main(String args[]) {
 8        Frame f = new Frame();
 9        f.setSize(400,200);
10        f.setLayout(new FlowLayout());
11        MyButton b = new MyButton("Please click me");
12        f.add(b);
13        f.setVisible(true);
14    }
15
16}
17
18class MyButton extends Button {
19    public MyButton(String label) {
20        super(label);
21        enableEvents(AWTEvent.ACTION_EVENT_MASK);   
22    }
23
24    public void processActionEvent(ActionEvent ae) {
25        System.out.println("Action Performed: "+ae);
26        super.processActionEvent(ae);
27    }
28}
29
30// Result:
31// 
32// When the button clicked, the following message is shown:
33// 
34//     Action Performed: java.awt.event.ActionEvent[ACTION_PERFORMED,
35//     cmd=Please click me] on button0

You can also make a component subclass handle its own events by making the subclass an event listener of itself:

 1class MyButton extends Button implements ActionListener {
 2    public MyButton(String label) {
 3        super(label);
 4        addActionListener(this);
 5    }
 6
 7    public void actionPerformed(ActionEvent ae) {
 8        System.out.println("Action Performed: "+ae);
 9    }
10}

Adapters

An adapter is simply a class that implements an interface by providing do-nothing methods. Adapters are used when you want to declare only one or a few methods for interfaces that have many ones.

Example:

1class MyMouse implements MouseListener {
2    public void mouseClicked(MouseEvent me) {
3
4    }
5}

This class won’t compile because you haven’t declared the other methods of the interface; mouseEntered(MouseEvent), mouseExited(MouseEvent), etc…

So you use an adapter:

1class MyMouse extends MouseAdapter {
2    public void mouseClicked(MouseEvent me) {
3    }
4}

The compiler won’t complain now because the superclass MouseAdapter already provides do-nothing methods for the interface implementation.

Adapter Class Listener Interface
ComponentAdapter ComponentListener
ContainerAdapter ContainerListener
FocusAdapter FocusListener
KeyAdapter KeyListener
MouseAdapter MouseListener
MouseMotionAdapter MouseMotionListener
WindowAdapter WindowListener

Action Commands

You can define an “Action Command” for every component. The Action Command is usually a string that you can read later from the XXXEvent object reference. It’s useful to describe components explicity without relaying in labels or other features that may change.

You set an action command this way:

1Button b = new Button("Ja");
2b.setActionCommand("OK");

You read your action command like this:

1public void actionPerformed(ActionEvent ae) {
2    String s = ae.getActionCommand();
3}

A good example, is an international set of buttons:

 1class Test {
 2
 3    public static void main(String args[]) {
 4        Frame f = new Frame();
 5        f.setSize(400,200);
 6        f.setLayout(new FlowLayout());
 7        Button b_y[] = new Button[4];
 8        String l_y[] = {"Sim","Ja","Yes","Si"};
 9        Button b_n[] = new Button[4];
10        String l_n[] = {"Nao","Nein","No","No"};
11        AL al = new AL();
12
13        for (int i = 0 ; i < b_y.length ; i++) {
14            b_y[i] = new Button(l_y[i]);
15            b_y[i].setActionCommand("yes");
16            b_y[i].addActionListener(al);
17            f.add(b_y[i]);
18
19            b_n[i] = new Button(l_n[i]);
20            b_n[i].setActionCommand("no");
21            b_n[i].addActionListener(al);
22            f.add(b_n[i]);
23        }
24        f.setVisible(true);
25    }
26
27}
28
29class AL implements ActionListener{
30    public void actionPerformed(ActionEvent ae) {
31        String s = ae.getActionCommand();
32        if (s.equals("yes")) {
33            System.out.println("YES!");
34        } else {
35            System.out.println("NO!");
36        }
37    }
38}
39
40// Result:
41// 
42//     A set of the following buttons is shown:
43// 
44//     Sim Nao Ja Nein Yes No Si No
45// 
46//     When a button is clicked, YES! or NO! are displayed according
47//     to the expected translation.

References

  • Complete Java 2 Certification Study Guide 2nd edition by Simon Roberts, Philip Heller and Michael Ernest. ISBN: 0-7821-2825-4 (Sep 2000 Sybex)
  • The Java Tutorial Third Edition by Mary Campione, Kathy Walrath and Alison Huml. ISBN: 0201703939 (Dec 2000 Addison Wesley)
  • Sun’s Java API 1.3. (Sun Microsystems)