CHAPTER 3
Basic Java Programming

“If you think you can do a thing or think you can't do a thing, you're right.”

—Henry Ford

3.1 Introduction

This chapter introduces the basic concepts of Java programming, intended for those who have some experience in coding and familiarity with programming concepts but are new to the Java language.

3.2 Variables

Java supports eight primitive types of variables: byte, short, int, long, float, double, char, and boolean. Variable names in Java are case-sensitive and must start with a letter, an underscore, or a dollar sign ($). They cannot start with a number. After the first character, a variable name can include any combination of letters and numbers. Spaces and special symbols, such as !, "", £, %, &, *, #, @, ~, and so on, are not allowed in variable names. In Java, all variables must be declared before they can be used. To declare a variable, you will need to specify the type, leave at least one space, and then specify the variable name and a semicolon (;).

type variablename;

The following code shows the declaration and initialization of an integer variable named x:

int x;        // Declares an integer type variable called x.
x = 10;       // Initialize x

or

int x = 10;  // Declaration and Initialization

You can also declare multiple variables of the same type all in one line.

int x, y, z;             // Declares three int variables, x, y, and z.
x = 10; y = 5; z=1;      // Initialize x, y, and z

or

int x = 10, y = 5, z=1;  // Declaration and Initialization

Example 3.1A shows a simple declaration and initialization of two integer variables. Here, as explained in Chapter 2, System.out.println() is used to display the text on the screen, and println("x = " + x) displays the text "x = " and the value of the variable x on the screen. The + sign means to concatenate the text and variable. You can find more details about input and output in section 3.5.

If you want to declare the variables outside the main() method, you need to declare the variables as static, as only static variables or methods can be used in the main() method; see Example 3.1B.

Example 3.1C shows an example of the declaration and initialization of different types of variables. Please note that you will need to put f at the end of the float number to indicate that it is a float number, not a double number. Similarly, you will need to put L at the end of a type long number to indicate that it is a long number.

3.2.1 Constants

Constants are variables whose value does not change during program execution. You can define constants the same way as variables, but with the static and final keywords in the front. The static keyword means that the variable persists throughout the class, and only one copy of the value is maintained in memory. The final keyword means that the variable's value cannot be changed. Because their value cannot be changed, constants must be initialized at the time they are defined. Conventionally, constant names use all capital letters, as in these examples:

static final int TOTALNUMBER=120;
static final float PI=3.1415926F;

3.2.2 The String and StringBuffer Types

Java also supports two variable types, String and StringBuffer, in which you can store sequences of character values. The following code shows how to create a String variable that stores a student's class name:

String ClassName;
ClassName = new String(); 
ClassName = "Maple Tree Class";

Or you can simply merge those three steps into one.

String ClassName = "Maple Tree Class";

String variables are object variables, which come with many useful methods (Java's term for functions; see section 3.9 for details) for string operations, such as length(), charAt(), equals(), concat(), trim(), compareTo(), toUpperCase(), toLowerCase(), and substring().

For example, if t1 and t2 are String type variables, the expression

t1.compareTo(t2)

returns 0 if t1 and t2 are equal in their American Standard Code for Information Interchange (ASCII) values, a negative number if t1 is less than t2, or a positive number if t1 is greater than t2. In ASCII, each alphabetic, numeric, or special character is represented with a 7-bit binary number. There are 128 possible characters.

The expression

t1 = t1.concat(t2)

appends (or concatenates) t2 to the end of t1.

In Java, the String type is immutable. String immutability means that once a String object is created, it cannot be changed. This brings several benefits in caching, security, synchronization, and performance. For String caching, Java uses the concept of a String pool, a special memory place where all the String values are stored. Because Strings are immutable, Java can optimize the amount of memory usage by storing only one copy of each literal String in the pool. This enhances performance by saving memory.

For example, in the following code, both s1 and s2 are actually pointing to the same object Hello in the String pool:

String s1 = "Hello";
String s2 = "Hello";

But after the following concatenation, s2 points to a different object Hello World, while s1 is still pointing to the original object Hello:

s2 = s2 + " World";

For security, Java uses String variables to store usernames and passwords for remote database connections and other networking information. String immutability means that once these objects have been created, they cannot be hacked. Java also uses String variables to store the hashcode, which means that once the hashcode is cached at the time of creation, it doesn't need to be calculated again. All of these features make Java more secure. See Chapter 9 for more about hashcodes and Java security.

Also, because Strings are immutable, a single String instance can be shared across different threads. This avoids the usage of synchronization and makes multithreading safer.

The StringBuffer type provides functions similar to String but is a more powerful type that can handle dynamic string information. StringBuffer is used when there are many modifications to strings of characters. This is because StringBuffer is mutable.

For example, the following code appends text to the existing StringBuffer variable. This process is much faster than the previous String concatenation, such as t1 = t1.concat(t2) or s2 = s2 + " World", as String concatenation will end up creating a new String object each time.

StringBuffer sB = new StringBuffer("Hello");
sB.append(" World");

StringBuffer also has functions such as reverse(), delete(), replace(), and the like.

3.2.3 The VAR Variable Type

The VAR variable type has existed in many other languages, such as JavaScript and Visual Basic. VAR allows you to declare a variable without specifying its type. Java will automatically figure out the type, which makes programming simpler and also improves the readability of code. Java developers have long complained about the need to include boilerplate code and the resulting verbosity in code writing. Finally, beginning in Java 10, the VAR type variable is also available in Java. Here are some examples of how to use it:

   //Java VAR type variable since Java 10
   var str = "Java 10";                 // infers String
   var list = new ArrayList<String>();  // infers ArrayList<String>
   var stream = list.stream();          // infers Stream<String>s

3.3 Operators

Like many other languages, Java supports a full range of standard operators, such as arithmetic operators, comparison operators, logical operators, bitwise operators, and the assignment operator. Table 3.1 summarizes them.

Table 3.1: The Java Operators

OPERATOR TYPE OPERATOR COMMENTS
Arithmetic Operators + Addition and String concatenation
- Subtraction
* Multiplication
/ Division
% Modulus (or remainder operator)
++ Increment
-- Decrement
Comparison Operators < Less than
<= Less than or equal to
> Greater than
>= Greater than or equal to
== Equal to *
!= Not equal to
Logical Operators ! Logical not
&& Logical and
|| Logical or
Bitwise Operators ~ Bitwise complement
& Bitwise and
| Bitwise or
^ Bitwise xor
<< Left shift
>> Right shift
>>> Zero fill right shift
Assignment Operators = Assign a value
+= Add AND assign
-= Minus AND assign
*= Multiply AND assign
/= Divide AND assign
%= Modulus AND assign
>>= Left shift AND assign
<<= Right shift AND assign
&= Bitwise and AND assign
^= Bitwise or AND assign
|= Bitwise xor AND assign

*Please note that == is for comparing numerical values. For comparing strings, use equals() instead.

3.4 Reserved Words

The following is a list of Java reserved words, or keywords that you cannot use for variable names, method names, or class names.

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

3.5 Input and Output

Input and output are important in almost all applications. In Java, System.out is used to print messages on the computer screen; this is called standard output. System.in and System.console are used to read text from the keyboard; this is called standard input.

Example 3.2 demonstrates Java input and output. System.console.readline() is used to read one line from the keyboard, and whatever the user types in will be stored in a String variable named x. Next, System.out.print() is used to display the text on-screen. Finally, System.out.println() works the same way as System.out.print(), except that after displaying the text, it moves the cursor to a new line. Figure 3.1 shows how to compile and run the example and its output.

Screen capture depicting Commands to compile and run the InputOutputExample.java program in Command Prompt.

Figure 3.1: Commands to compile and run the InputOutputExample.java program

Example 3.3 demonstrates another form of Java input and output. In this code, System.in is used to read from the keyboard. Combined with the Scanner class, it can read one line at a time from the keyboard; it can also read an integer value from the keyboard. The line

import java.util.Scanner; 

imports the java.util.Scanner library, which is needed by the Scanner class. This example first asks the user to enter a name, then reads the name from the keyboard, and finally displays the name on the screen. It also prompts for and displays the user's age. The name entered is stored in a String variable called name, and the age entered is stored in a variable named age.

In the Java input and output demonstrated in Example 3.4, System.in is used to feed into InputStreamReader and then into a BufferedReader. In Java all input and output sources are treated as streams, but the Stream type has limited functions, and it is often combined with BufferedReader. With BufferedReader, you can read one line each time from the keyboard. Integer.parseInt() is used to convert strings to integers. The throws IOException and try…catch blocks are used for handling exceptions. In Java it is mandatory to use exception handling for events such as input and output.

The try…catch block or try…catch…finally block is the classical approach to handling an exception in Java, as shown in the following pseudocode structure. The try block is used to enclose the code for doing something. One or more catch blocks are used to handle the exception. The finally block is used to enclose the code that is executed after the try block was successfully executed or a thrown exception was handled.

try {
    // do something
} 
catch (one exception) {
    //display error message
} 
catch (another exception) {
    //display error message
} 
finally {
    //do something
}

The corresponding libraries, java.io.BufferedReader, java.io.IOException, java.io., and InputStreamReader, are also imported.

Can you guess what the program in Example 3.4 will do?

3.6 Loops and Selections

There are three key ingredients that any programming language must have: sequences, loops, and selections. A sequence means statements executing sequentially, one after another. Loops and selections are control structures (or flow controls). Loops, also called iterations or iterative statements, are sets of statements that are executed repeatedly a number of times. Java supports for loops, while loops, and do loops. For example, the following for loop prints Hello World! five times:

for(i=0;i<5;i++){
    System.out.println("Hello World!");
}

Similarly, you can do the same thing using a while loop or a do loop. Both while and do loops need to define and initialize the counter variable before the loop (for example, int i=0;) and manually increase it during the loop (for example, i++). There is a subtle difference between the while loop and the do loop: the while loop checks the condition first before it runs the loop; the do loop runs the loop first and then checks the condition.

int i=0;
while(i<5){
    System.out.println("Hello World!");
    i++;
}
 
int i=0;
do{
    System.out.println("Hello World!");
    i++;
} while(i<5)

Example 3.5 uses a for loop to print all the command-line parameters. It uses args.length to get the total length of the parameters.

Selections, or conditional statements, allow your program to do different things under different conditions. Java supports the if…else conditional statement (a two-way choice), the switch conditional statement (multiple choices), and the ? operator (conditional assignment).

For example, if you want to do something when i==0 and do something else when it does not, you can write the following:

if (i==0){

}
else {

}

If you have more than two choices and want to do different things according to the value of i, you can use the switch conditional statement.

switch (i){
    case 1:

    break;
    case 2:

    break;
    case 3:

    break;
    default:

    break;
}

The ? symbol, often called the ternary operator because it takes three arguments, assigns either of two values to a variable depending on a condition. The following example assigns the value 3 to the variable y if i<0 and assigns the value 4 to y otherwise:

y=(i<0) ? 3:4;

Example 3.6 shows a simple Java program that demonstrates the use of the if…else conditional statement. Figure 3.2 shows how to compile and run the Hello1.java program and its output.

Screen capture depicting compilation, execution, and output results of Hello1.java in Command Prompt.

Figure 3.2: The compilation, execution, and output results of Hello1.java

3.7 Arrays, Matrices, and ArrayLists

Arrays are important in scientific and engineering programs. In Java, you can create one-dimensional arrays, two-dimensional arrays (matrices), multidimensional arrays, and dynamic arrays (lists), which can have a dynamic set of elements, growing and shrinking during the execution of the program.

The following examples show how to declare an integer-type one-dimensional array, a float-type two-dimensional array (a matrix), and a double-type multidimensional array, respectively:

int[] x=new int[10];
 
float[][] y=new float [2][5];
 
double[][][] z=new double [5][2][3];

Alternatively, you can declare and initialize them at the same time.

int[] x={1,2,3,4,5,6,7,8,9,0};
 
float[][] y={{1.0F,1.0F,1.0F,1.0F,1.0F},{2.0F,3.0F,4.0F,5.0F,6.0F}};
 
double[][][] z={{{1,2,3},{4,5,6}},{{7,8,9},{10,11,12}},{{13,14,15},{16,17,18}},{{19,20,21},{22,23,24}},{{25,26,27},{28,29,30}}};

You can also separate the array declaration into two steps. For example, the following:

int[] x;
x=new int[10];

is exactly the same as the previous one-line declaration, but the benefit of this approach is that it will allow you to decide on the size of the array later when you run the program.

Example 3.7 shows how to create an integer-type one-dimensional array called x[] and a double-type two-dimensional array called y[][]. Figure 3.3 shows the compilation, execution, and output results of Array1.java. (Please note that as the array y[][] is randomly generated, your final output may not be the same as in Figure 3.3.) In Java, you can refer to any element of an array using its index, such as x[2] or y[0][1]. All the elements of the array must be of the same type.

Screen capture depicting compilation, execution, and output results of Array1.java in Command Prompt.

Figure 3.3: The compilation, execution, and output results of Array1.java

A drawback of arrays is that their size is fixed when they are defined. Sometimes you need an array whose size can be changed as the program executes. You can create such an array by using Java's ArrayList class, which implements a “growable” array of objects. The following examples show how to declare dynamic arrays of String, integer, and double types. Similarly, you can also create an ArrayList of any objects.

ArrayList<String> v=new ArrayList<String>();
ArrayList<Integer> v=new ArrayList<Integer>();
ArrayList<double> v=new ArrayList<double>();

The angular braces, < >, indicate that this is a generic ArrayList. Java generic programming was introduced in Java SE 5 with the aim of reducing bugs and dealing with type-safe objects. As illustrated next, the nongeneric ArrayList example code can be compiled without any errors, but when you try to run the code, it will give a runtime error, as you are trying convert a string into an integer. In the generic ArrayList example code, <String> forces the ArrayList to contain only string values; therefore, it will generate a compilation error. In this way, generics programming makes Java code more robust and less error prone.

Here's the nongeneric ArrayList example code:

ArrayList list=new ArrayList();
list.add("hello");
Integer i = (Integer)list.get(0);

Here's the generic ArrayList example code:

ArrayList<String> list = new ArrayList<String>();
list.add("hello");
Integer i = (Integer)list.get(0);

For more information about Java generic programming, please visit these sites:

https://docs.oracle.com/javase/tutorial/java/generics/index.html

https://en.wikipedia.org/wiki/Generics_in_Java

http://tutorials.jenkov.com/java-generics/index.html

Example 3.8 shows how to create a dynamic ArrayList named list, which can grow using an add() method, as well as shrink using a remove() method. Figure 3.4 shows the compilation, execution, and output results of arraylist1.java.

Screen capture depicting compilation, execution, and output results of Arraylist1.java in Command Prompt.

Figure 3.4: The compilation, execution, and output results of Arraylist1.java

3.8 Reading and Writing Files

Many applications need to read and write files. Java supports reading and writing both text and binary files, but I will cover only text files here. There are many ways in Java to read and write text files; the most basic one is by using the FileReader and FileWriter classes of the java.io package.

To use the FileWriter class to write to a text file, take the following steps:

  1. Create a FileWriter object associated with the text file that you want to write.
  2. Create a BufferedWriter object associated with the FileWriter object.
  3. Write to the buffer.
  4. Flush the buffer.
  5. Close the file.

Using the BufferedWriter class for writing is optional; you can also write to a text file without using a buffer. But using a buffer, with the BufferedWriter and BufferedReader classes, can make the reading and writing process more efficient. The BufferedWriter class first bundles up several small write requests in its own internal buffer, and then when the buffer is full or the flush() method is called, it writes the whole buffer into the text file in one go. For file read and write, a try…catch block must be used to handle exceptions.

Example 3.9 shows how to write data to a text file using the FileWriter and BufferedWriter classes. It uses the args[0] parameter to get the name of the text file, which you will write data into. It will produce an error message and exit the program if no file name is provided.

To use the FileReader class to read from a text file, take the following steps:

  1. Create a FileReader object associated with the text file that you want to read.
  2. Create a BufferedReader object associated with the FileReader object.
  3. Read from the buffer.
  4. Flush the buffer.
  5. Close the file.

Again, using the BufferedReader class is optional, but it does make text file reading more efficient.

Example 3.10 shows how to read data from a text file using the FileReader and BufferedReader classes.

3.9 Methods

Methods, also called functions, modules, or subroutines in other languages, allow programmers to modularize their programs and hence reduce the complexity of their software and improve its reusability. Methods normally have some input parameters, which are also called formal parameters or arguments, to carry out their specific tasks. The variables defined within a method are called local variables because they can be used only within that method. Formal parameters are also local variables.

Example 3.11 demonstrates the use of methods. Instead of printing out the Hello message directly, this program calls a method called printhello() to do the job. printhello() has one String type input parameter, msg. The void keyword means that there is no returned parameter for printhello(). The static keyword means that printhello() exists within the entire class. It is compulsory here, as only the static methods can be called in the main() method. The private keyword means this method can be called only within the current class.

Example 3.12 shows another example of methods. In the main() method, a Method2 class object is created first, Test test = new Test();, and then the pupAge() method is called.

test.pupAge();

In this case, the pupAge() method is called through an object-oriented approach, and therefore it does not need to be static. See the next section for more details about object-oriented programming.

In cases where you do need to return some value from a method, you need to specify what type of value will be returned. For example, the add() method shown in Example 3.13 takes in two double numbers, adds them, and returns the sum.

3.10 Object-Oriented Programming

When a problem is simple, you can just write one Java program, or one class, to solve it. But as the problems become more and more complicated, it is better to write the program using “separation of concerns” or “divide-and-conquer” approaches, that is, to write multiple classes in an object-oriented programming (OOP) fashion. OOP is a huge topic that itself can fill a whole book. But the benefits of OOP are obvious. Because it divides a big Java class into several smaller, self-contained, and reusable Java classes, it makes the code more understandable, more manageable, and less bug-prone.

In the simplest terms, OOP in Java means developing or using self-contained Java classes, which have specific attributes and certain behavior. The attributes are parameters (variables, or objects), and the behavior consists of methods (again, called functions in other languages). These parameters are used for exchanging input/output values with external programs, and the methods are for performing specific tasks. The following are the most important OOP concepts.

3.10.1 Classes and Objects

A class is a template that specifies the attributes and behavior of typical instances of that class. An object is an instance that is created based on a class. A real-life analogy can be tree (class) and oak, palm, pine (objects), or animal (class) and lion, tiger, dog (objects). In object-oriented programming, a problem is described as a collection of objects with various interactions between them. This will, in turn, reduce the complexity of the problem and improve the reusability of software code.

3.10.2 Instantiation

The process of creating an object from a class is called instantiation. The object is an instance of that class.

3.10.3 Encapsulation

In object-oriented programming, all objects are self-contained. This means that although the methods of objects can be called by external programs, their details are hidden from outside. This approach is called encapsulation, and it makes object-oriented programming easier and less error-prone. To use encapsulation in Java, you need to declare the variables of a class as private and provide public setter and getter methods to modify and view the variable values.

3.10.4 Inheritance

In object-oriented programming, not only can you create classes, but you can also create subclasses using the existing classes to enrich their functionalities. This is called inheritance. Unlike other OOP languages, Java supports only single inheritance, which means that a subclass can be derived from only a single parent class. This constraint is intended to make OOP simpler. In cases where multiple inheritance is needed, you must create it through the Java interface, which defines collections of parameters and methods. You can create a Java class that implements as many interfaces as you like.

3.10.5 Overriding and Overloading

In Java, when a child class inherits its parent class's methods, it can also redefine the content of the methods. This standard OOP technique is known as method overriding. Another similar approach is method overloading, which allows you to define two or more functions with exactly identical names but with different formal parameters.

3.10.6 Polymorphism

In object-oriented programming, when an overriding method is invoked, the method that is associated with the class (or subclass) of the object is always used. In this example, there is a getLeafShape() method in a class called Tree, which is overridden in its subclasses Oak and Pine.

Tree tree;
Oak oak=new Oak();
Pine pine=new Pine();
tree=oak;
tree.getLeafShape();
tree=pine;
tree.getLeafShape();

There are two identical tree.getLeafShape() statements. The first one calls the Oak class's getLeafShape() method, while the second one calls the Pine class's getLeafShape() method. This feature is known as polymorphism, and it improves software reusability and enhances information hiding.

3.10.7 Object Accessibility

When an object, a variable, or a method is defined in a class, it falls into one of the four categories that control its accessibility or scope: public, protected, package, and private. For example, a public variable can be accessed from anywhere, but a private variable can be accessed only within the same class. The default is package, and there is no keyword for package accessibility. Table 3.2 summarizes class object accessibility.

Table 3.2: Class Object Accessibility

CATEGORY SAME CLASS SUBCLASS SAME PACKAGE OTHER
Public Yes Yes Yes Yes
Protected Yes Yes No No
Package Yes Yes Yes No
Private Yes No No No

3.10.8 Anonymous Inner Classes

In Java, anonymous inner classes provide a way to create an object without a name and without having to actually subclass a class. An anonymous inner class can be useful when making an instance of an object with overloading methods of a class or interface.

Example 3.14A gives you a taste of OOP. It has two classes, Math1 and Oop1. The class Oop1 is the main class because it has the main() function in it. The Oop1 class first creates an object (using the new keyword) named m using the Math1 class and then calls the setZ() method and the getZ() method to set the value for the private variable Z in the Math1 class, which is hidden from outside the class; this is called encapsulation.

You can also separate the Math1 class and the Oop1 class into two files, one called Math1.java and one called Oop1.java. The program will run exactly the same way, but separation into different files will make the code much easier to manage and understand.

Example 3.14B is another example of OOP. It also has two classes, Math2 and Oop2, where Oop2 is the main class. The Oop2 class first creates an object (using the new keyword) named m using the Math2 class and then calls the public parameters and methods by using dot notation (for example, m.add(), m.W, and so on). This is different from other languages such as C++ that use -> notation.

Note that there are two add functions in the Math2 class. This is an example of method overloading. The Math2() method is a special type of method called a constructor method. The constructor method is called automatically each time an object is created, and it is mainly used for initializing parameters. In Java, there are many ways to access the public parameters in a class.

Example 3.14C shows another example of OOP. It has three classes, the Math3 class, the Math3a class, and the Oop1 class, where Math3a is a subclass of Math3. When a child class is created from its parent class, it will not only inherit all the public and protected parameters and methods of the parent class but will also introduce its own new parameters and methods (toDegree()). In this example, the func() method has been redefined in the subclass, an example of method overriding.

You can also separate the Math3 class, Math3a class, and Oop3 class into three files, one called Math3.java, one called Math3a.java, and one called Oop3.java. They will still run in the same way.

Example 3.15A shows an example of an interface in OOP. It has an interface called Animal, which has a method called speak(). The Dog class implements the Animal interface and creates a definition of the speak() method. Finally, in the main() method, an object named p is created, and the speak() method is called.

Example 3.15B shows an anonymous inner class version of the previous example. As you can see, it creates an object, overriding the speak() method, and calls the speak() method, without needing to give the object a name or implement the interface. This has greatly simplified the code.

3.11 Multithreading

To thread or not to thread, that is the question—but not for Java programmers, fortunately.

Many applications need to perform several tasks at the same time. Multithreading is a powerful way of achieving that. In software programming, a thread is an independent path of executing a task within a program. Multithreading means to execute two or more tasks, or threads, concurrently within a single program. Unlike other traditional programming languages, Java supports multithreading natively. Threads are a standard part of the Java language.

Example 3.16A shows a simple multithreading program called HelloThread0.java. The HelloThread0 class is a subclass of the Java Thread class. You need to override the run() method of the Thread class in order to specify what you want to execute in the thread. In this example, this is only one thread, which just prints a message. Once the thread object has been created, you can use the start(), yield(), join(), and stop() methods to start, hold (give other threads a chance to execute), wait until another thread completes, and stop the thread.

Example 3.16B shows a slightly more complicated version of the previous program. In this case, it has three threads, and each thread can print different messages so that you can differentiate them from each other. The message is passed into the thread through the thread constructor method. When you run the code, you will find that the threads might not appear in order. That is because they are executed independently.

Although Example 3.16A and Example 3.16B show a standard way of creating a multithreaded program, Java's single-inheritance limitation sometimes makes it much more desirable to implement the Runnable interface to achieve multithreaded programming. You also need to override the run() method in the class to specify what you want to execute in the thread. Example 3.16C shows the details.

Example 3.16D shows another simple, elegant way of implementing multithreaded programming in Java by using a Java anonymous inner class, in which you create the thread object, define the run() method, and start the thread all in one go. See the previous OOP discussion for more details on Java anonymous inner classes.

Example 3.16E shows an object-oriented approach of implementing multithreaded programming. Here a separated class called Hello4 is created, which implements the Runnable interface.

Example 3.16F also shows an object-oriented approach to implementing multithreaded programming. Here a separate class named Hello5 is created, which is a subclass of the Thread class.

Example 3.16G shows a more complicated multithreading program that does some calculations in the thread. It is based on the previous OOP multithreaded example. Figure 3.5 shows the compilation, execution, and output results.

Image described by caption and surrounding text.

Figure 3.5: The compilation, execution, and output results of Thread1.java. Please note that your result might be different from the screenshot, as the threads can be executed in random order.

Example 3.16H is a variation of Example 3.16A. Instead of inheriting from the Thread class, it implements the Runnable interface.

3.11.1 The Life Cycle of a Thread

A thread can be in one of several defined states. When a thread is created, it is in the born state. When the thread's start() method is called, the thread enters the ready state. When the thread begins executing, it enters a running state. A running thread can then enter a waiting state, sleeping state, or blocking state, and a dead state when it is terminated. You can use the getState() method to determine the state of a thread.

The Java thread states are as follows:

  • New
  • Runnable
  • Running
  • Nonrunnable (blocked)
  • Terminated

Example 3.17A shows a simple example of how to use the getState() method to get the state of a thread. This is the same thread as shown in Example 3.16A, which simply prints a message. After creating the thread object, it displays the state of the thread before it starts and after it starts. It then waits for one second and displays the states again. The delay is achieved by using the Thread.sleep() method; please note that this method must be placed in a try…catch block. You can find more details about the Thread.sleep() method in the next section.

The following is the output of Example 3.17A. You can see the different states of the thread, and after one second, the thread has been terminated.

Example 3.17B is an improved version of the previous program. In this case, I added a five-second wait in the thread. Again, the wait is implemented by using the Thread.sleep(1000) method, placed in a try…catch block. After creating the thread object, it displays the state of the thread before it starts and then after it starts. It then waits for one second and displays the states again.

The following is the output of Example 3.38. As you can see, because of the five-second delay in the thread, the thread is in the TIMED_WAITING state after one second.

3.11.2 Thread Priorities

Different threads can have different priorities. Use the setPriority() method to set a thread's priority, which can range from 1 to 10, where 1 is the Thread.MIN_PRIORITY, 10 is the Thread.MAX_PRIORITY, and 5 is the Thread.NORM_PRIORITY.

Example 3.17C shows a simple thread priority example. It uses the setPriority() method to set the priority of each thread. If you run the code, you will find that the thread with higher priority tends to be executed first.

3.11.3 Thread Scheduling

Many Java platforms use time slicing to execute threads. With time slicing, the CPU time is divided into a series of small slots called quanta. Threads with equal priority will be executed in a round-robin fashion, one thread per quantum. Threads with higher priority will be executed ahead of those with lower priority.

3.11.4 Thread Synchronization

When several threads are operating on the same object and it has synchronized methods, the threads also need to be synchronized, which means that only one thread at a time is allowed to execute synchronized methods on the object. This requirement is implemented by locking the object when a synchronized method is invoked.

Example 3.17D demonstrates simple thread synchronization. In this example, the Test() class has a printMessage() method, which prints a message five times and waits for 500 ms each time. In the main() method, two threads were created, and both need to access the same Test() object. Without synchronization, you can see both threads can access the object.

Now uncomment the //synchronized void printMessage(String txt) line, and comment out the void printMessage(String txt) line. By adding the synchronized keyword in front of the printMessage() method, it becomes synchronized, which means that when the Test() object is accessed by one thread, it will be locked; the second thread cannot access it until the first thread has finished.

3.12 Date, Time, Timer, and Sleep Methods

You'll often need dates, times, and accurate timers in your programs. In Java, you can use the new Date().toString() method to get the date and time string, you can use the System.currentTimeMillis() method, or you can use the new Date().getTime() method to get the current time in milliseconds past midnight, January 1, 1970, UTC. You can also use the System.nanoTime() method to get the most accurate system time in nanoseconds.

Example 3.18A is a simple program that illustrates how to use the Date() class to get date and time information. Figure 3.6 shows the output result.

Screen capture depicting compilation, execution, and output results of DateExample.java in Command Prompt.

Figure 3.6: The compilation, execution, and output results of DateExample.java

The LocalDateTime() class, which belongs to the java.time.* package, provides the most useful date and time functions, such as getYear(), getMonth(), getDayofMonth(), and getDayofWeek(). You can also display the date and time using a certain format.

Example 3.18B is a simple program that illustrates how to use the LocalDateTime() class to get date and time information. Figure 3.7 shows the output result.

Screen capture depicting compilation, execution, and output results of DateExample2.java in Command Prompt.

Figure 3.7: The compilation, execution, and output results of DateExample2.java

The timer() class, also from the java.util package, provides the most accurate timer function, which you can set up using the schedule() method. Any class that you want to run in the timer must be a subclass of TimerEvent(), and it must override the run() method.

Example 3.19A is a simple timer program, which runs every 1000 ms (one second). It uses the System.currentTimeMillis() class to get the current time in milliseconds. From the output in Figure 3.8, you can see that the timer is accurate, with about 1 ms to 2 ms error. This is comparable to other programming languages.

Screen capture depicting compilation, execution, and output results of Timer1.java in Command Prompt.

Figure 3.8: The compilation, execution, and output results of Timer1.java

However, many people prefer using ScheduledExecutorService instead of Timer, as it's much more flexible—you don't have to worry about the timer blocking the rest of your program, and it handles exceptions better. Example 3.19B shows a simple ScheduledExecutorService example, which repeatedly executes a printing task, starting at 0 seconds and repeating every 10 ms.

Example 3.19C shows another version of the previous ScheduledExecutorService program. This time, the task is defined separately and uses a count variable to count how many times the task has been executed. It waits for five seconds first and then repeats the task every second. In addition to running the task repeatedly, you can run the task just once. To do so, just uncomment the last two lines, and comment out the ses.scheduleAtFixedRate() line.

Delay, or sleep, is also a useful function in many applications. In Java, you can use the Thread.sleep() class to implement a delay. Example 3.20 is a simple program that prints a message, delays for 5,000 milliseconds (5 seconds), and then prints another message. Figure 3.9 shows the output result.

Screen capture depicting compilation, execution, and output results of SleepExample.java in Command Prompt.

Figure 3.9: The compilation, execution, and output results of SleepExample.java

Example 3.21 is a variation of the previous program. By combining sleep() with the System.currentTimeMillis() class, you can display how many milliseconds the program has delayed or slept.

3.13 Executing System Commands

In Java, it is possible to run other applications in your operating system. This allows you to run, or to call, system programs and command-line programs from your Java program.

Example 3.22 is a simple program that illustrates how to use the Runtime.getRuntime().exec() class to get and execute the Windows Notepad.exe program. Figure 3.10 shows the output result.

Image described by caption and surrounding text.

Figure 3.10: The compilation and execution of Excute1.java (top) and the Notepad.exe program that results (bottom)

Example 3.23 shows another program that uses the Runtime.getRuntime().exec() class to execute the Windows command-line command dir C:\. The cmd.exe program is the Windows console (or terminal) command, and /C is the flag of cmd.exe, which says that the Windows console should carry out the command specified by String and then terminate. Figure 3.11 shows the output result.

Screen capture depicting compilation, execution, and output results of Execute2.java in Command Prompt.

Figure 3.11: The compilation, execution, and output results of Execute2.java

3.14 Packages and Programming on a Large Scale

For simple, small-scale Java applications, you can just write a few Java programs and put them in the save directory without needing to worry about directory structures. But for large-scale applications, a key part is to create reusable software components; therefore, directory structures become important. Java packages provide such a mechanism for organizing reusable Java classes and interfaces into different directory structures.

Example 3.24A and Example 3.24B show how to create a reusable Java class in a Java package and how to call this class. The reusable Java class is called SpecialFunctions.java (Example 3.24A). The program to test this special function is called SFTest1.java (Example 3.24B). To create a reusable Java class in a package, you need these two steps:

  1. Define a public class, like so:
     public class SpecialFunctions
  2. Define a package name, like so:
     package biz.biox

The package name also specifies the subdirectory that this Java class must belong to. In this example, if the current directory is H:ProjectA, then SpecialFunctions.java must be in H:ProjectAiziox, and the SFTest1.java file must be in the H:ProjectA directory, as shown here:

     H:ProjectA
     |----------- SFTest1.java
     |---------->iz
     |-------------->iox
     |-------------------- SpecialFunctions.java
 

To compile the SpecialFunctions.java program from a current directory of H:ProjectA, enter the following command, as shown in Figure 3.12:

Screen capture depicting compilation of SpecialFunctions.java in the biz.biox package in Command Prompt.

Figure 3.12: The compilation of SpecialFunctions.java in the biz.biox package

H:ProjectA> javac .izioxSpecialFunctions.java

SFTest1.java in Example 3.24B shows how to use the SpecialFunctions.java class in the biz.biox package. Make sure to add the following line at the beginning of the file:

import biz.biox.SpecialFunctions;

Type in the following commands to compile and run the SFTest1.java program. Figure 3.13 shows the output results.

Screen capture depicting output from SFTest1.java in Command Prompt.

Figure 3.13: The output from SFTest1.java

H:ProjectA> javac SFTest1.java
H:ProjectA> java  SFTest1

3.15 Software Engineering

Writing software is like making commercial products—quality is absolutely paramount. But how can you guarantee software quality? The answer is through software engineering. This concept means you should develop your software using a systematic engineering approach and write your code in a good programming style.

Using a systematic engineering approach means you should not simply go to your computer and start typing in code whenever you want to write a program. Instead, you should always follow the software development cycle; that is, you should design first and then write the code.

Good programming style means the software source code should be well structured, with correct indentations and sufficient comments. You also need to give your classes, methods, and variables meaningful names. Writing programs in good programming style will make programs easy to manage, modify, and debug, as well as less error-prone.

3.15.1 The Software Development Cycle

To develop high-quality software, you must follow the standard development cycle, shown here:

  • Step 1: Specify.
  • Step 2: Design.
  • Step 3: Implement (or code).
  • Step 4: Test and debug.
  • Step 5: Document.
  • Step 6: Go back to step 1 for the next version of software.

I cannot emphasize enough the importance of the specification and design steps in software development. You should always do a proper software specification and design first, before you start coding. Specification defines what your software is going to do, and it should be simple, precise, complete, and unambiguous. Design specifies how your software is going to do what it is supposed to do. The most commonly used design tools are flowcharts and pseudocode (or structured language).

For example, the Java code in Example 3.24B can be expressed as the following pseudocode. Pseudocode doesn't need to have a predefined syntax but should be in plain English with common programming constructs. It should also be generic and not specific to any languages.

Begin
    Define double x
    Create a SpecialFunctions object sf 
    Print the heading: "Number  Erf  Erfc ExpErfc"
    For each integer i in 1 to 10:
       x=(i-5)/10.0;
       Print x, sf.erf(x), sf.erfc(x), sf.ExpErfc(x)
    End of loop
End

For more details about flowcharts and pseudocode, please visit these web sites:

https://www.tutorialspoint.com/programming_methodologies/programming_methodologies_flowchart_elements.htm

https://www.programiz.com/article/flowchart-programming

https://www.go4expert.com/articles/pseudocode-tutorial-basics-t25593/

Once the software has been developed, documentation—which is often ignored—is an important step toward making it understandable to others, as well as easy to maintain. Luckily, Java provides a useful tool called javadoc, which can generate Java documents directly from your Java code. The documents generated by javadoc are in HTML format, so they can be easily put on a web site. As mentioned earlier, the elements of good coding style you will look at next—indentation, commenting, and following standard naming conventions—are also forms of documentation that contribute to code readability and ease of maintenance.

3.15.2 Indentation

Although indentation has no effect on the compilation and execution of the program, it is an important way to make the source code understandable to both yourself and others. Example 3.25A shows a Java program (the same as Hello1.java in Example 3.6) with correct indentation, and Example 3.25B shows poor or no indentation. You can imagine a program with hundreds of lines and nested loops; it would be difficult to understand the structure without proper indentation. Therefore, I strongly recommend that when writing a program, big or small, you should always use indentation to show the logical structure of your code and use it consistently throughout the program.

3.15.3 Comments

Like indentation, comments are also important in source code even though they do not affect the compilation and execution of the program. Sufficient comments can make a program much more understandable, especially for others, which is essential in a team project. Like C/C++, Java supports both single-line comments (//) and multiple-line comments, grouped by (/*) and (*/). Example 3.26 shows the Hello1.java program again, but with comments.

Note that for multiple-line comments, there is no difference between

/*
 This is a multiple line 
 comment
*/

and

/**
 * This is a multiple line 
 * comment
 */

However, because the javadoc tool can recognize comments beginning with /**, we recommend using /** and */comments at least for classes and methods, as they will be automatically used to generate program documents by the javadoc tool. You can find more information about Java documentation and the javadoc tool in Appendix A.

3.15.4 Naming Conventions

Giving sensible names to your classes, methods, and variables is important, especially when there are tens and hundreds of them. The most popular naming convention in many programming languages is Hungarian notation, which simply says that any variable name must have a prefix and suffix. The prefix should identify the type of the variable, and the suffix should provide its meaning. Normally the prefix is in lowercase, and the suffix is a string of words, without spaces, with only the first letter of each word capitalized. For example, a double-type variable that stored the ambient temperature could be named any of the following:

doubleAmbientTemperature 
dblAmbientTemperature 
dAmbientT

The following are some more examples of variable names:

strPlotTitle          //A String variable that stores the plot title
bolDeviceOn           //A boolean variable that stores a true or false value for 
                      //representing the on/off status of a device
intNumberOfPoints     //An integer variable that stores the total number of 
                      //points

Although you probably do not need prefixes for class names and method names, you do need to make them meaningful; and it is better to follow the Java naming convention for classes and methods. According to this convention, the Java class name should be a collection of words without spaces, with the first letter of each word capitalized; the Java method name should also be a collection of words without spaces, with the first letter of each word except the first capitalized. Here's an example:

getCurrentDirectory()      //A method that returns the current directory
setCurrentDirectory()      //A method that sets the current directory
toString()                 //A method that returns a string representing                              the object
FileReader()               //A class that reads character files
FileWriter()               //A class that writes character files

You do not need to follow Hungarian notation, as Java is already strongly typed. But whatever naming convention you use, you should be consistent throughout your programs.

For more details about Java code conventions, please visit these web sites:

https://www.oracle.com/technetwork/java/codeconventions-135099.html

http://gee.cs.oswego.edu/dl/html/javaCodingStd.html

https://google.github.io/styleguide/javaguide.html

3.16 Deploying Java Applications

For Linux/Unix users, there may seem nothing wrong with compiling and running Java programs through the text mode terminal or console, as you've done so far. But for Windows users, who have been used to graphical interfaces and a mouse, opening a text mode Windows command prompt to type in commands such as java HelloWorld to run a Java program is not very appealing. Therefore, in this section you will first look at several different ways of creating an executable Java program, which can be run with just a double mouse click. Then you will see how to deploy Java applications.

3.16.1 Using a Windows Batch File

The simplest way to compile and run Java code is to use a Windows batch file. You can use Notepad++ or any other text editor to create the batch file. Figure 3.14 shows an example of creating a Windows batch file called Oop1run.bat to compile and run the previous Oop1.java program. It has three lines of commands: the first line is to compile the code, the second line is to run the code, and the third line, the pause command, is to hold the screen so that you can observe the results. Once the batch file is created, all you need to do is double-click the file to compile and run the program, as shown in Figure 3.15. This approach is simple, quick, and efficient.

Image described by caption and surrounding text.

Figure 3.14: Create a Windows batch file using Notepad++ (top) and save it as a batch file called Oop1run.bat (bottom).

Screen capture depicting Windows batch file Oop1run.bat selected in Windows Explorer with a cmd.exe window on top to compile and run the Oop1.java program.

Figure 3.15: Double-click to execute the Windows batch file Oop1run.bat to compile and run the Oop1.java program.

3.16.2 Using an Executable JAR File

Java technologies support the concept of executable JAR files, which means you can create a JAR (which is a Java archive, the equivalent of a zipped file) that can run when it's double-clicked.

To create an executable JAR file, you must first create a mainClass manifest file, which contains two lines specifying where mainClass is to be found in the JAR file. In this example, the mainClass file is named Oop1.mf, and the following is its content:

Main-Class: Oop1
Class-Path: Oop1.jar

Now you can create an executable JAR file by typing in the following command in a Windows command prompt window:

jar cmf Oop1.mf Oop1.jar *.class

This combines all the Java classes (*.class) to create an executable JAR file called Oop1.jar.

To run this JAR file, just type the following:

java -jar Oop1.jar

You can also double-click the Oop1.jar file to run it. Figure 3.16 shows the content of the Oop1.mf file and the corresponding Oop1.jar file.

Image described by caption and surrounding text.

Figure 3.16: The content of the Oop1.mf file and corresponding Oop1.jar file

3.16.3 Using Microsoft Visual Studio

You can also use Microsoft Visual Studio to write a simple Windows command prompt program to compile and run Java programs using system calls. Microsoft Visual Studio is a popular software development tool in the industry, and you can use it to create an installation package easily. Figure 3.17 shows an example of creating a Microsoft Visual Studio C# program to use the System.Diagnostic.Process.Start() class to execute the javac Oop1.java command to compile the Java program and to execute the java Oop1 command to run the Java program.

Screen captures depicting Microsoft Visual Studio C# program with Create Application selected in New Project window (top) and Java command for compilation and execution (bottom).

Figure 3.17: The C# program to execute the Java command for compilation and execution

3.16.4 Java Application Installations

When you are developing commercial Java applications or you need to give your programs to others to use, you will need to use dedicated professional software to create an installation package to deploy your Java applications.

Install Creator 2 is a simple, user-friendly, and yet powerful professional software deployment and installation tool from ClickTeam. It offers a range of standard installation options, such as specifying the version to install, languages, software registration code, and installation directory, and it can produce a colorful installation process. With Install Creator 2, you can also specify a range of installation-related operations, such as configuring the main executable program; copying the DLL, EXE, and ActiveX files to system directories; registering the DLL and other elements to the Windows Registry; installing device drivers; and creating new entries in the Windows INI and Registry files. For more details, visit this site:

https://www.clickteam.com/install-creator-2

Advanced Installer is another Windows Installer authoring tool. It offers a friendly and easy-to-use graphical user interface for creating and maintaining installation packages (EXE, MSI, and so on) based on Windows Installer technology. For more details, visit this site:

https://www.advancedinstaller.com/

Install4J is an application from EJ technologies. Install4J is a powerful multiplatform Java installer builder that generates native installers and application launchers for Java applications. For more detail, visit this site:

https://www.ej-technologies.com/products/install4j/overview.html

Oracle also has a tutorial on how to deploy Java; see the following for details:

https://docs.oracle.com/javase/tutorial/deployment/selfContainedApps/index.html

3.17 Summary

This chapter provided a basic introduction to Java programming. Topics included variables, operators, reserved words, input and output, loops and selections, arrays, matrices and vectors, file read and write, object-oriented programming, multithreading, methods, dates and times, system commands, software engineering, and deployment.

This chapter lays the foundation for the coming chapters; it is important especially when you are new to Java programming. The variables, operators, and control structures such as loops and selections are the fundamental building blocks of the Java programming language. You will see them used throughout the book. Arrays and lists are useful for storing complex user data. The file read and write tools allow programs to read information from a file and save information into a file. OOP is one of the main features of the Java language, and all the example programs in this book use OOP in one way or another. Multithreaded programming is also crucial for many networking applications, as different threads are needed to handle different connections. It is good practice to develop Java software programs using a systematic, engineering approach. If you want to distribute your Java program to others, it is best to package your program in an installation package.

3.18 Chapter Review Questions

Q3.1.What are the standard input and output? Which Java classes deal with standard input, and which classes deal with standard output?
Q3.2.Which eight primitive variable types does Java support?
Q3.3.What is a constant? What is a VAR type variable?
Q3.4.What is the difference between String and StringBuffer?
Q3.5.What is ASCII?
Q3.6.What operators does Java support?
Q3.7.How many reserved words are there in Java?
Q3.8.What are loops and selections?
Q3.9.What is the difference between a Java array and a Java vector?
Q3.10.How do you read and write text files in Java?
Q3.11.What are the advantages of object-oriented programming?
Q3.12.What is the life cycle of a Java thread?
Q3.13.What is the software development cycle?
Q3.14.How do you create an executable Java program?
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset