Java 2 Guide
A concise guide to the Java 2 Programming Language. Every feature of the language is compulsively tested with examples.
- Language Fundamentals
- Operators
- Control Flow Statements
- Objects and Classes
- Exceptions
- Modifiers
- Converting and Casting
- Threads
- The java.lang and java.util Packages
- The Object Class
- The Math Class
- Methods of the Math Class
- The Wrapper Classes
- Strings
- Most useful String methods
- char charAt(int index)
- String concat(String addThis)
- int compareTo(String otherString)
- boolean endsWith(String suffix)
- boolean equals(Object ob)
- boolean equalsIgnoreCase(String s)
- int indexOf(int ch)
- int lastIndexOf(int ch)
- int length()
- replace(char oldChar, char newChar)
- boolean startsWith(String prefix)
- String substring(int startIndex)
- String toLowerCase()
- String toUpperCase()
- String toString()
- String trim()
- The StringBuffer Class
- StringBuffer constructors
- Most useful StringBuffer methods
- The Collections API
- Collection Implementations in the API
- Input and Output
- AWT - Layout Managers
- AWT - Components
- AWT - Painting
- AWT - Events
- References
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:
- Package declaration
- Import statements
- 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:
- A letter (a-Z)
- A dollar sign ($)
- An underscore (_)
Subsequent characters may be all of these plus digits (0-9). An identifier must never begin with a digit, they are valid only if placed after the three accepted characters.
Identifiers are always case sensitive.
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:
- true
- false
Never try to assign a number, it won’t work.
Char
Java characters are in Unicode, as opposed to other languages in which characters and bytes are basically the same thing. If the most significant nine bits of a char are all 0, Java assumes the encoding to be 7-bits ASCII.
There are several ways to express char literals:
a) By enclosing the chosen character in single quotes:
char a = ‘h’;
b) By specifying the Unicode value using 4 hexadecimal digits, preceded by \u, with the entire expression in single quotes:
char a = ‘\u4566’;
c) By representing a special character using escape sequences:
'\n' = new line '\r' = return '\t' = tab '\b' = backspace '\f' = formfeed '\'' = single quote '\"' = double quote '\\' = blackslah
Integral
Integral literals may be expressed in three ways:
a) In decimal, by writing the number as it is:
byte a = 28;
b) In octal, by prefixing the literal with 0 (zero):
byte a = 034;
c) In hexadecimal, by prefixing the literal with ‘0x’:
byte a = 0x1C;
Hexadecimal digits (abcdef) and the x from ‘0x’ may be upper or lower case.
All integral literals are 32-bits by default. To indicate a long literal (64-bits), you need append the suffix L to the literal expression.
long a = 0x1CL;
Floating Point
A floating-point literal expresses a floating-point number. In order to be interpreted as a floating-point literal, an expression must contain one of the following:
a) A decimal point:
float a = 1.414;
b) The letter E/e, indicating scientific notation:
double a = 4.23E+21;
c) The suffix F/f, indicating a float literal:
float a = 1.828f;
d) The suffix D/d, indicating a double literal:
double a = 1234d;
Java assumes literals without the d/f prefixes to be double.
Strings
A string literal is a sequence of characters enclosed in double quotes.
String s = "Hello World";
- The characters in strings are also 16-bit Unicode.
- You can use all literals associated with the char data type.
Arrays
A Java array is an ordered collection of:
- primitives
- object references
- or other arrays
Arrays are homogeneous, all elements must be of the same type. There is only one exception: When the elements are instances of the array’s class or sub-classes of that class.
To set up an array, three steps must be followed:
- Declaration
- Construction
- Initialisation
Declaration
- It tells what the array’s name will be
- It tells the type of elements of the array
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:
- The first element of an array is 0
- The last one is the array’s size - 1
So, if you want to include the first and last letters of the alphabet, what would you do?
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:
- Arrays are actually objects
- You can often execute methods on them
- The array class itself cannot be sub-classed
You can combine declaration, construction and initialisation by enclosing the desired elements using curly braces:
Examples:
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 main() method is the normal entry point for Java applications.
- An application usually has a class definition that includes a main() method
- Such an application is executed by typing java followed by the name of the class containing the main() method.
The signature for main()
public static void main(String[] args)
public:
The main() method is declared public by conventionstatic:
It is declared static so there’s no need to create an instance of the corresponding class.args:
The args array contains arguments that the user might have entered on the command line. The args variable is used by most Java books but any legal identifier may be employed.
Example:
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:
- It is created when an instance is created.
- It is destroyed when the object is destroyed.
- It is subject to accessibility rules.
- It is subject to the need for a reference to the object.
- It is accessible as long as the enclosing object exists.
An automatic variable of a method (also known as method local):
- It is created on entry to the method.
- It exists only during execution of the method.
- It is therefore only accessible during execution of that method.
Member variables are initialised if no value is provided according to this table:
Element type | Initial value |
---|---|
byte | 0 |
int | 0 |
float | 0.0f |
char | \u0000 |
obj. reference | null |
short | 0 |
long | 0L |
double | 0.0d |
boolean | false |
Example of member variables:
class MyClass {
int a = 50;
int b = 60;
int c;
static z = 3000;
}
- The non-static variables a, b and c are initialised just before the class constructor is executed.
- The variable c is automatically initialised to its default value (0).
- The static variable z is initialised when the class is loaded
Example of automatic variables:
public int take_care () {
int a = 50; // OK
int b = 60; // OK
inc c; // WRONG!
return c;
}
- Local variables are NOT initialised by the system. You need to explicitly initialise them yourself.
- Therefore, c will cause an error.
Argument Passing
By default, when Java passes a primitive argument into a method call, it is actually a copy of the argument that gets passed, not a reference.
Example:
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
- Objects are always allocated on the heap.
- Method local variables and arguments are allocated on the stack.
- In Java, you never explicitly free memory that you have allocated.
- Memory is freed by the Garbage Collector.
- The Garbage Collector works automatically, you CAN’T call it explicitly.
There are a couple of methods that suggest that is possible to run the Garbage Collector explicitly:
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
- a is a double literal
- b is a float literal.
Operators
Table of Operators in Descending Order of Precedence
Category | Operators |
---|---|
Unary | ++ – + - ! ~ () |
Arithmetic | * / % |
+ - | |
Shift | << >> >>>> |
Comparision | < <= > >= instanceof |
== != | |
Bitwise | & ^ | |
Short-circuit | && || |
Conditional | ?: |
Assingment | = “op=” |
Evaluation Order
- Operands are evaluated from left to right.
- For Assignments, associability is from right to left.
Consider this example:
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
- Unary operators take just one single operand.
- They are unlike others where operators take two ( i.e. multiplication).
Java provides 7 unary operators:
Operator | Description |
---|---|
++ | Increment operator |
– | Decrement operator |
+ | Plus operator |
- | Minus operator |
~ | Bitwise inversion operator |
! | Boolean complement operator |
() | Cast operator |
The cast is not an operator strictly speaking. It is however included in this category for simplicity.
The Increment and Decrement Operators: ++ and –
- These ones modify the value of an expression by adding or substracting 1.
- The operands may be placed after or before the expression.
Examples:
++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: ~
- The ~ operator performs bitwise inversion on integral types.
- The ~ works by converting all the 1 bits in a binary value to 0s and all the 0 bits to 1s.
Example:
int a = 0xFFFFFFFF;
a = ~a;
System.out.println(a);
Result: 0
The Boolean Complement Operator: !
- The ! operator inverts the value of a boolean expression.
- !true gives false and !false gives true.
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
- ArithmeticException: Produced by integer division by zero (or modulo %)
- No other arithmetic causes any exception. Instead, the operation proceeds to a result, even though that result might be arithmetically incorrect.
- IEEE 754 infinity, minus infinity and Not a Number (NaN) values: Produced by floating-point calculations where the value is out-of-range.
- Integer calculations that cause overflow or similar errors simply leave the final, typically truncated pattern in the result. The number representation might even be of bad sign.
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 >>>
- Shifting involves taking the binary representation of a number and moving the bit pattern left or right.
- The shift operators may be applied to arguments of integer type only.
- As possible, they should be applied only to operands of either int or long type.
To know how to perform shift operations on integer types, you have to understand how negative and positive values are represented in bits. Although most bit manipulations are performed on ints, we will use bytes to reduce large-number-headache.
Bytes are signed. They can represent the minimum value of -128 and the maximum value of +127. But, a byte is still a byte, that means that it can hold 256 different bit patterns.
Consider this example:
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:
- The minimum negative value of a type is equal to the unsigned value of the absolute value of that negative type.
- All negative numbers have the MSB (Must Significant Bit) turned on.
- -1 is always the maximum unsigned value of the type (-1 equals to turning on all the bits of the type)
Shifting Negative and Positive Numbers
Sadly, Java shifts numbers under a “signed” environment. When negative numbers are shifted, the missing spaces are filled with the MSB bit, that means 1. Of course, this only occurs on right shifting. Let’s take a look at some examples to get a better picture:
Original value = 01000000 (64)
Exp. | Binary | Decimal (signed) | |
---|---|---|---|
None - Original value | 01000000 | 64 | |
Shifted left 1 bit | 64 << 1 | 10000000 | -128 |
Shifted right 1 bit | 64 >> 1 | 00100000 | 32 |
Shifted left 2 bits | 64 << 2 | 00000000 | 0 |
Shifted right 2 bits | 64 >> 2 | 00010000 | 16 |
Original value = 10111111 (-65)
Exp. | Binary | Decimal (signed) | |
---|---|---|---|
None - Original value | 10111111 | -65 | |
Shifted left 1 bit | 65 << 1 | 01111110 | 126 |
Shifted right 1 bit | 65 >> 1 | 11011111 | -33 (MSB carried) |
Shifted left 2 bits | 65 << 2 | 11111100 | -4 |
Shifted right 2 bits | 65 >> 2 | 11101111 | -17 (MSB carried) |
To avoid this (sometimes undesirable) effect, you can use the right >>> operator rather than the plain “>>” one.
Examine now an example using the >>> operator:
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:
- Ordinal comparison: Test the relative value of numeric operands
- Object-type comparison: Determine wether the runtime type of an object is of a particular type or a subclass of that particular type.
- 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
- The instanceof operator tests the class of an object at runtime.
- The left argument can be any object reference expression, usually a variable or an array element.
- The right operand must be a class, an interface or an array.
- The instanceof operator returns true if the class of the left argument is the same as, or is some subclass, of the class specified by the right operand.
Example #1:
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:
- AND: If one operand is false, the result is false, without regard to the other operand.
- OR: If one operand is true, the result is true, without regard to the other operand.
So, for any boolean value x:
- false AND x = false
- true OR x = true
What is this really all about? Consider you have this segment of code:
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
....
}
- The statement is executed immediately before the loop itself is started. It is often used to set up starting conditions. It can also contain variable declarations.
- The condition must be a boolean expression and is treated exactly the same as in the
while()
loop. The body of the loop will be executed repeatedly until the condition ceases to be true. As with thewhile()
loop, it is possible that the body of afor()
loop might never be executed. This occurs if the condition is already false at the start of the loop. - The expression is executed immediately after the body of the loop, just before the test is performed again. Commonly, this is used to increment a loop counter.
Empty for() loops:
Any part of a for()
loop’s control may be omitted. Omitting the test is equivalent to a perpetually true test, so the 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:
- In the class that defines de original method, or a subclass of that class, the method name can be reused if the argument list differs in terms of the type of at least one argument.
- A difference in return type alone is insufficient to constitute an overload and it is illegal.
Example #1:
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:
- The accessibility must not be more restricted than the original method
- The method must not throw checked exceptions of classes that are not possible for the original method.
Example #1:
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:
- In a hierarchy of classes, Java calls the default constructor of each parent class before the body of the current constructor is executed. You cannot prevent Java from calling a constructor for each class but you can tell Java which constructor should be chosen by the parent class using the super() keyword.
- Java provides a default constructor for each class if NO CONSTRUCTOR is defined by the programmer. If just one constructor is defined, Java doesn’t put up a default constructor anymore.
- Constructors are not inherited, you must define all the constructors again or reuse the functionality of a parent constructor using super().
Inner Classes
An inner or nested class is the same as any other class but is declared inside some other class or method. When an instance of an inner class is created, there must normally be a pre-existing instance of the outer class acting as context. An inner class and an outer class belong together, the inner class is not just another member of the outer instance.
Example #1:
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 arising in the finally block itself
- The death of the thread
- The use of System.exit()
- Shutting down the power of the computer where java is running on.
An exception in a finally block can also be handled via a try/catch construct.
Catching Multiple Exceptions
A more specific catch block must precede a more general one in the source. Failure to meet this ordering requirement causes a compiler error. Only one catch block, that is, the first applicable one, will be executed.
Example:
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
- public
- protected
- private
- (default) access level assumed when no modifier is used.
At most 1 access modifier may be used at a time.
Legal overridden method access
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
- abstract
- native
- transient
- synchronized
- volatile
final
Final features may not be changed. A final class may not be subclassed, a final variable may not be modified and a final method can’t be overridden. Thus, final features only apply to:
- methods (except the constructor method)
- variables
- classes
Example #1:
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:
- Classes
- Methods
An abstract class may not be instantiated. An abstract method can’t be invoked, a subclass must override it and provide a non-abstract version of it. The compiler insists that a class must be abstract if:
- The class has one or more abstract methods.
- The class inherits one or more abstract method (from an abstract parent) for which it does not provide implementations.
- The class declares that it implements an interface but does not provide implementations for every method of that interface.
Basic rules for static methods:
- A static method may only access the static data of its class; it may not access non-static data.
- A static method may only call the static methods of its class; it may not call non-static methods.
- A static method has no this
- A static method may not be overridden to be non-static.
Abstract is the opposite of final. A final class, for example, may not be subclassed; an abstract class must be subclassed.
Example #1:
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:
- methods
- variables
- free-floating blocks
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:
- Assignment
- Method call
- Arithmetic promotion
- Explicit casting
Widening conversions
Assignment
General rules for primitive assignment conversions:
- A boolean may not be converted to any other type.
- A non-boolean may be converted to another non-boolean type, provided the conversion is a widening conversion.
- A non-boolean may not be converted to another non-boolean type, if the conversion would be a narrowing conversion.
Example #1:
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:
- All arithmetic expressions are promoted to the wider of the operands.
- The promotion is at least to int, even if no int operand appears.
These rules don’t apply to the unary operators: ++ and – and assignment operators.
Example #1:
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:
- Assignment
- Method call
- Casting
(There is no arithmetic promotion)
Assignment
Object reference assignment conversion happens when you assign an object reference value to a variable of a different type. There are 3 general kinds of object reference type:
- A class type, such as Button or TextField
- An interface type, such as Clonable or LayoutManager
- An array type, such as int[][] or TextArea[]
Conversion rules for implicit casting on this context:
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
- Ready
- Suspended, Sleeping or Blocked (Waiting states)
- Dead
Running
A thread that is running is in execution of the code contained in the run() method. A loop or some “waiting routine” as shown in the previous examples don’t prevent threads from being in the running state.
Ready
In this state, the thread is “ready” to execute. As soon as the Java’s thread scheduler finds a chance, the thread in this state will be executed (or will continue execution). For example, when a Thread is started via start(), the Ready state is set. Once conditions allow the scheduler to execute the thread, run() will be invoked. This may not happen immediately, if the CPU is busy or there are some heavy Threads already running that don’t free the CPU up.
Suspended (Deprecated)
Sleeping
A sleeping thread passes time without doing anything and without using the CPU. A sleeping Thread is PUT TO SLEEP via the sleep() method. A loop that is expecting some value to continue program flow doesn’t put a thread in the sleeping state.
Example:
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:
- All synchronized features of an object based on the class that has a synchronized block become blocked to all other threads.
- When the thread exists the synchronized block, the object becomes unblocked for any other thread including the current one.
To illustrate the last example, we have produced a complete working program:
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:
- The class is final so it cannot be extended.
- The constructor is private so no instances can be created.
- The methods and constants are static and may be accessed directly.
Math constants
- Math.PI
- Math.E
They are declared to be public, static, final, and double.
Methods of the Math Class
int abs(int i)
Returns the absolute value of i:
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:
- public byte byteValue()
- public short shortValue()
- public long longValue()
- public float floatValue()
- public double doubleValue()
- public char charValue()
The wrapper classes are useful whenever it would be convenient to treat a piece of primitive data as if it were an object.
Summary:
- Every primitive type has a corresponding wrapper class type.
- All wrapper types can be constructed from primitives; all except Character that can also be constructed from strings.
- Wrapped values can be tested for equality with the equals() method.
- Wrapped values can be extracted with various Value() methods.
- Wrapper classes provide various utility methods, including the static valueOf() methods, which parse an input string.
- Wrapped values cannot be modified.
Strings
Java uses 16-bits Unicode characters rather than 8-bits byte characters.
The String class
The String class contains an immutable string. Once an instance is created, the string it contains cannot be changed.
Construction:
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:
- Implement the equals() method
- Implement the java.lang.Comparable interface and the compareTo() method
- You may also require to define the hashCode() method if you use Map.
Collections impose no order, nor restrictions, on content duplication.
Some concepts:
- Lists maintain an order (possibly inherent in the data, possibly externally imposed).
- Sets reject duplicate entries.
- Maps use unique keys to facilitate lookup of their contents
Let’s see the most common collection storage approaches now:
Array storage
Tends to be fast to access, but it is relatively inefficient as the number of elements in the collections grows or if elements need to be inserted or deleted in the middle of the set.
A linked list
Alows elements to be added to, or removed from, the collection at any location in the container, and allows the size of the collection to grow arbitrarily without the penalities associated with array copying. Indexed access is slower.
A tree
Allows easy addition and deletion of elements and arbitrary growth of the collection. Unlike a list, trees insist on a means of ordering. Indexed access is slow, but searching is faster.
A hash table
It requires that some unique identifying key can be associated with each data iten, which in turn provides efficient searching. Indexed access is slow, but searching is particulary fast.
Collection Implementations in the API
HashMap/Hashtable
These two classes are very similar, using hashbased storage to implement a map. Hashtable does not allow the null value to be stored.
HashSet
This is a set, so it does not permit duplicates and it uses hashing for storage
LinkedList
This is an implementation of a list, based on a linked-list storage.
TreeMap
This class provides an ordered map. The elements must be orderable, either by implementing the Comparable interface or by providing a Comparator class to perform the comparisions.
TreeSet
This class provides an ordered set, using a tree for storage. As with the TreeMap, the elements must have an order associated with them.
Vector
This class implements a list using an array internally for storage. The array is dynamically reallocated as necessary, as the number of items in the vector grows.
Input and Output
Character Encodings
Various constructors and methods in Java accept string arguments that specify the character encoding to be used when converting between raw eight-bit bytes and sixteen-bit Unicode characters.
Every implementation of the Java platform is required to support the following character encodings:
- US-ASCII: Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set
- ISO-8859-1: ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1
- UTF-8: Eight-bit Unicode Transformation Format
- UTF-16BE: Sixteen-bit Unicode Transformation Format, big-endian byte order
- UTF-16LE: Sixteen-bit Unicode Transformation Format, little-endian byte order
- UTF-16: Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order accepted on input, big-endian used on output)
Main Classes
- File: allows to navigate the file system, move and delete files, etc.
- RandomAccessFile: low-level byte random file access.
- File[Input/Output]Stream: low-level byte sequential file access.
- Data[Input/Output]Stream: high-level datatype sequential file access.
- Buffered[Input/Output]Stream: allows to read/write large blocks of bytes.
Classes to manipulate Unicode characters:
- File[Reader/Writer]: standard file access.
- CharArray[Reader/Writer]: reads and writes char arrays.
- Piped[Reader/Writer]: provides a mechanism for thread communication.
- String[Reader/Writer]: reads and writes strings.
- Buffered[Reader/Writer]: alllos to read/write data in large blocks.
- InputStreamReader and OutputStreamWriter: sequential char access
- LineNumberReader: file access as a sequence of lines of text.
The File Class
The java.io.File class represents the name of a file or directory that might exist on the host machine’s file system. File does not create a file on the local file system.
Common constructors:
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:
- RandomAccessFile(String file, String mode)
- RandomAccessFile(File file, String mode)
The mode string should be either “r” or “rw”.
Seeking methods:
- long getFilePointer() throws IOException: returns the current position within the file, in bytes. Subsequent reading and writing will take place starting at this position.
- long length() throws IOExeception: returns the length of the file, in bytes.
- void seek(long position) throws IOException: sets the current position within the file, in bytes. Subsequent reading and writing will take place starting at this position. Files start at position 0.
More common methods that support byte reading and writing:
- int read() throws IOException: returns the next byte from the file. Returns -1 if reaches the end of the file.
- int read(byte dest[]) throws IOException: attempts to read enough bytes to fill array dest[]. It returns the number of bytes read, or -1 if the the file was at end of file.
- int read(byte dest[], int offset, int len) throws IOException: attempts to read len bytes into array dest[], starting at offset. It returns the number of bytes read, or -1 if the file was at end of file.
- void write(int b) throws IOException: writes the low-order byte of b
- void write(byte b[]) throws IOException: writes all bytes of array b[]
- void write(byte b[], int offset, int len) throws IOException: writes len bytes from byte array b[], starting at position offset.
Several methods for primitive data types are also provided. When a random-access file is no longer needed, it should be closed:
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:
- FileInputStream(String pathname)
- FileInputStream(File file)
Mose useful methods:
- int read() throws IOException: returns the next byte from the file or -1 if the file was at the end of the file.
- int read(byte dest[]) throws IOException: attempts to read enough bytes to fill array dest[]. It returns the number of bytes read or -1 if the file was at the end of file.
- int read(byte dest[], int offset, int len) throws IOException: attempts to read len bytes into array dest[], starting at offset. It returns the number of bytes read or -1 if the file was at the end of file.
- int available() throws IOException: returns the number of bytes that can be read without blocking.
- void close() throws IOException: releases non-memory system resources associated with the file. A file input stream should always be closed when no longer needed.
- long skip(long nbyes) throws IOException: attempts to read and discard nbytes bytes. Returns the number of bytes actually skipped.
FileOutputStream
Main consturctors:
- FileOutputStream(String pathname)
- FileOutputStream(File file)
Most useful methods:
- void write(int b) throws IOException: This writes the low-order byte of b.
- void write(byte bytes[]) throws IOException: This writes all members of array bytes[].
- void close() throws IOException: releases non-memory system resources associated with the file.
Example:
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:
- boolean readBoolean() throws IOException
- byte readByte() throws IOException
- char readChar() throws IOException
- double readDouble() throws IOException
- float readFloat() throws IOException
- int readInt() throws IOException
- long readLong() throws IOException
- short readShort() throws IOException
- String readUTF() throws IOException
- void close() throws IOException
DataOutputStream
Constructor:
DataOutputStream(OutputStream oststream)
Most useful methods:
- void writeBoolean(boolean b) throws IOException
- void writeByte(int b) throws IOException
- void writeBytes(String s) throws IOException
- void writeChar(int c) throws IOException
- void writeDouble(double d) throws IOException
- void writeFloat(float b) throws IOException
- void writeInt(int i) throws IOException
- void writeLong(long l) throws IOException
- void writeShort(int s) throws IOException
- void writeUTF(String s) throws IOException
Example:
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:
- FileReader(String pathname)
- FileReader(file)
FileWriter
Constructors:
- FileWriter(String pathname)
- FileReader(File file)
FileReader and FileWriter extends the Reader and Writer classes, some other useful classes are:
- CharArrayReader and CharArrayWriter: These classes read and write char arrays.
- StringReader and StringWriter: These classes read and write strings.
Reader, most useful methods:
- int read() throws IOException: returns the next char (stored in the low order 16 bits of the int return value) or -1 if at end of input.
- int read(char dest[]) throws IOException: attempts to read enough chars to fill array dest[]. It returns the number of chars read or -1 if at end of input.
- int read(char dest[], int offset, int len) throws IOException: attempts to read len chars into array dest[], starting at offset. It returns the number of chars read or -1 if at the end of input
Writer, most useful methods:
- write(int ch) throws IOException: writes the char that appears in the low-order 16 bits of ch
- void write(String str) throws IOException: writes the string called str
- void write(String str, int offset, int len) throws IOException: writes the substring of str that begins at offset and has length len
- void write(char chars[]) throws IOException: writes the char array chars[]
- void write(char chars[], int offset, int len) throws IOException: writes len chars from array chars[], beginning at offset.
Other high-level classes:
- Buffered[Reader/Writer]: minimize I/O overhead
- [Input/Output]Stream[Reader/Writer]: convert between streams of bytes and sequences of Unicode characters. By default, the classes assume that the stream use the plataform’s default character encoding; alternative constructors provide any desired encoding
- LineNumberReader: views its input as a sequence of lines of text. A method called readLine() returns the next line, and the class keeps track of the current line number
Example:
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:
- Applets and Panels: FlowLayout
- Frames: BorderLayout
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
- FlowLayout(int align)
- FlowLayout(int align, int hgap, int vgap)
Align is usually one of these constants:
- FlowLayout.LEFT
- FlowLayout.RIGHT
- FlowLayout.CENTER
- FlowLayout.LEADING
- FlowLayout.TRAILING
The hgap and vgap parameters set the horizontal and vertical gap of the components affected by the Layout Manager.
The Grid Layout Manager
The Grid layout manager subdivides its territory into a matrix of rows and columns. The number of rows and number of columns are specified as parameters to the manager’s constructor:
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:
- BorderLayout.NORTH
- BorderLayout.SOUTH
- BorderLayout.EAST
- BorderLayout.WEST
- BorderLayout.CENTER
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:
- void first(Container)
- void last(Container)
- void next(Container)
- void previous(Container)
- void show(Container, String)
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:
- GridBagConstraints.CENTER
- GridBagConstraints.NORTH
- GridBagConstraints.NORTHEAST
- GridBagConstraints.EAST
- GridBagConstraints.SOUTHEAST
- GridBagConstraints.SOUTH
- GridBagConstraints.SOUTHWEST
- GridBagConstraints.WEST
- GridBagConstraints.NORTHWEST
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:
- GridBagConstraints.HORIZONTAL
- GridBagConstraints.VERTICAL
- GridBagConstraints.BOTH
- GridBagConstraints.NONE
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:
- GridBagConstraints.RELATIVE: Specify that this component is the next-to-last component in its column or row (gridwidth, gridheight), or that this component be placed next to the previously added component (gridx, gridy).
- GridBagConstraints.REMAINDER: Specify that this component is the last component in its column or row.
Complete example:
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:
- public void setSize(int width, int height)
- public void setBounds (int x, int y, int width, int height)
There are also other versions for these methods
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:
- boolean getState()
- void setState(boolean state)
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:
- Checkbox getSelectedCheckbox()
- void setSelectedCheckbox(Checkbox newSelection)
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:
- FileDialog.LOAD
- FileDialog.SAVE
Partial method list:
- public String getFile()
- public void setFile(String file)
- public String getDirectory()
- public void setDirectory(String dir)
Label
Labels are used to display simple but dynamic text information
Common construction:
Label l = new Label();
Label l = new Label("Hello");
Label l = new Label("Hello", Label.LEFT);
Alignments:
(int constants)
- Label.LEFT
- Label.CENTER
- Label.RIGHT
Partial method list:
- String getText()
- void setText(String newText)
List
A list is a collection of text items, arranged vertically. If a list contains more items that it can display, it automatically acquires a vertical scroll bar.
Partial constructor list:
- List()
- List(int VisibleRows)
- List(int VisibleRows, boolean multiSelectOK)
Example:
List l = new List(10, true);
l.add("English");
l.add("Spanish");
l.add("Italian");
add(l);
Partial method list:
- void add(String text)
- void add(String text, int index)
- String getItem(int index)
- int getItemCount()
- int getRows()
- int getSelectedIndex()
- int[] getSelectedIndexes()
- String getSelectedItem()
- String[] getSelectedItems()
ScrollPane
The ScrollPane component may contain a single component, which may be taller or wider than the scroll pane itself. If the contained component is larger than the scroll pane, then the default behavior of the scroll pane is to acquire horiziontal and/or vertical sscroll bars as needed.
Constructors:
- ScrollPane(): constructs a scroll pane with default scroll bar hehavior
- ScrollPane(int scrollbarPolicy): construct a scroll pane with the specified scroll bar behavior
Constants:
- ScrollPane.SCROLLBARS_AS_NEEDED
- ScrollPane.SCROLLBARS_ALWAYS
- ScrollPane.SCROLLBARS_NEVER
Example:
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:
- Scrollbar(): constructs a vertical scroll bar
- Scrollbar(int orientation): consturcts a scroll bar with the specified orientation.
- Scrollbar(int orientation, int initialValue, int sliderSize, int minValue, int maxValue): constructs a scroll bar with the specified parameters.
Description:
- initialValue: It is the value that the slider represents. It should be represented by the range of numbers between minValue and maxValue.
- sliderSize: It’s the size relative to the range of numbers between minValue and maxValue. For example, if minValue = 300 and maxValue = 350, a sliderSize of 75 would extend to a half of the scrollbar.
- minValue and maxValue: These are the values for the range of numbers you want to represent using a scroll bar.
Orientation constants:
- Scrollbar.HORIZONTAL
- Scrollbar.VERTICAL
Partial method list:
- public int getValue()
- public void setValue(int newValue)
Example:
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:
- TextField(): empty text field
- TextField(int Cols): empty text field with the specified number of columns
- TextField(String text): text field whose initial constent is text
- TextField(String text, int nCols): text field whose initial constent is text, with the specified number of columns.
TextArea constructors:
- TextArea(): empty text area
- TextArea(int Rows, int Cols): empty text area with the specified number of rows and columns.
- TextArea(String text): a text area whose initial content is text
- TextArea(String text, int Rows, int Cols): a text area whose initial content is text, with the specified number of rows and columns.
- TextArea(String text, int Rows, int Cols, int scrollbarPolicy): Same as above but the scroll bar placement policy is determinated by the last parameter, which should be one of the following:
- TextArea.SCROLLBARS_BOTH
- TextArea.SCROLLBARS_NONE
- TextArea.SCROLLBARS_HORIZONTAL_ONLY
- TextArea.SCROLLBARS_VERTICAL_ONLY
Partial method list:
- String getSelectedText()
- String getText(): returns the text contents of the component
- void setEditable(boolean editable): if editable is true, permits the user to edit the component; if false, the user can still navigate using the arrow keys, but cannot alter the text.
Example:
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:
- void setSize(int width, int height)
- void setBounds(int pos_x, int pos_y, int width, int height)
- void setVisible(boolean)
Panel
Applets and frames serve as top-level or outermost GUI components. Panels provide an intermediate level of spatial organization for GUIs. You are free to add all components of a GUI directly into an applet or a frame but you can provide additional levels of grouping by adding components to panels and adding panels to a top-level appplet or frame.
Dialog
A dialog is a pop-up window that typically accepts user input in response to a specific question. The window manager often uses slighty different decorations for dialogs. Dialogs may optionally be made modal, in which case input cannot be directed at the application’s main frame while the dialog is visible. The Dialog class is the superclass of the FileDialog class. The default layout manager for a dialog is Border layout.
Complete constructor:
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:
- Create a menu bar and attach it to the frame
- Create and populate the menu
- Attach the menu to the menu bar
There are four kinds of elements that can be mixed and matched to populate a menu:
Menu items:
Constructor: MenuItem(String text)
Check-box menu items:
Constructor: CheckboxMenuItem(String text) Methods: getState(boolean checked_state), boolean setState()
Separators
Menu's method: addSeparator()
Menus
When you add a menu to another menu, the first menu’s label appears in the second menu, with a pull-right icon.
Frames have a special method called setHelpMenu() to set the usual last menu of an application.
Example:
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);
Popup menus
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:
- Components
- Images
- Printers
There are four classes of “blank” components that have no default appearance and will show up as empty rectangles, unless they are subclassed and given paint() methods. These four component classes are:
- Applet
- Canvas
- Frame
- Panel
Selecting a Color
You can choose one of these color constants:
- Color.black
- Color.blue
- Color.cyan
- Color.darkGray
- Color.gray
- Color.green
- Color.lightGray
- Color.magenta
- Color.orange
- Color.pink
- Color.red
- Color.white
- Color.yellow
You can also create your own colors using the basic RGB constructor:
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:
- Serif
- Sansserif
- Monospaced
style: some of these constants are commonly used:
- Font.PLAIN
- Font.BOLD
- Font.ITALIC
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:
- ActionEvent
- AdjustmentEvent
- ItemEvent
- TextEvent
Event Class Hierarchy
java.util.EventObject
|
|
java.awt.AWTEvent --|
| |- ActionEvent
| |- AdjustmentEvent
| |- ItemEvent
| |- TextEvent
|
ComponentEvent --|
| |- ContainerEvent
| |- FocusEvent
| |- PaintEvent
| |- WindowEvent
|
InputEvent --|
|- KeyEvent
|- MouseEvent
Event details:
- ActionEvent: generated by activation of components.
- AdjustmentEvent: generated by adjustment of adjustable components such as scroll bars.
- ContainerEvent: generated when components are added to or removed from a container.
- FocusEvent: generated when a component receives or loses input focus.
- ItemEvent: generated when an item is selected from a list, choice, or check box.
- KeyEvent: generated by keyboard activity.
- MouseEvent: generated by mouse activity.
- PaintEvent: generated when a component is painted.
- TextEvent: generated when a text component is modified.
- WindowEvent: generated by window activity (such as iconifying or decoinfying).
Listener Interfaces and their methods
Interface | Interface Methods | Add Method |
---|---|---|
ActionListener | actionPerformed(ActionEvent) | addActionListener() |
AdjustmentListener | adjustmentValueChanged (AdjustmentEvent) | addAdjustmentListener() |
ComponentListener | componentHidden(ComponentEvent) 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:
- Create a listener class that implements the desired listener interface.
- Construct the component.
- Construct an instance of the listener class.
- 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:
- Create a subclass of a component
- In the subclass constructor, call enableEvents(AWTEvent.XXX_EVENT_MASK)
- Provide the subclass with a processXXXEvent() method; this method should call the superclass’ version before returning.
Example:
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
- Complete Java 2 Certification Study Guide 2nd edition by Simon Roberts, Philip Heller and Michael Ernest. ISBN: 0-7821-2825-4 (Sep 2000 Sybex)
- The Java Tutorial Third Edition by Mary Campione, Kathy Walrath and Alison Huml. ISBN: 0201703939 (Dec 2000 Addison Wesley)
- Sun’s Java API 1.3. (Sun Microsystems)