Home >

Java 2 Guide

A concise guide to the Java 2 Programming Language. Every feature of the language is compulsively tested with examples.

Posted on July 9, 2003 by Ernesto Garbarino

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:

    PieGraph.java            OK
    Bars.jav                 WRONG
    StatCalc.jv              WRONG
    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:

 --- [ PieGraph.java ] ----------------------------------------------------

 public class PieGraph {}

 --------------------------------------------------------------------------

 Action: javac PieGraph.java

 Result: OK - The file PieGraph.class is created

Example #2:

 --- [ PieGraph.java ] ----------------------------------------------------

 public class PieGraph {}
 public class BarGraph {}

 --------------------------------------------------------------------------

 Action: javac PieGraph.java

 Result: ERROR - class BarGraph is public and should be declared in a file
 named BarGraph.java

Example #3:

 --- [ BarGraph.java ] ----------------------------------------------------

 public class BarGraph {}
 class BigGraph extends BarGraph {}
 class SmallGraph extends BarGraph {}

 --------------------------------------------------------------------------

 Action: javac BarGraph.java

 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

Description

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:

 abstract              boolean               break
 byte                  case                  catch
 char                  class                 const
 continue              default               do
 double                else                  extends
 false                 final                 finally
 float                 for                   goto
 if                    implements            import
 instranceof           int                   interface
 long                  native                new
 null                  package               private
 protected             public                return
 short                 static                strictfp
 super                 switch                synchronized
 this                  throw                 throws
 transient             true                  try
 void                  volatile              while

Rules

An identifier must begin with one of the following characters:

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.

Examples:

 int apples = 5;         // OK
 int return = 0;         // WRONG
 boolean 3cats = true;   // WRONG
 char $perlvar = 'a';    // OK
 int %bonus = 3000;      // WRONG
 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:

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";

Arrays

A Java array is an ordered collection of:

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

Examples:

    int[] winners;
    char[] alphabet;
    short[][] space;

    int loosers[];
    char character_set[];

    int [] winners;
    char 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:

    int[] winners;

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

    winners = new int[45];        // By using a literal

    int amount = 45;              
    winners = new int[amount];    // By using a variable

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

    char[] alphabet = new char[26];

Please note:

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

    alphabet[0] = 'a';      // OK
    alphabet[25] = 'z';     // OK
    alphabet[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:

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

Examples:

    char letters[] = {'a', 'e', 'i', 'o', 'u'}; 
    double sci_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:

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

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

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

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 signature for main()

    public static void main(String[] args)

Example:

    c:\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:

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

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:

    class MyClass {
        int a = 50;
        int b = 60;
        int c;
        static z = 3000;
    }

Example of automatic variables:

    public int take_care () {
        int a = 50;         // OK
        int b = 60;         // OK
        inc c;              // WRONG!
        return c;
    }

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:

    int age = 24;
    add_a_year(age);
    System.out.println("My age is now " + age);

    static void add_a_year(int age) {
        age++;
    }

    Result: 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:

    static public void main(String args[]) {
        int age[] = { 24 };
        add_a_year(age);
        System.out.println("My age is now " + age[0]);
    }

    static void add_a_year(int obj_age[]) {
        obj_age[0]++;
    }

    Result: 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:

    static public void main(String args[]) {
        TextField username = new TextField("Ernest");
        change_user(username);
        System.out.println("Welcome back " + username.getText());
    }

    static void change_user(TextField user) {
        user.setText("Adriana");
    }

    Result: 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:

    static public void main(String args[]) {
        TextField username = new TextField("Ernest");
        change_user(username);
        System.out.println("Welcome back " + username.getText());
    }

    static void change_user(TextField user) {
        user = new TextField("Taffy");
        user.setText("Adriana");
    }

    Result: 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:

    user = 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:

    user.setText("Adriana");

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

    static public void main(String args[]) {
        TextField username = new TextField("Ernest");
        change_user(username);
        System.out.println("Welcome back " + username.getText());
    }

    static void change_user(TextField user) {
        TextField old_user = user;
        user = new TextField("Taffy");
        user = old_user;
        user.setText("Adriana");
    }

    Result: 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

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

    System.gc()
    Runtime.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

double literals

Be aware of double literals:

    a = 1.2456
    b = 1.2456f

Operators

Table of Operators in Descending Order of Precedence

Category Operators
Unary ++ – + - ! ~ ()
Arithmetic * / %
+ -
Shift << >> >>>>
Comparision < <= > >= instanceof
== !=
Bitwise & ^ |
Short-circuit && ||
Conditional ?:
Assingment = “op=”

Evaluation Order

Consider this example:

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

    Result: 1-0-3

The program was interpreted as follows:

    a[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:

    b = 0;
    a[1] = b;

What about this?

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

The assignment was interpreted this way:

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

The Unary Operators

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 –

Examples:

    ++x;
    x++;
    y--;
    --y;
    x--;

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:

    x = 1;
    y = x++;

    Results: 

    y = 1
    x = 2

These couple of sentences are interpreted this way:

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

A bit of pre-decrement now:

    x = 1;
    y = --x;

    Results:

    y = 0
    x = 0

The example above was interpreted as follows:

    x = 1;
    x = x - 1;
    y = 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:

    int x = +5;   // OK. The positive value 5 is assigned to x.

    short a = +7  // OK. The positive value 7 is assigned to a. 
    short b = +a  // Error. +a is promoted to int and there is
                  // a "loss of precision" error.

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

Examples:

    int y = -(5 * 6);
    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: ~

Example:

    int a = 0xFFFFFFFF;
    a = ~a;
    System.out.println(a);

    Result: 0

The Boolean Complement Operator: !

Example #1:

    boolean married = flase;
    if (!married) {
        System.out.println("Why not?");
    }

    Result: Why not?

Example #2:

    boolean oxygen = false;
    boolean hydrogen = true;

    oxygen = !oxygen;

    if (!(oxygen && hydrogen)) {
        System.out.println("No water today");
    } 

    Result: 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:

    int a = (int)2.3;                 // Result: 2
    char 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:

    int x = 6;
    int z = 15;

    int a = x * z / 3;
    int b = z / 3 * x;

    Results:

    a = 30
    b = 30

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

Example #2:

    int x = 6;
    int z = 14;

    int a = x * z / 3;
    int b = z / 3 * x;

    Results: 

    a = 28
    b = 24

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

Example #1:

    a = 6 * 15;             // Result = 90
    a = 90 / 3;             // Result = 30

    b = 15 / 3;             // Result = 5
    b = 5 * 6;              // Result = 30

Example #2:

    a = 6 * 14;             // Result = 84
    a = 84 / 3;             // Result = 28    (Right value)

    b = 14 / 3;             // Result = 4 (4,666...)
    b = 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:

    int x = 6;
    float z = 14;

    int a = (int)( x * z / 3);
    int b = (int)( z / 3 * x);

    Results:
     
    a = 28
    b = 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:

    int 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 casted 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

Comparisions with Not a Number

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

    float a = Float.NaN;
    double b = Double.NaN;

    if (Float.isNaN(a)) {
        System.out.println("It is Not a Number!");
    }
    if (!Double.isNaN(a)) {
        System.out.println("Yes, this is a number!");
    }

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

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

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

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:

    byte a = 127;  
    a++;

    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.

    byte a = 127;  
    a = (byte)(a + 128);  

    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:

    byte a = (byte)255;
    byte a = (byte)0xFF;

    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:

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:

    int a = -65;
    int r = 0;

    r = a >> 1;   // Result: -33
    r = 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:

    byte pattern:                              10111111                        
    int pattern:    11111111 11111111 11111111 10111111

Shifted 1 bit to the left:

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

Examine also this example:

    int a = -65;
    int r = 0;

    r = a << 1;               // Result: -130
    r = (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:

    <  <=  >  >=  == !=

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 wether 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 >=

     < Less than
    =< Less than or equal to
     > Greater than
    => Greater than or equal to

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

Example #1:

    char x = 'A';
    char y = 'Z';
    char z = 'G';

    if (z >= x && z <= y) {
        System.out.println("It's part of the alphabet");
    }

    Result: It's part of the alphabet

Example #2:

    float a = 2.999999f;
    int b = 3;

    if (b > a) {
        System.out.println("Sorry, b is greater");
    }

    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

Example #1:

    TextField a = new TextField();

    if (a instanceof TextField) {
        System.out.println("Yes, it is!");
    } else {
        System.out.println("Sorry, it isn't");
    }

    Result: Yes, it is!   

    a is an instace of the TextField class.

Example #2:

    class SuperTextField extends TextField {}

    TextField a = new SuperTextField();

    if (a instanceof TextField) {
        System.out.println("Yes, it is!");
    } else {
        System.out.println("Sorry, it isn't");
    }

    Result: Yes, it is!

    a is an instance of a subclass of TextField

Example #2.b:

    TextField a = new TextField();

    if (a instanceof SuperTextField) {
        System.out.println("Yes, it is!");
    } else {
        System.out.println("Sorry, it isn't");
    }

    Result: Sorry, it isn't

    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:

    String var1 = "Hello"; 
    TextField field = new TextField("Hello");

    if (field.getText().equals(var1)) {
        System.out.println("Equal!");
    } 

    Result: Equal!

The Bitwise Operators: &, ^, and |

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

    & AND
    ^ XOR
    | 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)

    Op1 Op2 Result
    0   0   0
    0   1   0
    1   0   0
    1   1   1

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

Example:

    10011100 = 156
    00011001 = 25
    -------- (AND)
    00011000 = 24

XOR (Op1 ^ Op2)

    Op1 Op2 Result
    0   0   0
    0   1   1
    1   0   1
    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:

    10011100 = 156
    00011001 = 25
    -------- (XOR)
    10000101 = 133

OR (Op1 | Op2)

    Op1 Op2 Result
    0   0   0
    0   1   1
    1   0   1
    1   1   1

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

Example:

    10011100 = 156
    00011001 = 25
    -------- (OR)
    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:

    boolean her_status = true;
    int my_status = 1;

    if (her_status & my_status) {
        // Do something
    }

    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:

So, for any boolean value x:

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

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

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:

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

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.

    a = x ? b : c;

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

    if (x) {
        a = b;
    } else {
        a = c;
    }

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

Example:

    int customer_age = 17;

    boolean adults_only = true;
    int minimum_age = adults_only ? 18 : 3;

    if (customer_age < minimum_age) {
        System.out.println("Sorry, grow up a little bit");
    } 

    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:

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

Examples:

    byte a = 16;
    x += 4;            // x = (byte)(x + 4);

    short b = 5;
    b *= 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:

    String s = "Hello ";
    s += "dude"; 
    System.out.println(s);

    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:

    int a = 128;

    int b = a >>> 32;         // Result: b = 128
    int c = a << 32;          // Result: c = 128
    int d = a >> 32;          // Result: d = 128

    byte x = -1;              
    x >>= 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.

    int a = 15;
    Integer 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:

    if (a == b) {
        // Wrong.
    }

But this is ok:

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

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

    String x = "Hello world";
    String y = "Hello world";
    String a = new String("Hello world");
    String b = new String("Hello world");
        
    if (x == y) {
        System.out.println("x == y");
    } 
    if (x == a) {
        System.out.println("x == a");
    } 
    if (a == b) {
        System.out.println("a == b");
    } 

    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:

    String x = "Hello world";
    String y = "Hello world";
    String a = new String("Hello world");
    String b = new String("Hello world");

    y += " dude!";
    System.out.println("y = " + y);
    y = y.substring(0,11);
    System.out.println("y = " + y);

    if (x == y) {
        System.out.println("x == y");
    } 
    if (x == a) {
        System.out.println("x == a");
    } 
    if (a == b) {
        System.out.println("a == b");
    }   

    Result:

    y = Hello World dude!
    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:

    byte a = 5;
    float c = 2;
    boolean yes = true;

    System.out.println( yes ? a : c );

    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:

    while (boolean_condition) 
        statement

Block form:

    while (boolean_condition) {
        statement 1
        statement 2
        ...
    } 

Example #1:

    int i = 0;
    while (i++ < 3)
        System.out.println("Hello "+i);
    System.out.println("Bye!"); 

    Result:        

        Hello 1
        Hello 2
        Hello 3
        Bye!

Example #2:

    int i = 0;
    while (i > 3) {
        i++;
        System.out.println("Hello");
    }
    System.out.println("Bye!");

    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:

    do
        statement
    while (boolean_condition);

Block form:

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

Example #1:

    int i = 0;
    do 
        System.out.println("Hello "+i);
    while (i++ < 3);

    System.out.println("Bye!");

    Result:

        Hello 0
        Hello 1
        Hello 2
        Hello 3
    Bye!

Example #2:

    int i = 0;
    do {
        System.out.println("Hello "+i);
        i++;
    } while (i > 3);
    System.out.println("Bye!");

    Result:

        Hello 0
        Bye!

The for() loop

Single form:

    for (statement ; condition ; expression)
        loop_body

Block form:

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

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 construct:

    for (;;) {}

creates a loop that repeats forever.

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:

    for (int j = 0, k = 0; k <= 5 ; k++, j = (k*2)) {
        System.out.println("2*" + k + " = " +j);
    }

    Result:

        2*0 = 0
        2*1 = 2
        2*2 = 4
        2*3 = 6
        2*4 = 8
        2*5 = 10

Example #2:

    int j;
    for (j = 0, int k = 0; k <= 5 ; k++, j = (k*2)) {
        System.out.println("2*" + k + " = " +j);
    }

    // WRONG: j = 0, int k = 0

Example #3:

    for (int j = 0, long k = 0, int k = 0; k <= 5 ; k++, j = (k*2)) {
        System.out.println("2*" + k + " = " +j);
    }

    // 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:

    int i, j, k;
    int 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.

    char a[] = {'a','b','c','d','e','7','f','g','h'};

    for (int i = 0 ; i < a.length ; i++) {

        if (a[i] == '7') continue; 

        System.out.print("Letter: " + a[i]);
        System.out.print(" = #");
        System.out.println((int)(a[i]));

    }

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.

    MyLabel: for (int i = 10 ; i <= 13 ; i++) {
        for (int j = 2 ; j <= 6 ; j += 2) {

            int r = i / j;
            if (r == 6) continue MyLabel;
            System.out.print(i + " / " + j);
            System.out.print(" = ");
            System.out.println(r);
        }

        System.out.println("------------------");
    }

    Result:

        10 / 2 = 5
        10 / 4 = 2
        10 / 6 = 1
        ------------------
        11 / 2 = 5
        11 / 4 = 2
        11 / 6 = 1
        ------------------

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:

    for (int i = 1 ; i < 10 ; i++) {
        if (i > 3) break;
        System.out.println(i);
    }

    Result: 

    1
    2
    3

Example #2:

    for (int i = 1 ; i < 10 ; i++) {
        int x = 0;
        System.out.print("Round " + (char)(i+64) + ": ");
        while (++x <= 3) {
            if (i >= 4 && i <= 8 ) break;
            System.out.print(x + ", ");
        }
        System.out.println("end.");
    }
    System.out.println("Job done");

    Result:

        Round A: 1, 2, 3, end.
        Round B: 1, 2, 3, end.
        Round C: 1, 2, 3, end.
        Round D: end.
        Round E: end.
        Round F: end.
        Round G: end.
        Round H: end.
        Round I: 1, 2, 3, end.
        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:

    MyLabel: for (int i = 1 ; i < 10 ; i++) {
        int x = 0;
        System.out.print("Round " + (char)(i+64) + ": ");
        while (++x <= 3) {
            if (i >= 4 && x >= 2) break MyLabel;
            System.out.print(x + ", ");
        }
        System.out.println("end.");
    }
    System.out.println("Job done");

    Result:

    Round A: 1, 2, 3, end.
    Round B: 1, 2, 3, end.
    Round C: 1, 2, 3, end.
    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.

    if (condition) {
        statement 1
        statement 2
        ....
    }

Example #1:

    int x = 1;
    if (x > 1) {
        System.out.println("It's greater than 1");
    } else {
        System.out.println("It's lower than 1 :(");
    }

    Result: It's lower than 1 :(

Example #2:

    int x = 1;
    if (x == 1) System.out.println("Equals to 1");
    if (x < 5) System.out.println("It's lower than 5");

    Result: 

    Equals to 1
    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:

    byte x = 2;
    switch (x) {
        case 1:
            System.out.println("You won!");
            break;
        case 2:
            System.out.println("2nd, great!");
            break;
        case 3:
            System.out.println("3rd, well done");
            break;
        case 4:
        case 5:
        case 6:
            System.out.println("Close enough!");
        default:
            System.out.println("You lost");
            break;
    }

    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:

Example #1:

    public static void main(String args[]) {

        System.out.println( DoubleIt("Hello world") );
        System.out.println( DoubleIt(15) );
    }

    static String DoubleIt(String name) {
        name += " " + name;
        return name;
    }
    static String DoubleIt(int id) {
        id *= 2;
        return Integer.toString(id);
    }

    Result:

    Hello Hello
    30

Example #2:

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

Example #3:

    int MyMethod(int value) { }
    float MyMethod(int number) { } // ERROR!!!. The types should be 
                                   // 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:

    public static void main(String args[]) {
        allStars(15);
    }

    static void allStars(String name) {
        name = "*** " + name + " ***";
        System.out.println(name);
    }
    static void allStars(int id) {
        String s = ""+id;
        allStars(s);
    }

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:

Example #1:

    class Animal {
        void saySomething() {
            System.out.println("Our kind is unable to speak");
        }
    }

    class Dog extends Animal {
        void saySomething() {
            System.out.println("Wof!");
        }

    }

    Animal a[] = new Animal[2];
    a[0] = new Animal();
    a[1] = new Dog();

    a[0].saySomething();
    a[1].saySomething();

    Result:

    Our kind is unable to speak
    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:

    class Animal {
        void saySomething() {
            System.out.println("Our kind is unable to speak");
        }
    }

    class Dog extends Animal {
        void saySomething() {
            super.saySomething();
            System.out.println("Wof!");
        }
    }

    Dog d = new Dog();
    d.saySomething();

    Result:

    Our kind is unable to speak
    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:

    class Test {

        static int i = 0;

        public static void main(String args[]) {

            Terrier t = new Terrier(15);
            System.out.println("Count: "+ i);
        }
    }

    class Animal {
        Animal (int increment) {
            Test.i += increment;
            System.out.println("Animal constructor");
        }
    }

    class Dog extends Animal {
        Dog (int increment) {
            super(increment);
            Test.i += increment;
            System.out.println("Dog constructor");
        }
    }

    class Terrier extends Dog {
        Terrier (int increment) {
            super(increment);
            Test.i += increment;
            System.out.println("Terrier constructor");
        }
    }

    Result:

        Animal constructor
        Dog constructor
        Terrier constructor
        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:

    class Test {

        public static void main(String args[]) {

            Dog d = new Dog();
            Animal a = new Animal(); //     ERROR. No constructor
            System.out.println("Legs: "+ d.legs);
            System.out.println("Mammal: "+ d.mammal);
            System.out.println("Vertebrate: "+ d.vertebrate);
        }
        
    }

    class Animal {

        int legs = 0;
        boolean mammal = false;
        boolean vertebrate = false;

        Animal (int l, boolean m, boolean v) {
            legs = l;
            mammal = m;
            vertebrate = v;
            System.out.println("Animal constructor");
        }
    }

    class Dog extends Animal {
        Dog () {
            super(4,true,true);
            System.out.println("Dog constructor");
        }
    }

    Result:

        Animal constructor
        Dog constructor
        Legs: 4
        Mammal: true
        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:

    Animal() {}

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:

    class Bird extends Animal {
        Bird() {
            System.out.println("Bird constructor");
        }
    }

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

    Bird 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:

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

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:

    class Test {

        public static void main(String args[]) {

            Animal d = new Animal(true);
            System.out.println("Legs: "+ d.legs);
            System.out.println("Mammal: "+ d.mammal);
            System.out.println("Vertebrate: "+ d.vertebrate);
        }
        
    }

    class Animal {

        int legs = 0;
        boolean mammal = false;
        boolean vertebrate = false;


        Animal (int l, boolean m, boolean v) {
            legs = l;
            mammal = m;
            vertebrate = v;
            System.out.println("Animal constructor - all arguments");
        }

        Animal (boolean m) {       
            this(4,m,true);  
            System.out.println("Animal constructor - mammal argument");
        }
    }

    Result:

        Animal constructor - all arguments
        Animal constructor - mammal argument
        Legs: 4
        Mammal: true
        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:

    class Test {
        public static void main(String args[]) {
            Reptile r = new Reptile(4);
        }
    }

    class Animal {
        int legs = 0;
        boolean cold_blood = false;
    }

    class Reptile extends Animal {
        Reptile (int l) {
            legs = l;
            System.out.println("Reptile constructor - leg argument");
        }       
    }

    Result:

        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:

    class Test {
        public static void main(String args[]) {
            Reptile r = new Reptile(4);
        }
    }

    class Animal {
        int legs = 0;
        boolean cold_blood = false;
        Animal() {
            System.out.println("Animal constructor - default");
        }
    }

    class Reptile extends Animal {
        Reptile (int l) {
            legs = l;
            System.out.println("Reptile constructor - leg argument");
        }       
    }

    Result:

        Animal constructor - default
        Reptile constructor - leg argument

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

Example #3:

    class Test {
        public static void main(String args[]) {
            Reptile r = new Reptile(4);
        }
    }

    class Animal {
        int legs = 0;
        boolean cold_blood = false;

        Animal() {
            System.out.println("Animal constructor - default");
        }

        Animal (int l) {
            legs = l;
            System.out.println("Animal constructor - leg argument");
        }

    }

    class Reptile extends Animal {
        Reptile (int l) {

            legs = l;
            System.out.println("Reptile constructor - leg argument");
        }       
    }
 
    Results:

        Animal constructor - default
        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:

    class Test {
        public static void main(String args[]) {
            Reptile r = new Reptile(4);
        }
    }

    class Animal {
        int legs = 0;
        boolean cold_blood = false;

        Animal (int l) {
            legs = l;
            System.out.println("Animal constructor - leg argument");
        }

    }

    class Reptile extends Animal {
        Reptile (int l) {
            legs = l;
            System.out.println("Reptile constructor - leg argument");
        }       
    }

    Result:

        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:

    class Test {
        public static void main(String args[]) {
            Reptile r = new Reptile(4);
        }
    }

    class Animal {
        int legs = 0;
        boolean cold_blood = false;

        Animal (int l) {
            legs = l;
            System.out.println("Animal constructor - leg argument");
        }

    }

    class Reptile extends Animal {
        Reptile (int l) {
            super(l);
            System.out.println("Reptile constructor - leg argument");
        }       
    }

    Result:

        Animal constructor - leg argument
        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:

        Reptile r = new Reptile();
        Animal 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:

    class Test {
        public static void main(String args[]) {
            Reptile r = new Reptile(4);
        }
    }

    class Animal {
        int legs = 0;
        boolean cold_blood = false;

        Animal (int l) {
            legs = l;
            System.out.println("Animal constructor - leg argument");
        }
        Animal (boolean b) {
            cold_blood = b;
            System.out.println("Animal constructor - blood argument");
        }

    }

    class Reptile extends Animal {
        Reptile (int l) {
            this(true);     // WATCH OUT!!!
            System.out.println("Reptile constructor - leg argument");
        }       
        Reptile (boolean b) {
            cold_blood = b;
            System.out.println("Reptile constructor - blood argument");
        }
    }

    Result:

        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:

    class Test {
        public static void main(String args[]) {
            Reptile r = new Reptile(4);
        }
    }

    class Animal {
        int legs = 0;
        boolean cold_blood = false;

        Animal (int l) {
            legs = l;
            System.out.println("Animal constructor - leg argument");
        }
        Animal (boolean b) {
            cold_blood = b;
            System.out.println("Animal constructor - blood argument");
        }

    }

    class Reptile extends Animal {
        Reptile (int l) {
            this(true);
            System.out.println("Reptile constructor - leg argument");
        }       
        Reptile (boolean b) {
            super(b);
            System.out.println("Reptile constructor - blood argument");
        }
    }

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:

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:

    class Test {
        class Inner {
            Inner () {
                System.out.println("Hello World");
            }
        }

        public static void main(String args[]) {
            Test.Inner i = new Test().new Inner();
            
        }
    }

    Result:

        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:

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

Example #2:

    class Test {
        String h = "Hello World";

        class Inner {
            Inner () {
                System.out.println(h);
                Bye();
            }
        }

        public static void main(String args[]) {
            Test.Inner i = new Test().new Inner();
        }

        void Bye() {
            System.out.println("Good bye!");
        }
    }

    Result:

        Hello World
        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:

    class Test {
        private static String h = "Hello World";

        static class Inner {
            static void MyMethod () {
                System.out.println(h);
            }
        }

        public static void main(String args[]) {
            Test.Inner.MyMethod();
        }

    }

    Result:

        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:

    class Test {

        public static void main(String args[]) {
            Hi("Ernest");
        }

        static void Hi(String name) {

            final String h = "Hello World " + name;
            int j = 5;

            class Inner {
                void MyMethod () {
                    System.out.println(h);
                    // j++; // ERROR !!!
                }
            }

            Inner i = new Inner();
            i.MyMethod();
        } 

    }

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

    new 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:

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

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

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

Example:

    class Test {

        public static void main(String args[]) {

            Base o = new Base() { 
                public void Hi() {
                    System.out.println("Hi!");
                }
                public void Bye() {
                    System.out.println("Bye");
                }
            };

            o.Hi();
        }

    }

    interface Base {
        public void Hi();
    }

    Result:

        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:

    class Test {

        public static void main(String args[]) {

            Base o = new Base(15) { 
                public void Hi() {
                    System.out.println("Hi!");
                }
                public void Bye() {
                    System.out.println("Bye");
                }
            };

            o.Hi();
            // o.Bye(); // ERROR. 
        }
    }

    class Base {
        Base (int i) {
            System.out.println("I've got "+i);
        }
        public void Hi() { }
    }

    Result:

        I've got 15
        Hi!

Initialising Anonymous classes

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

    class Test {

        public static void main(String args[]) {
            Object o = new Object() { 
                {
                    System.out.println("Init");
                }
            };
        }
    }

    Result:

        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:

    int x = (int)(Math.random()*5);
    int z[] = new int[4];
    int r = 0;
    try {
        r = 15 / x;
        z[x] = r;
    }
    catch (ArithmeticException e) {
        System.out.println("Arithmetic error: "+e);
    }
    catch (ArrayIndexOutOfBoundsException e) {
        System.out.println("Array error: "+e);
    }
    finally {
        System.out.println("Done!");
    }   

    Result if x is between 1 and 3:

        Done!

    Result if x is 0:

        Arithmetic error: java.lang.ArithmeticException: / by zero
        Done!

    Result if x is 4:

        Array error: java.lang.ArrayIndexOutOfBoundsException
        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 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:

    try {

        File inputFile = new File("d:\\workbench\\test.txt");
        FileReader in = new FileReader(inputFile);
        int c;
        while ((c = in.read()) != -1) {
            System.out.print((char)c);
        }
        in.close();
        System.out.println();

    }
    catch (EOFException e) {   // More specific Exception
        System.out.println(e);
    }
    catch (IOException e) {    // More general Exception    
        System.out.println(e);
    }

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

        class AgeException extends Throwable {}

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

        class AgeException extends Throwable {
            String my_error_message;
            public AgeException() { }
            public AgeException(String s) {
                my_error_message = s;
            }
        }

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

        class AgeException extends Throwable {
            String my_error_message;
            public AgeException() { }
            public AgeException(String s) {
                my_error_message = s;
            }
            public String getMessage() {
                return my_error_message;
            }
        }

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

        static void ageCounter(int age) throws AgeException {}

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

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

Our complete Exception-test program looks like this:

    class Test {
        public static void main(String args[]) {
        
            try {
                ageCounter(250);
            }
            catch (AgeException e) {
                System.out.println();
                System.out.println(e);
            }
        }

        static void ageCounter(int age) throws AgeException {

            if (age > 200) {    
                throw new AgeException("Nobody is that old");
            }
            for (int i = 0 ; i < age ; i++) {
                System.out.print(".");
            }       
        }
    }

    class AgeException extends Throwable {
        String my_error_message;
        public AgeException() { }
        public AgeException(String s) {
            my_error_message = s;
        }
        public String getMessage() {
            return my_error_message;
        }
    }

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:

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

Categories of Exceptions

    java.lang.Throwable > java.lang.Exception 
    java.lang.Throwable > java.lang.Exception > java.lang.RunTimeException
    java.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:

    class Care {
        public static void main(String args[]) {
            try {
                readIt("c:\\workbench\\test.txt");
            }
            catch (IOException e) {
                System.out.println(e);
            }
        }

        public static void readIt(String my_file) throws IOException {

            File inputFile = new File(my_file);
            FileReader in = new FileReader(inputFile);
            int c;

            while ((c = in.read()) != -1) {
                System.out.print((char)c);
            }
            in.close();
            System.out.println();
        }
    }

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:

    class Disk {
        void readFile() throws EOFException {}
    }

    class FloppyDisk extends Disk {            // ERROR !!!
        void readFile() throws IOException {}
    }

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

Then:

    class Disk {
        void readFile() throws IOException {}
    }

    class FloppyDisk extends Disk {            // OK.
        void readFile() throws EOFException {}
    }

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

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:

class Test {

    public static void main(String args[]) {

        Dog terrier = new Dog();
        terrier.a = 5;            // Ok.
        terrier.b = 5;            // Error. b is private.
    }


    class Dog {
        public int a = 1;   
        private int b = 1;
    }
}

Example #2:

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

Example #3:

    class Canine {

        public static void main(String args[]) {

            Dog     dog1 = new Dog (); 
            Terrier dog2 = new Terrier("taffy", 3);

            System.out.println(dog1.info()); // Error. info() is private
            System.out.println(dog2.info()); // Error. info() is private
        }
    }

    class Dog {
        private int age = 2;        
        String name = "homeless dog";

        private String info() {
            return name + " is " + age + " years old";
        }
    }

    class Terrier extends Dog {
        Terrier(String dogs_name, int dogs_age) {
            age = dogs_age;    // Error. age is private.
            name = dogs_name;
        }
    }

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

    homeless dog is 2 years old
    taffy 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:

    --- [ Canine.java ] ------------------------------------------------------

    import my_dog_package.Dog;

    class Canine {

        public static void main(String args[]) {

        }
    }

    --------------------------------------------------------------------------

    --- [ my_dog_package/Dog.java ] ------------------------------------------

    package my_dog_package;

    class Dog {                   // Error. Must be declared public
        int age = 0;        
        String name = "homeless dog";

        private String info() {
            return name + " is " + age + " years old";
        }
    }

    --------------------------------------------------------------------------

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.

    --- [ Canine.java ] ------------------------------------------------------

    import my_dog_package.Dog;
    import my_dog_package.Bulldog;

    class Canine {

        public static void main(String args[]) {

                Dog     anydog  = new Dog (); 
                Bulldog spike = new Bulldog("Spike",5); 
                Terrier taffy = new Terrier("Taffy",3);

                System.out.println(anydog.info()); 
                System.out.println(spike.info());
                System.out.println(taffy.getTerrierAge());
                // System.out.println(spike.getAge()); // WRONG!!

        }
    }

    --------------------------------------------------------------------------

    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.
    
    --- [ my_dog_package/Dog.java ] ------------------------------------------

    package my_dog_package;

    public class Dog {

        protected int age = 0;      
        protected String name = "homeless dog";

        public String info() {
            return name + " is " + age + " years old";
        }

        protected int getAge() {  // SEE INFO !!
            return age;
        }

    }

    --------------------------------------------------------------------------

    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. 

    --- [ my_dog_package/Bulldog.java ] --------------------------------------

    package my_dog_package;

    public class Bulldog extends Dog {
        public Bulldog(String dogs_name, int dogs_age) {
            age = dogs_age;    
            name = dogs_name;
        }
        public int getBulldogAge() {  // TAKE AT LOOK AT THIS!!
            return (getAge());
        }
    }

    --------------------------------------------------------------------------

    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. 

    --- [ Terrier.java ] -----------------------------------------------------

    import my_dog_package.Dog;

    public class Terrier extends Dog {
        Terrier(String dogs_name, int dogs_age) {
            age = dogs_age;    
            name = dogs_name;
        }
        int getTerrierAge() {   // TAKE AT LOOK AT THIS!
            return (getAge());
        }
    }

    --------------------------------------------------------------------------

    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

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:

Example #1:

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

Example #2:

    class Dog {
        int age = 0;
        final void getAge() {
        }
    }

    class Terrier extends Dog{
        void getAge() {  // WRONG. Final method can't be overridden
        }
    }

Example #3:

    class Dog {
        final Dog() {    // WRONG. Constructors may not be defined 
        }                // final.
    }

Example #4:

    final TextField my_tf = new TextField();
    
    my_tf.setText("Felix");             // LEGAL
    my_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:

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:

Basic rules for static methods:

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

Example #1:

    class Test {
        public static void main(String args[]) {
            Car compact = new Car();  // WRONG. Car is abstract. 
            Ford focus = new Ford();  // OK.                 
        }
    }

    abstract class Car {}

    class Ford extends Car {}

Example #2:

    class Car {  // WRONG. The class should also be abstract
        String color = "white";
        abstract void setColor(String car_color);
        abstract String getColor() {  // WRONG. Abstract methods 
            // Do nothing           // should have no body.
        }
    }

    class Ford extends Car {
        void setColor(String car_color) {
            color = car_color;
        }
        String getColor() {
            return color;
        }
    }

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:

Example #1:

    class Test {
        public static void main(String args[]) {
            Car honda = new Car();
            honda.wheels++;
            Car bmw = new Car();
            bmw.wheels++;
            System.out.println(bmw.wheels);
        }
    }

    class Car {
        static int wheels = 0;
    }

    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:

    class Care {

        public static void main(String args[]) {
            Car honda = new Car();
            honda.setWheels(4);
            honda.getWheels();

            Car.getWheels();
            Car.setWheels(3);    // A non-static method cannot be 
                                 // referenced from a static context.       
        }
    }

    class Car {
        static int wheels = 0;
        static void getWheels() {
            System.out.println(wheels);
        }
        void setWheels(int car_wheels) {
            wheels = car_wheels;
        }
    }

Example #3:

    class Vehicle {
        static int cars = 5;
        int bikes = 8;

        public static void main(String args[]) {
            cars++;
            bikes++ // Error. bikes is a non-static variable 
            Vehicle var = new Vehicle();
            var.bikes++;
            System.out.println("cars = "+cars+", bikes = "+var.bikes);
        }

        static {
            cars++;
            // bikes++; // Error. bikes is a non-static variable
        }
    }

    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:

    class Car {
        static void getColor() {
        }
    }

    class Ford extends Car {
        void getColor() { // Error. static methods can't be overridden
        }
    }

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:

    class Car implements Serializable {
        private String model;
        private String customer_name;
        private transient int customer_credit_Card;
    }

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:

Widening conversions

Assignment

General rules for primitive assignment conversions:

Example #1:

    int i = 5;
    float 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:

    public static void main(String args[]) {
        byte x = 126;
        System.out.println( DoIt(x) );
    }

    static String DoIt(int a) {
        return "I've received an int of value "+a;
    }

    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:

    public static void main(String args[]) {
        byte x = 126;
        System.out.println( DoIt(x) );
    }

    static String DoIt(int a) {
        return "I've received an int of value "+a;
    }
    static String DoIt(byte a) {
        return "I've received a byte of value "+a;
    }

    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:

    public static void main(String args[]) {
        float x = 1.26f;
        System.out.println( DoIt( (int)x ) );
    }

    static String DoIt(int a) {
        return "I've received an int of value "+a;
    }

    Result: I've received an int value of 1

Last example:

    public static void main(String args[]) {
        char x = 'A';
        System.out.println( DoIt(x) );
    }

    static String DoIt(int a) {
        return "I've received an int of value "+a;
    }
    static String DoIt(byte a) {
        return "I've received a byte of value "+a;
    }

    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:

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

Example #1:

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

Example #2:

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

Example #3:

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

Example #4:

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

Example #5:

    float a = 1.0f;
    int b = 15;
    int x = a * b;       // Error. Expression is promoted to float.
    int 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:

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

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

    byte a = 1;
    boolean 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:

    int a = -128;
    byte x = (byte)a;

    float a = -128.0f;
    byte x = (byte)a;

    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:

    int a = 128;
    byte x = (byte)a;

    Result: -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:

    int a = 129;
    byte x = (byte)a;

    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:

    int a = 257;
    byte x = (byte)a;

    Result: 1

    257 = [00000000] [00000000] [00000001] [00000001]
                     32-bits int value      

                                       1 = [00000001]
                                      8-bits byte value 

Example #2:

    int a = -135;
    byte x = (byte)a;

    Result: 121
        
    -135 = [11111111] [11111111] [11111111] [01111001]
                     32-bits int value      

                                      121 = [01111001]
                                      8-bits byte value

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:

    int a = 65535;
    char x = (char)a;

    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:

    int a = 65539;
    char x = (char)a;

    Result: 3

      65539 = [00000000] [00000001] [00000000] [00000011]
                     32-bits int value      

                              3 = [00000000] [00000011]
                                   16-bits char value

Example #2:

    int a = -1;
    char x = (char)a;

    Result: 65535

         -1 = [11111111] [11111111] [11111111] [11111111]
                       32-bits int value      

                          65535 = [11111111] [11111111]
                                   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:

    double a = 1.9999999;
    int x = (int)a;

    Result: 1

Object Reference Conversion

Object reference conversion takes place in:

(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:

Conversion rules for implicit casting on this context:

        OLD_Type a = new OLD_Type;
        NEW_Type b = a;     

                       OLD\_Type = Class                             OLD\_Type = Interface                           OLD\_Type = Array
NEW_Type = Class NEW_Type = Interface NEW_Type = Array OLD_Type must be a subclass of NEW_Type. OLD-Type must implement interface NEW_Type Compiler ERROR NEW_Type must be Object. OLD_Type must be a subinterface of NEW_Type Compiler ERROR NEW_Type must be Object. NEW_Type must be Cloneable or Serializable OLD_Type must be an array of some object reference type that can be converted to what- ever NEW-Type is an array of
versions are usually per eritance hierarchy. mited when NEW_Type is a “up” in the
# Example #1, Subclass / Class implicit casting:
class Animal {} class Dog extends A nimal {}
Dog a = new Dog(); Animal b = a; // Ok. Animal is the superclass of Dog .
Animal x = new Anim Dog y = x; al(); // Error. Dog is a subclass of Animal.
Animal l = new Anim Object m = l; al(); // Ok. Object is above Animal in the C // hierarchy. lass
lass may be converted to verting to a class type, e. a class type or to an interface type. If the new type must be a superclass of the old
# Example #2, Class / I nterface implicit casting:
interface Eatable { }
class Animal {} class Dog extends A nimal implements Eatable {}
Eatable e; Animal a; Object o;
e = new Dog(); e = new Animal(); // Ok. Dog implements Eatable // Error. Animal doesn’t implements Eatable
a = e; // Error o = e; // Ok. A . An interface may not be converted to a Class n interface may be converted to an Object.
lass may be converted to verting to a interface t erface. a class type or an interface type. If ype, the old class must implement the
interface type may only ect. If the new type is old type. be converted to an interface type or to an interface, it must be a superinterface of
# Example #3, Arrays:
class Animal {} class Dog extends A nimal {}
Animal canines[] = for (int i = 0 ; i canines[i] = ne } new Animal[25]; < canines.length ; i++) { w Animal();
Object some_canines Animal dangerous_ca Dog friendly_canine [] = new Object[25]; nines[] = new Animal[25]; s[] = new Dog[25];
some_canines = cani dangerous_canines = some_canines[5] = c friendly_canines = friendly_canines[5] nes; canines; anines [5]; canines; // Error. = canines[5]; // Error. Incompatible types
array me be converted to Serializable, or to an a converted to an array, a the new element type. the class Object, to the interface Clonable rray. Only an array of object references may nd the old element type must be convertible
Method-call
rules for method-call c ignment conversion. onversion are the same as the rules for
)
ect 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:

    class Animal {}
    class Dog extends Animal {}
    class Cat extends Animal {}

    Animal a = new Animal();
    Dog d = new Dog();
    Cat c = new Cat();

    d = (Dog)a;    // Ok. Compiles but produces a runtime Exception.
    d = (Dog)c;    // Error. Won't compile.
    a = (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:

    class Animal implements Eatable {}
    class Dog extends Animal {}
    interface Eatable {}

    Eatable e;
    Animal a; 
    Dog d = new Dog();

    e = d;       // Ok. Eatable is implemented by Animal and hence by Dog. 
    d = e;       // Error. An interface cannot be converted to a class.
    d = (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:

    class Animal {}
    class Dog extends Animal {}

    Dog terriers[] = new Dog[25];
    Animal canines[] = new Animal[25];

    canines = terriers;        // Ok. Animal is more general than Dog
    terriers = (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

    class Test {

        public static void main(String args[]) {
            
            MyThread t = new MyThread();
            t.start();
            for (int i = 0 ; i < 10 ; i++) {
                Test.Wait(1);
                System.out.print(".");
            }

        }

        static void Wait(int s) {
            s *= 1000;
            long time = System.currentTimeMillis(); 
            while (System.currentTimeMillis()-time < s) { } 
        }

    }

    class MyThread extends Thread {
        public void run() {
            Test.Wait(5);   
            System.out.println(" A threat is bothering!");
            Test.Wait(2);
            System.out.println(" Here I am again!");
        }
    }

    Result:

        .... A threat is bothering!
        .. Here I am again!
        ....

Implementing Runnable

    class Test {

        public static void main(String args[]) {
            
            MyObject or = new MyObject();
            Thread t = new Thread(or);
            t.start();
            for (int i = 0 ; i < 10 ; i++) {
                Test.Wait(1);
                System.out.print(".");
            }
        }

        static void Wait(int s) {
            s *= 1000;
            long time = System.currentTimeMillis(); 
            while (System.currentTimeMillis()-time < s) { } 
        }
    }

    class MyObject implements Runnable {
        public void run() {
            Test.Wait(5);   
            System.out.println(" A threat is bothering!");
            Test.Wait(2);
            System.out.println(" Here I am again!");
        }
    }

    Result:

        .... A threat is bothering!
        .. Here I am again!
        ....

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

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:

    class Test {

        public static void main(String args[]) {

            Thread t = new MyThread();
            t.start();
            
            for (int i = 0 ; i < 50 ; i++) {
                try {
                    Thread.sleep(50);
                }
                catch (InterrupedException e) { }
                System.out.print(".");
            }
        }
    }

    class MyThread extends Thread {
        public void run() {
            try {
                sleep(1000);
            }
            catch (InterruptedException e) { }
            System.out.println(" I ran!");
        }
    }

    Result:


        ................... I ran!
        ...............................

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

    public static void sleep(long milliseconds) 
    throws InterruptedException

    public 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.

    class Test {

        public static void main(String args[]) {

            Thread t = new MyThread();

            t.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {}
            t.interrupt();
        }
    }

    class MyThread extends Thread {
        public void run() {
            System.out.println("I've started.");
            try {
                System.out.println("Now, I'll sleep");
                sleep(8000);
                System.out.println("I'll wake up now");
            }
            catch (InterruptedException e) { 
                System.out.println("You woke me up!");
            }
            System.out.println("All done!");
        }
    }

    Result:

        I've started.
        Now, I'll sleep
        You woke me up!
        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:

    class Test {

        static boolean done = false;

        public static void main(String args[]) {
            Thread t = new MyThread();
            t.start();
            System.out.print("Type your name: ");
            try {
                Thread.sleep(5000);
                while (!done) {
                    System.out.print("\nWhat are you waiting");
                    System.out.print(" for?\nChristmas?");
                    System.out.print("\nType your name: ");
                    Thread.sleep(5000);
                }
            } catch (InterruptedException e) {}
            System.out.println("All Done!");
        }
    }

    class MyThread extends Thread {
        public void run() {

            try {
                int c = System.in.read();
            }
            catch (IOException e) { 
                System.out.println("I/O Trouble!");
            }
            System.out.println("Ok, wait up...");
            Test.done = true;
        }
    }

    Result: (I try to write my name but I seem to be too slow)

        C:\temp>java Test
        Type your name: Er
        What are you waiting for?
        Christmas?
        Type your name: Erne
        What are you waiting for?
        Christmas?
        Type your name: Ernest
        Ok, wait up...
        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:

        Thread t = new MyThread();
        t.setPriority(9);
        t.setPriority(Thread.MAX_PRIORITY);
        t.setPriority(Thread.MIN_PRIORITY);
        t.setPriority(Thread.NORM_PRIORITY);
        int 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:

    for (int i = 0 ; i < 640 ; i++) {
        for (int j = 0 ; j < 480 ; j++) {
            // CalculatePixel(i,j);
            yield();
        }
    }

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:

    class CarDealer {

        int available_cars = 1;

        synchronized boolean buyCar(int mIncome, String car) {
            boolean sell = false;
            if (available_cars > 0) {
                available_cars--;

                if (mIncome > 1500) sell = true;
                // Consult banks, validate credit and so on...
                // ...
                if (car.equals("jaguar") && mIncome < 5000) 
                    sell = false;
            }
            if (!sell) available_cars++;
            return sell;
        }
    }

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:

    car_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:

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

    class Test {

        static boolean done = false;

        public static void main(String args[]) {

            CarDealer cd = new CarDealer();

            OtherCustomer oc = new OtherCustomer(cd);
            Thread t = new Thread(oc);
            t.start();

            try {
                // Thinking, should I sell or not?
                Thread.sleep(1*1000);
            } catch (InterruptedException e) { }

            System.out.print("I am looking ");
            System.out.print("for a car...\n");

            if (cd.buyCar(6000,"ford")) {
                System.out.println("Me: I've got a new car");
            } else {
                System.out.println("Me: I'll keep walking");
            }
        }


    }

    class OtherCustomer implements Runnable {
        CarDealer cd;

        OtherCustomer(CarDealer cd) {
            this.cd = cd;
        }

        public void run () {

            System.out.print("Other customer is looking ");
            System.out.print("for a car...\n");
            if (cd.buyCar(500,"jaguar")) {
                System.out.println("OC: I've got a new car");
            } else {
                System.out.println("OC: I'll keep walking");
            }
        }
    }

    class CarDealer {

        int available_cars = 1;

        synchronized boolean buyCar(int mIncome, String car) {
            boolean sell = false;
            if (available_cars > 0) {
                available_cars--;

                if (mIncome > 1500) sell = true;
                if (car.equals("jaguar") && mIncome < 5000) 
                    sell = false;
                try {
                    // Thinking, should I sell or not?
                    Thread.sleep(5*1000);
                } catch (InterruptedException e) { }
            }
            if (!sell) available_cars++;
            return sell;
        }

    }

    Result as is:

        Other customer is looking for a car...
        I am looking for a car...
        OC: I'll keep walking
        Me: I've got a new car

    Result without the synchronized keyword:

        Other customer is looking for a car...
        I am looking for a car...
        Me: I'll keep walking
        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:

    class CarDealer {

        int available_cars = 1;

        synchronized boolean buyCar(int mIncome, String car) {
            boolean sell = false;
            if (available_cars > 0) {
                available_cars--;

                if (mIncome > 1500) sell = true;
                if (car.equals("jaguar") && mIncome < 5000) 
                    sell = false;
                try {
                    wait(5*1000);   
                } catch (InterruptedException e) {}
            }
            if (!sell) available_cars++;
            return sell;
        }

    }

    Result:

        Other customer is looking for a car...
        I am looking for a car...
        Me: I'll keep walking
        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:

    class Test {
        public static void main(String args[]) {
            
            BeerDealer bd = new BeerDealer();
            Timer t = new Timer();      
            t.schedule(new TTask(bd), 2000);    

            if (bd.buyBeer()) {
                System.out.println("I'm allowed to drink");
            } else {
                System.out.println("I'm not allowed to drink");
            }
            System.out.println("Execution ended");
            System.exit(0);
        }
    }

    class TTask extends TimerTask {
        BeerDealer bd;
        TTask(BeerDealer bd) {
            this.bd = bd;
        }
        public void run() {
            bd.setAge(18);
        }
    }

    class BeerDealer {
        public static int age = 17;

        synchronized boolean buyBeer() {
            while (age < 18) {
                try {
                    wait(); 
                } catch (InterruptedException e) {}
            }
            return true;
        }

        synchronized void setAge(int age) {
            this.age = age;
            notify();
        }
    }

    Result:

        I'm allowed to drink
        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:

    class Test {

        static boolean done = false;

        public static void main(String args[]) {

            Mailbox answerMachine = new Mailbox(); 
            ThreadOBJ to = new ThreadOBJ(answerMachine);
            Thread t = new Thread(to);
            
            t.start();

            System.out.println("Welcome!");
            System.out.println("What kind of message do you");
            System.out.println("want to leave?\n");
            System.out.println("1 - Hi, call me back please!");
            System.out.println("2 - Please return my calls!");
            System.out.println("3 - Exit\n");
            System.out.print("Select (1-3) and press Enter: ");

            int c;

            try {
            exit:   while ((c = System.in.read()) != -1) {
                    switch(Character.getNumericValue((char)c)) {
                        case 1:
                            answerMachine.storeMessage
                            ("Hi, call me back please!");   
                            break;
                        case 2:
                            answerMachine.storeMessage
                            ("Please return my calls!");
                            break;
                        case 3:
                            break exit;
                    }

                }
            } catch (IOException e) {}

            answerMachine.hungup();
            System.out.println("Good bye!");

        }
    }

    class ThreadOBJ implements Runnable {
        Mailbox mb;

        public ThreadOBJ (Mailbox mb) {
            this.mb = mb;
        }
        
        public void run() {
            String s;

            while (!((s = mb.retriveMessage()).equals("<>"))){
                System.out.println(s);
            }
            System.out.println("Thread canceled");
        }
    }

    class Mailbox {
        private boolean request;
        private String message;

        public synchronized void storeMessage(String m) {
            while (request) {
                try {
                    wait();
                } catch (InterruptedException e) { }
            System.out.println("Waiting input");
            }
            request = true;
            message = m;
            notify();
        }

        public synchronized String retriveMessage() {
            while (!request) {
                try {
                    wait();
                } catch (InterruptedException e) { }
            }
            request = false;
            notify();
            return message;
        }

        public void hungup () {
            request = false;
            storeMessage("<>");
        }
    }

    Result:

        C:\temp>java Test
        Welcome!
        What kind of message do you
        want to leave?

        1 - Hi, call me back please!
        2 - Please return my calls!
        3 - Exit

        Select (1-3) and press Enter: 1
        Hi, call me back please!
        2
        Please return my calls!
        3
        Good bye!
        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 alogirthm.

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:

Math constants

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:

        abs(-5) = 5
        abs(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.

        ceil(4.00001)  = 5.0
        ceil(4.99999)  = 5.0

        ceil(-4.00001) = -4.0
        ceil(-4.99999) = -4.0

double floor(double d)

    Returns the largest integer that is not greater than d.

        floor(4.00001)  = 4.0
        floor(4.99999)  = 4.0

        floor(-4.00001) = -5.0
        floor(-4.99999) = -5.0

    Other methods: double floor(double d) 

int max(int i1, int i2)

    Returns the greater of i1 and i2

    max(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

        min(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

        random() = 0.012288873641244202
        random() = 0.8962267714328847
        ...

int round(float f)

    Returns the closest int to f.

        round(4.00001)  = 4
        round(4.49999)  = 4
        round(4.5)      = 5
        round(4.99999)  = 5

        round(-4.5)     = -4
        round(-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 appropiate constructor.

Examples:

    int a = 567;
    Integer i = new Integer(a);

    Character 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:

    Boolean wboolean = new Boolean("true");
    try {
        Byte wbyte = new Byte("124");
        Short wshort = new Short("25943");
        Integer wint = new Integer("532342");
        Long wlong = new Long("78888900000");
        Float wfloat = new Float("5.4f");
        Double wdouble = new Double("1.1000004");   

    } 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:

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

Summary:

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:

    String s1 = new String("Hello World");
    String s2 = new String("Hello World");
    String s3 = "Hello World";
    String 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

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

String concat(String addThis)

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

        "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. 

        "Ernest".compareTo("Ricardo") = -13
        "Ernest".compareTo("Ernest") = 0
        "Ernest".compareTo("Felix") = -1

boolean endsWith(String suffix)

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

        "Ernest".endsWith("est") = true
        "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. 

        "Ernest".equals(new String("Ernest")) = true
        "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. 

        "Ernest".equalsIgnoreCase("Ernest") = true
        "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. 

        "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.

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

int length()

    This returns the number of characters in the current string.

        "Ernest".length() = 6

replace(char oldChar, char newChar)

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

        "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.

        "Ernest".startsWith("Ern") = true
        "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.

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

String toLowerCase()

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

        "Ernest".toLowerCase() = "ernest"

String toUpperCase()

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

        "Ernest".toUpperCase() = "ERNEST"

String toString()

    This returns the executing object.

        "Ernest".toString() = "Ernest"

String trim()

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

        "   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.

        new StringBuffer("Ernesto ").append("Garbarino") = "Ernesto Garbarino"

        char[] word = {'h','e','l','l','o'};
        new 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.

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

StringBuffer reverse()

    This reverses the characters of the current string buffer.

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

void setCharAt(int offset, char newChar)

    This replaces the character at position offset with newChar

        StringBuffer sb = new StringBuffer("Ernest");
        sb.setCharAt(1,'l');

        sb = "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.

        StringBuffer sb = new StringBuffer("Ernest");
        sb.setLength(3);

        sb = "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:

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

Some concepts:

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:

Main Classes

Classes to manipulate Unicode characters:

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:

    File(String pathname);
    File(String dir, String subpath);
    File(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

        class Test {
            public static void main(String args[]) {
                File f = new File("Test.java");
                System.out.println(f.getAbsolutePath());
            }
        }

        Result:

            c:tempTest.java

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

        class Test {
            public static void main(String args[]) throws IOException {
                File f = new File("..tempTest.java");
                System.out.println(f.getCanonicalPath());
            }
        }

        Result:

            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

        File f = new File(System.getProperty("user.dir"));
        System.out.println(f.getName());

        Result:

            temp

String getParent(): returns the name of the directory that contains the file

        File f = new File(System.getProperty("user.dir"),"Test.java");
        System.out.println(f.getParent());

        Result:

            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

    class Dir {
        public static void main(String args[]) throws IOException {
            File dir = new File(System.getProperty("user.dir"));
            System.out.println("Contents of: "+dir.getAbsolutePath());
            String files[];
            files = dir.list();
            for (int i = 0 ; i < files.length ; i++) {
                File current = new File(files[i]);
                if (current.isDirectory()) {
                    System.out.print("  ");
                } else {
                    System.out.print("       ");
                }
                if (current.canRead()) {
                    System.out.print("r");
                } else {
                    System.out.print("-");
                }
                if (current.canWrite()) {
                    System.out.print("w");
                } else {
                    System.out.print("-");
                }
                System.out.println(" " + current);
            }
        }
    }

    Result:

        Contents of c:temp

               r- autorun.inf 
               rw Dir.java
               rw Dir.class
          rw InstallShield
               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:

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

Seeking methods:

More common methods that support byte reading and writing:

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

    void close() throws IOException

Low-level Streams

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

FileInputStream

Main constructors:

Mose useful methods:

FileOutputStream

Main consturctors:

Most useful methods:

Example:

    class Test {
        public static void main(String args[]) throws IOException {
            FileInputStream srcFile = new FileInputStream("tale.txt");
            FileOutputStream dstFile = new FileOutputStream("tale.bak");            
            int b;
            while ((b = srcFile.read()) != -1) {
                dstFile.write(b);   
            }
            dstFile.close();
            srcFile.close();
        }
    }
    Result:

        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:

    DataInputStream(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:

DataOutputStream

Constructor:

    DataOutputStream(OutputStream oststream)

Most useful methods:

Example:

    class Test {
        public static void main(String args[]) throws IOException {
            String name = "Ernest";
            int age = 24;
            float weight = 94.5f;
            char gate = 'A';
    
            FileOutputStream fos = new FileOutputStream("record.dat");
            DataOutputStream dst = new DataOutputStream(fos);
            dst.writeUTF(name);
            dst.writeInt(age);
            dst.writeFloat(weight);
            dst.writeChar(gate);
            dst.close();
            fos.close();
            
            FileInputStream fis = new FileInputStream("record.dat");
            DataInputStream src = new DataInputStream(fis);
            System.out.println("  Name: " + src.readUTF());
            System.out.println("   Age: " + src.readInt());
            System.out.println("Weight: " + src.readFloat());
            System.out.println("  Gate: " + src.readChar());
            src.close();
            fis.close();
        }
    }
    
    Result:
    
          Name: Ernest
           Age: 24
        Weight: 94.5
          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:

FileWriter

Constructors:

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

Reader, most useful methods:

Writer, most useful methods:

Other high-level classes:

Example:

    class Test {
        public static void main(String args[]) throws IOException {
            FileInputStream fis = new FileInputStream("tale.txt");
            InputStreamReader src = new InputStreamReader(fis, "ISO-8859-1");
            FileOutputStream fos = new FileOutputStream("tale.utf");
            OutputStreamWriter dst = new OutputStreamWriter(fos, "UTF-16");
            int c;
            while ((c = src.read()) != -1) {
                dst.write(c);
            }
            dst.close();
            fos.close();
            src.close();
            fis.close();
        }
    }

    Result:

        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:

    class Test {
        public static void main(String args[]) throws IOException, ClassNotFoundException {
            int fields = 3;
            String database = "students.db";

            Record student[] = new Record[3];
            student[0] = new Record("Ernesto",24);
            student[1] = new Record("John",14);
            student[2] = new Record("Mike",29);
            FileOutputStream fos = new FileOutputStream(database);
            ObjectOutputStream dst = new ObjectOutputStream(fos);
            dst.writeInt(fields);
            for (int i = 0 ; i < student.length ; i++) {
                dst.writeObject(student[i]);            
                student[i] = null;
            }
            dst.close();
            fos.close();

            Record current_student;

            FileInputStream fis = new FileInputStream(database);
            ObjectInputStream src = new ObjectInputStream(fis);
            int db_fields = src.readInt();
            for (int i = 0 ; i < db_fields ; i++) {
                current_student = (Record)src.readObject();
                System.out.println(current_student);
            }
            src.close();
            fis.close();
        }
    }

    class Record implements Serializable {
        private String name;    
        private int age;
        Record(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public String toString() {
            return name + " is " + age + " years old."; 
        }
    }

    Result:
    
        Ernesto is 24 years old.
        John is 14 years old.
        Mike is 29 years old.   

AWT - Layout Managers

Default layout managers:

The Flow Layout Manager

The flow layout manager arranges components in horizontal rows.

Example:

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

Most useful constructors

Align is usually one of these constants:

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:

    GridLayout(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:

    Frame f = new Frame();
    f.setSize(400,200);
    f.setLayout(new GridLayout(3,2));
    for (int i = 0 ; i < 6 ; i++) {
        f.add(new Button("Button #"+ i));
    }
    f.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:

    BorderLayout()

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

    add(Component comp, int index)

Where index may be one of these constants:

Example:

    Frame f = new Frame();
    f.setSize(400,200);
    f.setLayout(new BorderLayout());
    Button b1 = new Button("North");
    Button b2 = new Button("South");
    Button b3 = new Button("West");
    Button b4 = new Button("East");
    Button b5 = new Button("CENTER");

    f.add(b1, BorderLayout.NORTH);
    f.add(b2, BorderLayout.SOUTH);
    f.add(b3, BorderLayout.WEST);
    f.add(b4, BorderLayout.EAST);
    f.add(b5, BorderLayout.CENTER);

    f.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:

    Panel p = new Panel(); 
    cl = new CardLayout();
    p.setLayout(cl);
    Button b = new Button("A Component");
    p.add(b, "Button-A");
    cl.show(cl, "Button-A");

Methods:

Example:

    Frame f = new Frame();
    f.setSize(500,300);
    CardLayout cl = new CardLayout();
    f.setLayout(cl);
    for (int i = 0 ; i < 4 ; i++) {
        f.add(new Button("Button #"+i), "Button-"+i);
    }
    f.add(new Button("String button"),"mybutton");

    cl.show(f, "mybutton");
    f.setVisible(true);

    for (int w = 0 ; w < 4 ; w++) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {}
        cl.next(f);
    }

    Result:

        1. A big button called "String button" is shown
        2. 1s pause
        3. A big button called button#0 is shown
        4. 1s pause
        5. A big button called button#1 is shown
        6. 1s pause
        7. A big button called button#2 is shown
        8. 1s pause
        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:

    Frame f = new Frame();
    f.setSize(400,200);
    GridBagConstraints 
    gbc = new GridBagConstraints();    // Holds layout position info
    f.setLayout(new GridBagLayout());
    gbc.gridx = 0;                     // You specify which cell you
    gbc.gridy = 0;                     // want to place the component in.
    f.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.

    gbc.gridx = 3;
    gbc.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.

    gbc.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:

    gbc.weightx = 20;
    gbc.gridx++;
    gbc.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:

Example:

    gbc.anchor = GridBagConstraints.SOUTHEAST;
NW

N

NE


W


C


E


SW

S

(SE)

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:

Example:

    gbc.fill = GridBagConstraints.HORIZONTAL;

HORIZONTAL


NONE

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.

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




X


X








X


X








X


X




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

Short-hands:

Complete example:

    Frame f = new Frame();
    f.setSize(250,80);
    GridBagConstraints gbc = new GridBagConstraints();
    GridBagLayout gbl = new GridBagLayout();
    f.setLayout(gbl);
    gbc.gridx = 0;
    gbc.gridy = 0;
    gbc.weightx = 80;
    gbc.fill = GridBagConstraints.HORIZONTAL;
    f.add(new Button("Top left"), gbc);
    gbc.fill = GridBagConstraints.BOTH;
    gbc.gridheight = 2;
    gbc.weightx = 20;
    gbc.gridx++;
    f.add(new Scrollbar(Scrollbar.VERTICAL), gbc);
    gbc.fill = GridBagConstraints.NONE;
    gbc.gridy = 1;
    gbc.gridx = 0;
    gbc.anchor = GridBagConstraints.WEST;
    f.add(new Button("Bottom Left"), gbc);
    f.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.

    Frame f = new Frame();
    f.setSize(400,200);
    System.out.println(f.getSize().width);
    System.out.println(f.getSize().height);
    
    Result:

        400
        200

setForeground() and setBackground()

These methods receive a Color object as a parameter.

    Frame f = new Frame();
    f.setSize(400,200);
    f.setLayout(new FlowLayout());
    f.setBackground(new Color(255,255,0));
    f.setForeground(new Color(0,255,0));
    f.add(new Label("Hello World"));
    f.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.

    Frame f = new Frame();
    f.setSize(400,200);
    f.setLayout(new FlowLayout());
    f.setFont(new Font("Serif",Font.BOLD,66));
    f.add(new Label("Hello World"));
    f.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.

    Frame f = new Frame();
    f.setSize(400,200);
    f.setLayout(new FlowLayout());
    Button b = new Button("I'm disabled");
    b.setEnabled(false);
    f.add(b);
    f.setVisible(true);

setSize() and setBounds()

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

There are also other versions for these methods

    Frame f = new Frame();
    f.setBounds(0,0,400,200);
    f.setVisible(true);

    Result:

    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:

    Button 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:

    Canvas c = new Canvas();

Checkbox

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

Common construction:

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

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

Radio buttons

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

Example:

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

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

Choice

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

Example:

    Choice c = new Choice();
    c.addItem("China");
    c.addItem("Korea");
    c.addItem("Japan");
    add(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:

    FileDialog(Frame parent, String title, int mode)

Example:

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

Modes:

Partial method list:

Label

Labels are used to display simple but dynamic text information

Common construction:

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

Alignments:

(int constants)

Partial method list:

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:

Example:

    List l = new List(10, true);
    l.add("English");
    l.add("Spanish");
    l.add("Italian");
    add(l);

Partial method list:

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:

Constants:

Example:

    Frame f = new Frame();
    f.setSize(220,140);
    f.setLayout(new FlowLayout(FlowLayout.LEFT));
    ScrollPane sp = new ScrollPane(ScrollPane.SCROLLBARS_ALWAYS);
    Button b = new Button("BIG WORDS");
    b.setFont(new Font("Serif", Font.BOLD, 90));
    sp.setSize(200,100);
    sp.add(b);
    f.add(sp);          
    f.setVisible(true);

Scrollbar

Scrollbars are usually called “sliders” on other environments.

Constructors:

Description:

Orientation constants:

Partial method list:

Example:

    Frame f = new Frame();
    f.setSize(200,40);
    Scrollbar sc = new Scrollbar
    (Scrollbar.HORIZONTAL, // Type
    1,    // Initial Value
    25,   // Size (1/4 of the total sliding size)
    1,    // Min. Value
    100); // Max. Value
    f.add(sc);          
    f.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:

TextArea constructors:

Partial method list:

Example:

    Frame f = new Frame();
    f.setSize(500,300);
    TextArea ta = new TextArea("Ernesto",6,3);
    f.add(ta);
    f.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:

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

Useful partial method list:

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:

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

Common construction:

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

Example:

    Frame f = new Frame("Ernesto");
    f.setSize(500,300);
    Dialog d = new Dialog(f, "Smash your hard disk?", true);
    d.setSize(200,100);
    d.setLayout(new FlowLayout());
    d.add(new Button("Ok"));
    d.add(new Button("Cancel"));
    f.setVisible(true);
    d.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:

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

Example:

    Frame f = new Frame("Ernesto");
    f.setSize(500,300);

    MenuBar mb = new MenuBar();
    f.setMenuBar(mb);

    Menu filemenu = new Menu("File");
    filemenu.add(new MenuItem("New"));
    filemenu.add(new MenuItem("Open"));
    filemenu.addSeparator();
    filemenu.add(new MenuItem("Save"));

    Menu filemenu_sa = new Menu("Save As");
    filemenu_sa.add(new MenuItem(".doc"));
    filemenu_sa.add(new MenuItem(".txt"));
    filemenu.add(filemenu_sa);

    filemenu.addSeparator();
    filemenu.add(new MenuItem("Close"));
    mb.add(filemenu);

    Menu editmenu = new Menu("Edit");
    editmenu.add(new MenuItem("Cut"));
    editmenu.add(new MenuItem("Copy"));
    editmenu.add(new MenuItem("Paste"));
    editmenu.addSeparator();
    editmenu.add(new CheckboxMenuItem("Insert Mode Enabled"));
    mb.add(editmenu);

    Menu helpmenu = new Menu("Help");
    helpmenu.add(new MenuItem("Help Topics"));
    helpmenu.add(new MenuItem("About..."));
    mb.setHelpMenu(helpmenu);

    f.setVisible(true);

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

Example:

    import java.awt.*;
    import java.awt.event.*;

    class Test {
        public static void main(String args[]) {
            
            Frame f = new Frame("Ernesto");
            f.setSize(500,300);
            f.setLayout(new FlowLayout());
            PopupMenu editmenu = new PopupMenu();
            Button b = new Button("Click for options");

            b.addActionListener(new MyAL(b,editmenu));
            f.add(b);

            editmenu.add(new MenuItem("Cut"));
            editmenu.add(new MenuItem("Copy"));
            editmenu.add(new MenuItem("Paste"));
            f.add(editmenu);

            f.setVisible(true);
        }
    }


    class MyAL implements ActionListener {
        Button b;
        PopupMenu m;

        MyAL (Button b, PopupMenu m) {
            this.b = b;
            this.m = m;
        }
        public void actionPerformed(ActionEvent ae) {
            m.show(b,20,20);
        }
    }

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:

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:

Selecting a Color

You can choose one of these color constants:

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

    Color(int Red, int Green, int Blue)

Selecting a Font

The constructor for the Font class look like this:

    Font(String fontname, int style, int size)

fontname: there are only three plataform independent fonts:

style: some of these constants are commonly used:

These ones may be combined:

    new 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)

    public void drawLine(int x0, inty0, int x1, int y1);

Example:

    g.drawLine(10, 30, 100, 100);

drawRect() and fillRect()

The drawRect() and fillRect() methods respectively, draw and fill rectangles.

    public void drawRect(int x, int y, int width, int height);
    public 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:

    g.fillRect(10, 10, 100, 50);
    g.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:

    public void drawOval(int , int y, int width, int height);
    public void fillOval(int , int y, int width, int height);

Example:

    g.fillOval(10, 10, 100, 50);
    g.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:

    public void drawArc(int x, int y, int w, int h, int sDeg, int aDeg)
    public void drawArc(int x, int y, int w, int h, int sDeg, int aDeg)

                90º
                 *
                 *
                 *
        180º ********* 0º
                 *
                 *
                 *
                279º

Example:

    g.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:

    public void drawPolygon(int xs[], int ys[], int Points);
    public void fillPolygon(int xs[], int ys[], int Points);

Example:

    int xs[] = {10,110, 110,  10, 60};
    int ys[] = {10, 10, 110, 110, 60};
    g.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.

    public void drawPolyline(int xs[], int ys[], int Points)

Example:

    int xs[] = {10,110, 110,  10, 60};
    int ys[] = {10, 10, 110, 110, 60};
    g.drawPolyline(xs, ys, 5);

drawString()

The drawString() method paints a string of text. The signature is:

    public 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:

    g.drawString("Hello World", 10, 30);

drawImage()

An Image is an off-screen representation of a rectangular collection of pixel values:

    boolean 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:

    setClip (x, y, width, height)

Example:

    g.setClip(20,20,100,100);
    for (int i = 10 ; i < 300 ; i += 20) {
        for (int j = 10 ; j < 300 ; j += 20) {
            g.fillOval(i, j, 15, 15);
        }
    }

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:

    class MyCanvas extends Canvas {
        public void paint(Graphics g) { }
    } 

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:

    public void update(Graphics g) {
        g.clearRect(0,0,width,height);
        paint(g);
    }

If you don’t want the screen to be cleared you can override this method:

    paint void update(Graphics g) {
        paint(g);
    }

Animation / Double Buffering example

    import java.awt.event.*;
    import java.awt.*;

    class Test {

        public static void main(String args[]) {
            Frame f = new Frame();
            f.setSize(200,200);
            MyCanvas c = new MyCanvas();
            f.add(c);
            f.setVisible(true);

            for (int i = 0 ; i < 360*4 ; i += 2) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {}
                c.setArc(i);
                c.repaint();
            }
            System.exit(0);
        }
    }

    class MyCanvas extends Canvas {
        Image im;
        Graphics ig;
        int arc = 0;

        public void addNotify() {
            super.addNotify();
            im = createImage(640,480);
            ig = im.getGraphics();
        }

        public void update(Graphics g) {
            paint(g);
        }

        public void setArc(int arc) {
            this.arc = arc;
        }

        public synchronized void paint(Graphics g) {
            ig.setColor(Color.white);
            ig.fillRect(0,0,this.getWidth(),this.getHeight());
            ig.setColor(Color.black);

            ig.setColor(Color.yellow);
            ig.fillOval(0,0,this.getWidth(),this.getHeight());
            ig.setColor(Color.black);
            ig.fillArc(0,0,this.getWidth(),this.getHeight(),arc,10);

            g.drawImage(im,0,0,this);

        } 
    }



    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:

Event Class Hierarchy

    java.util.EventObject
      |
      |
    java.awt.AWTEvent --|
      |                 |- ActionEvent 
      |                 |- AdjustmentEvent
      |                 |- ItemEvent
      |                 |- TextEvent
      |
    ComponentEvent --|
      |              |- ContainerEvent
      |              |- FocusEvent
      |              |- PaintEvent
      |              |- WindowEvent
      |
    InputEvent --|
                 |- KeyEvent
                 |- MouseEvent

Event details:

Listener Interfaces and their methods

Interface Interface Methods Add Method
ActionListener actionPerformed(ActionEvent) addActionListener()
AdjustmentListener adjustmentValueChanged (AdjustmentEvent) addAdjustmentListener()
ComponentListener componentHidden(ComponentEvent)
componentMoved(ComponentEvent)
componentResized(ComponentEvent)
componentShown(ComponentEvent)
addComponentListener()
ContainerListener componentAdded(ContainerEvent)
componentRemoved(ContainerEvent)
addContainerListener()
FocusListener focusGained(FocusEvent)
focusLost(focusEvent)
addFocusListener()
ItemListener itemStateChanged(ItemEvent) addItemListener()
KeyListener keyPressed(keyEvent)
keyReleased(keyEvent)
keyTyped(keyEvent)
addKeyListener()
MouseListener mouseClicked(MouseEvent)
mouseEntered(MouseEvent)
mouseExited(MouseEvent)
mousePressed(MouseEvent)
mouseReleased(MouseEvent)
addMouseListener()
MouseMotionListener mouseDragged(MouseEvent)
mouseMoved(MouseEvent)
addMouseMotionListener()
TextListener textValueChanged(TextEvent) addTextListener()
WindowListener windowActivated(WindowEvent)
windowClosed(WindowEvent)
windowClosing(WindowEvent)
windowDeactivated(WindowEvent)
windowDeiconified(WindowEvent)
windowIconified(WindowEvent)
windowOpened(WindowEvent)
addWindowListener()

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:

    import java.awt.event.ActionListener;
    import java.awt.event.ActionEvent;
    import java.awt.*;

    class Test {

        public static void main(String args[]) {
            Frame f = new Frame();
            f.setSize(400,200);
            f.setLayout(new FlowLayout());
            Button b = new Button("Please click me");
            b.addActionListener( new AL() );
            f.add(b);
            f.setVisible(true);
        }

    }

    class AL implements ActionListener {
        public void actionPerformed(ActionEvent ae) {
            System.out.println("Action Performed: "+ae);
        }
    }

    Result:

    When the button clicked, the following message is shown:

        Action Performed: java.awt.event.ActionEvent[ACTION_PERFORMED,
        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:

Example:

    import java.awt.event.ActionListener;
    import java.awt.event.ActionEvent;
    import java.awt.*;

    class Test {

        public static void main(String args[]) {
            Frame f = new Frame();
            f.setSize(400,200);
            f.setLayout(new FlowLayout());
            MyButton b = new MyButton("Please click me");
            f.add(b);
            f.setVisible(true);
        }

    }

    class MyButton extends Button {
        public MyButton(String label) {
            super(label);
            enableEvents(AWTEvent.ACTION_EVENT_MASK);   
        }

        public void processActionEvent(ActionEvent ae) {
            System.out.println("Action Performed: "+ae);
            super.processActionEvent(ae);
        }
    }

    Result:

    When the button clicked, the following message is shown:

        Action Performed: java.awt.event.ActionEvent[ACTION_PERFORMED,
        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:

    class MyButton extends Button implements ActionListener {
        public MyButton(String label) {
            super(label);
            addActionListener(this);
        }

        public void actionPerformed(ActionEvent ae) {
            System.out.println("Action Performed: "+ae);
        }
    }

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:

    class MyMouse implements MouseListener {
        public void mouseClicked(MouseEvent me) {

        }
    }

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:

    class MyMouse extends MouseAdapter {
        public void mouseClicked(MouseEvent me) {
        }
    }

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:

    Button b = new Button("Ja");
    b.setActionCommand("OK");

You read your action command like this:

    public void actionPerformed(ActionEvent ae) {
        String s = ae.getActionCommand();
    }

A good example, is an international set of buttons:

    class Test {

        public static void main(String args[]) {
            Frame f = new Frame();
            f.setSize(400,200);
            f.setLayout(new FlowLayout());
            Button b_y[] = new Button[4];
            String l_y[] = {"Sim","Ja","Yes","Si"};
            Button b_n[] = new Button[4];
            String l_n[] = {"Nao","Nein","No","No"};
            AL al = new AL();

            for (int i = 0 ; i < b_y.length ; i++) {
                b_y[i] = new Button(l_y[i]);
                b_y[i].setActionCommand("yes");
                b_y[i].addActionListener(al);
                f.add(b_y[i]);

                b_n[i] = new Button(l_n[i]);
                b_n[i].setActionCommand("no");
                b_n[i].addActionListener(al);
                f.add(b_n[i]);
            }
            f.setVisible(true);
        }

    }

    class AL implements ActionListener{
        public void actionPerformed(ActionEvent ae) {
            String s = ae.getActionCommand();
            if (s.equals("yes")) {
                System.out.println("YES!");
            } else {
                System.out.println("NO!");
            }
        }
    }

    Result:

        A set of the following buttons is shown:

        Sim Nao Ja Nein Yes No Si No

        When a button is clicked, YES! or NO! are displayed according
        to the expected translation.

References