COMP6700: Lectures Block 1

Alexei B Khorev

February-March 2013

Who is involved

A man (lecturer) Java (technology) Students

Alexei Khorev

Duke (creature)

Young (at heart) men and women

mon_mag

duke

students

Room N214, Bld. 108

Bred by Sun Corp, now feral

They still exist. Thanks!

Important Links

Topics of the block 1

Doing a university course can be fun, but it should not be a walkover!

What we will do in this part:

  1. Introduction to the course

  2. Java’s story

  3. Java as a language; Java SDK commands

  4. Program structure

  5. First Java Program

  6. Types and values (literals)

  7. Variables and operators; String s and arrays

  8. Expressions and statements, methods and return

  9. Control structures

  10. Top-down design, algorithms

About the course

What is Java?

  1. A programming language : Modern OO language, mature (18 years old), very well supported (extensive, often high quality libraries), actively developed (8 major releases; the last — Java SE 7 — has been finally released in 2011, the next is expected later this year, big deal…)
  2. A software developing platform (language + SDK + API): Includes the language itself, libraries for creating all major types of software artefacts: Graphical User Interfaces(GUI), Networking, Database Interactions, Web Applications, Encryption, Text Processing and so on, and necessary tools for compiling, optimising, testing, transforming and integrating your code into working applications.
  3. A software deploying and runtime environment, JVM: Java HotSpot Virtual Machine is the core of Java software platform: everything — compiling, debugging, executing etc — is done by the JVM.
  4. Java philosophy : Get started quickly, write less code, write better code, develop programs quicker, avoid platform dependencies, “write once, run anywhere”, distribute easier
  5. Java Hype (see previous item)
  6. Java™ (the trade mark)

In 2009 Sun was bought by Oracle

… and Java is going to change, but how?

Rest in peace, dear friend
Sun + Oracle = Snorcle?

(you wish, Gosling!)

sun_rip

snorkel

Java: huge, big, small, tiny… and defunct

Different Java for different hardware platforms:

Java current position in the industry

According to TIOBE latest data:

image

Long-term trends

image

Tools of Java programming — 1

A program is just a text with instructions. How to make computer to execute them? And how best to write programs? Use tools — special applications to create software.

Tools of Java programming — 2

Java Software Development Kit, SDK

The Java programming environment includes a set of programs (tools), which is needed by everyone who wants to create software in Java. The list of these tools is ever growing (Java 1.5 had 36; Java 1.6 has half a dozen more). The most important SDK tools are the following:

Types of programming language

(Wiki says): “A programming language is a language used to write computer programs, which involve a computer performing some kind of computation or algorithm and possibly control external devices (printers, censors, robots, etc)”. Several hundred programming languages exist. Why so many? How do they differ apart from syntax features (which may or may not be accidental)? There are several programming paradigms and styles:

Modern Java is procedural and object-oriented language. It does not have features of a functional programming language (but wait till Java 8).

Java — Compiled or interpreted?

A language definition is not enough. To be used in practice a language must be implemented (realised, adopted for creating software). Depending on implementation, a programming language can be

Java is a hybrid : javac produces a bytecode, which is like an assembly code, but architecture neutral. Java code compiled on a Linux machine will run on a Windows machine without recompilation (was important for the applet technology — the server could send a required applet which the client browser could execute immediately). The Java interpreter (java command) executes Java bytecode. The same bytecode runs on any platform if it has a JVM. Some compilers can produce a binary code, which is faster but platform specific, like any binary code; modern JVMs, like Sun’s Java HotSpot are very efficient, and such translation into machine-specific code — Just in Time Compilation — are rarely needed. Such combination of compilation and interpretation is what distinguishes Java from other common languages.

Is Java good for modern age?

Java provides support for creating robust distributed systems. The design principles which Java strives to implement are:

What is a computer program?

Various definitions of computation exist (including very abstract and powerful, namely using λ-calculus by A. Church). We shall adopt a simple one: A computation is a dynamical process, and a program is a definition of such process.

Create stars, planets, Earth, plants, animals, humans… name them… define how they behave (move, interact, change)… set them loose, let them try and repeat whatever they need to do… until you decide that the time is up (or a “catastrophe” ensues).

What describes a process?

Computation is (almost) a physical process

A computation is a dynamical process, and a program is a definition of such process. Compare it to a mechanical phenomenon of stone throwing, or a chemical reaction, when the initial state of the agents involved is given, and the evolution is determined by the corresponding laws (Newton’s laws, etc). But! In a computer program you can define your own laws (following some rules, though)!

Let's calculate

Let’s calculate! (When Creator strikes the ball, the main method is called, and execution begins.)

The structure of a computer program

“All programming languages have building blocks for description of data and processes or transformations applied to them” (Wiki).

That’s how my favourite Java text “The Java Programming Language” characterises a program text:

What can be wrong with a computer program

The path from an empty file to a correctly functioning program can tortious. Things can go wrong, and they can even go wrong when it appears the thing are right. Remember, a program code is aimed for human eyes as much as for machine.

At the beginning, your programs inevitably will be:

Program and its execution

A (single-threaded) computer program is a sequence of instructions (statements) which are executed sequentially, one after another when the program is run. Many modern programs are multi-threaded. Their structure is (significantly) more complex. We shall have a glimpse of multi-threaded programming in Block-7 (time permitting).

Program as text

Program as execution

prog_text

prog_exec

Before making first steps

Essential elements of Java code

Loosely speaking, the world of a Java program is populated with:

We shall start with the bare essentials: studying the syntax and semantics of the core language. We will type code into an editor, compile it and run the byte code interpreter. Our simple programs will read and write text to a terminal (shell) window (or, using simple pop-up windows).

The first Java program — 1

After that the program will exit (terminate).

The first Java program — 2, code anatomy

The first Java program — 3, code anatomy (cont.)

The first Java program — 4, code anatomy (concl.)

/** The very first Java program with added comments
 *  @author abx
 *  @version 1.0 */
public class FirstSample { 
    /* we need a main method to run it as a standalone app */
    public static void main( String[] args ) {
        // to say "G'day mate" is more authentic
        System.out.println( "Hello World" );
    }
}

The first Java program — 5

What we can add to the code without changing its behaviour :

A very important part of every code in every language are comments. They are totally irrelevant to the compiler, but they mean a great deal to a human who writes and reads the code. Comments do not affect the executable code at all but can increase the readability of the source. You can add as many comments as you like but too many comments can make the code unreadable as well. Look at good code examples to get a feel for appropriate comments. There are three kind of comments in a Java code :

Block and Javadoc comments do not nest!

Try adding some comments of these kinds to the above code — neither compilation, nor execution should change.

Types and Literals — 1

Computer programs operate on values, which are sequences of bits. These values can be represented as symbols (variables or constants), or as literals. Symbols must be declared with a type. The type determines the number of bits representing a given symbol in memory and how these bits are interpreted (its value). There are primitive types and references. Each type has literals (set of constant values of this type). The only literal of reference type is null (it represents an invalid or uncreated object). Primitive type literals are various.

Java is a strongly typed language — every symbol must have a declared type (such as the String array args in main).

boolean (1-bit, true/false) byte (8-bit signed) char (16-bit unicode)
short (16-bit signed) int (32-bit signed) float (32-bit FP numbers)
long (64-bit signed) double (64-bit FP numbers)
Variables of different type store either different types of data or a different range of possible values. byte, short, integer and long are signed (their range includes negative values from -2bitsize -1 to +2bitsize -1 -1 (one bit is used for the sign, +/-). For instance, the int values change from -2,147,483,648 to +2,147,483,647.

image

Types and Literals — 2

public class PrintNumber { // usage of the primitive type variables
  public static void main( String[] args ) { 
      int i = 90000; short s = 20000; // one can have 2 statements on one line
      byte b = -100; double d = 1.5; // but normally it's not good for readability
      long l = 4000000000L; // note the `L` here
      float f = 1.5f; // note the f here
      char c = 'a'; boolean bn = true;
      int i2; i2 = -90000; // delayed initialisation
      System.out.println( "The value of s is " + s ); // display the value
   } 
}

Floating point numbers

floats and doubles (the real numbers) have mantissa and exponent parts, according to “scientific” representations: x = α * 10β. The same value can be given different representation by consistently changing the mantissa (α) and the exponent (β) — “the point can float”. Storage for the values of mantissa, exponents and the sign is allocated separately.

Types and Literals — 3

Java 7 has introduced enhanced syntax for numeric literals to make it more readable for humans.

These new features include:

Type compatibility

A variable of one type is often assigned a value of a different type: var1=var2 (var1 is of type1, var2 is of type2). This is a type conversion: type2→type1. Java is a strongly typed language — it checks for type compatibility at compile time in almost all cases — all “fishy” assignments are forbidden. To allow assignment of types with compatibility only known at run time, or to explicitly force conversion for primitive types that would otherwise loose range (double→float), Java provides cast operator: var1 = (type1)var2;

Type conversions and casts — 1

Hierarchy of widening and narrowing conversions

  • a → b means that a variable of the type b is assigned a value which is held by a variable of the type a:
  • Widening represents an implicit type conversion and does not require the cast (because value doesn’t change)
  • narrowing needs the explicit cast (because the value can change of assignment).

cast_ladder

Conversion and round-off effects

If we declare double d = 1.3237 and int i = 10, then d * i, if assigned to a double, its value is 13.237, but if assigned to an int, the value will be 13, and if d is first converted to int, (int) d, the product (int)d * i is 10. To converted float to int:

  1. use an explicit cast, (int)x, this truncates the fractional part of x, OR
  2. use proper rounding, call the method Math.round(x) from the Math class

Type conversions and casts — 2

Casts (including the effect of losing the fractional parts of floats) are illustrated by the following code:

public class ConversionTest {
  public static void main(String[] args) {
    int i1 = 10; 
    float f1 = i1; 
    double d1 = i1; 
    double d2 = 1.5;
    float f2 = (float) d2; 
    int i2 = (int) d2;
    // call print method to display the values
  }
}   

Add printing statements for all the variables involved, compile and run this code to see the values generated by conversion (alternatively, use DrJava’s interaction pane).

Rounding and Finite precision

What do you get when you print the value of i2 ? It turns out that roundoff errors are a fact of life with floating point numbers:

double f = 4.35; int n = (int) ( 100 * f );
System.out.println( n ); // prints 434: Wrong!

What’s wrong? In the binary number system, there is no exact representation for the number 4.35, it is actually stored as 4.349999999... so 100 * f = 434.99999... (etc) and when cast to an integer the fractional component is dropped (not rounded!) thus n = 434. The proper way to do rounding is to use a function call from the Math class:

double f = 4.35; int n = (int)Math.round( 100 * f );
System.out.println( n ); // prints 435! (Good)

Characters

Characters are represented by primitive char s. Strings are not characters — strings are instances of the class String. But it may help to think of them as sequences of characters (which is what they are in term of values). Character literals appear between single quotes: 'a', string literals appear between double quotes: "a" ('a' and "a" are literal values of different type!). The character literals can be expressed either by a single symbol, or by an escape sequence inside the pair of single quotes, including an octal character constant (\ddd, where each d is one of 0-7, but the code cannot exceed \377).

char literals are what you can find in the Unicode character table (used to be — and still is for C — the plain old ASCII with 128 entries; not for Java!): characters (Latin and other scripts), digits and many special symbols. Any valid Unicode character can be represented using ASCII symbols only with an escape sequence \uxxxx, where x is a hexadecimal digit (0,…,F). The number of u symbols can be more than one. (BTW, the Unicode character mapping escape sequence can be used anywhere inside Java code as a part of an identifier, not only in char and String literals). Here is a portability problem for Java: despite you can manipulate all Unicode characters in your code, you may not be able display them because many terminal and other output devices are yet to adopt the Unicode standard. Some special ASCII characters: \n = \u000A (new line), \t = \u0009 (tab), \\ = \u005C (backslash) etc.

Two chars can be added: their code values are added, and this int value must be cast back to char: char c = (char)(a + b). Study these programs:

Strings

Character strings are implemented as instances of the class String. All string literals in Java programs, such as “abc”, are implemented as instances of this class. Strings are immutable (constant) — their values cannot be changed after creation. (StringBuffer objects support mutable strings). Because String objects are immutable they can be shared.

String str1 = "abc";
char data[] = {'a', 'b', 'c'}; 
String str2 = new String(data);
System.out.println(str1.equals(str2)); 
// should print "true", the objects str1 and str2 are identical
// But, str1 and str1 references were independently assigned, therefore
System.out.println(str1 == str2); // should print "false" 

More examples of how strings can be used:

System.out.println("abc"); String cde = "cde";
System.out.println("abc" + cde); String c = "abc".substring(2,3);
String d = cde.substring(1, 2);

The String class includes methods for examining individual characters of the sequence, for comparing strings, for searching strings, for extracting substrings, and for creating a copy of a string with all characters translated to uppercase or to lowercase, etc (string operations are important, and String is a large class).

String literals and String pool

Three strings defined with the same content: What is the difference?

String s1 = "abc";             
String s2 = new String("abc"); 
String s3 = "abc";             

Strings (as other same type objects) are compared by state (content) and by reference. Because of same content: s1.equals(s2) is true. Yet s1 and s2 are different objects: s1 == s2 is false. Why then s1 == s3 is true? When a string is initialised to a literal, it’s stored in the literals’ pool (in the same part of memory where primitives are stored). String’ pool literals are unique. Strings from the literals’ pool can be content compared by their references; it is much faster then calling s1.equals(s3). The strings which are created with new operator are references to the proper objects (they are placed in the heap, with all other objects).

One way to make a faster program with a lots of string operations is to avoid string creations by using one (or few) StringBuffer object, manipulate its content as required, and create a String object at the end. To get additional performance boost (in case of many “text comparing operations”), internalise every string:

str = str.intern(); // str variables is recycled

returns a string from the literals’ pool which has the same content as str.

String’s pool of literals crosses class and package boundaries. But different JVMs (my guess) maintain their own pools (DrJava flaw?!)

Arrays

An array is an ordered collection of elements indexed by an integer starting from 0 (first element) up to length - 1. All elements have the same type. The declaration and instantiations can be done differently:

//standard instantiation with elements set to default values
int[] ia = new int[3]; 
// creation of an array with initialisation of its element values (no new!)
int[] ia = { 2, 3, 5, 7, 11, 13 };
 // next C-like syntax is also legal, but don't do it!
int ia[] = { 2, 3, 5, 7, 11, 13 }; 
// as above, but note absence of the array size! (array size is inferred)
int[] ia =  new int[] { 17, 19, 23, 29, 31, }; // last comma is optional

Dimension of the array variables in the type declaration is omitted. The symbol new is the creation operator (used when an object is created). When it’s called, an array is instantiated (ie, an array object is created) and its dimension is set. As every reference, ia can be later assigned to a different array of a different size (example ArrayTest.java).

Multidimensional (arrays of) arrays

Arrays of arrays, which are standard one-dimensional arrays, each element of which is an array itself, are supported in Java. This is an examples of how a matrix (two-dimensional array) can be declared and used:

float[][] mx = new float[3][3]; 
// initialises every element of mx
setupMatrix(mx); 
// loop through rows
for (int row = 0; row < mx.length; row++) { 
   // loop through columns
   for (int col = 0; col < mx[row].length; col++) 
      // prints every element in the row
      System.out.print(mx[row][col] + '\t'); 
      // print new line after a row is printed
      System.out.println(); 
} 

On initialising, only the overall size of the arrays must be specified, therefore, only the number of leftmost indices must be provided. The inner array sizes can be initialised later, and can, in fact, be of different size:

float[][] mx = new float[4][];
for (int row = 0; row < mx.length; row++) { 
   mx[row] = new float[row + 1]; // (row + 1) is the size of inner 1D array

Variables — 1

Declarations, initialisation, assignment

Variables of char, int, double, String and an array type, discussed above, are just names which determine storage locations, “boxes” in which one can “place” a value of the right type (can fill in), then swapped with another similar typed value and so on (assign and reassign a value of the correct type). Variables can appear as: class fields, local variables inside a block of code, and method parameters.

Variables are introduced as program’s entities through declaration:

[@annotation-type] [modifier]* type identifier

where (we skip the @annotation here) modifiers can be optional and possible few (like private, static, or final), type is absolutely essential (it determines the size of allocated memory location and behaviour supported by the declared entity), and identifier is the actual name of this location. The declaration can be accompanied by initialisation (assigning a value for the first time), or the initialisation can be performed later in the code. The variables of the same type can be declared in a comma separated list, but the variables of different type must be declared in separate statements.

float x, y; // declared but not initialised
int z, u = 2; // two declared, one initialised

Variables — 2

A variables must be initialised before it can be used (except for formal method parameters). Once a variable is initialised, its value can be used in expressions or assignments.

int x; //not initialised, cannot be used
int y = 10;
x = y * y; // now x has a value
int z = x; // now x can be used and given a new value

Field variables are declared inside the class definition. Non-static fields exist during the life of an object to which they belong. If not initialised explicitly, they are given the default values when the object is created.

Local or block variables are declared anywhere inside the block (method’s body is a typical example of a block); unlike field variables, local variables must be initialised explicitly before they can be used; they cease to exist when the flow of control (the computation path which the program execution goes through) reaches the end of the block or method body in which they were declared. Local variables can have only one modifier, final.

All variables can change their values during their lifetime unless they were declared final (they are essentially constants). The final variables of the reference type (object’s name) are called immutable. String instances are such immutable objects — any would be manipulation with the String object content actually involves creation of a new String object with modified value.

Variables — 3

An attempt to change the value of a final variable will result in a compile error:

final int x; // if not initialised during the declaration, 
             // the final variable is a blank constant
x = 2; // ok, now the constant x is given a value
x = 4; // can't do!

final variables are normally initialised right where they are declared. It is legal to postpone assignment (which must precede the use), but not recommended (all modern IDEs issue warning in such case). Blank finals are mostly used if the field value is determined by the constructor arguments:

class Student {
    //below is the Student class constructor
    final String name; 
    Student(String name) { this.name = name; }
}

Parameter variables are the parameters declared in methods, constructors or catch part of the try-catch-finally blocks. A parameter declaration includes an optional modifier (an @annotation or final), type and a single identifier. During the method declaration or its invocation in the bodies of other methods, the method variable is a formal parameter (it has no value), but during the actual invocation of the method, it is an actual parameter (it must have value). Parameter variables cease to exist when the method body in which they appear completes (by reaching its logical end, or terminating with error).

Operators — 1, primitive operands

Variables are subject to various operations which use their values and produce new ones. The program operations which involve primitive type variables and Strings feature the variables themselves and operator, much like mathematical operators which together with variables and parameters form a part of the mathematical expression. These are five binary operators which can be applied to a pair of any primitive numeric type variables (called operands)

and two unary operators

Operators — 2, details

Integer arithmetic is modular two’s complement arithmetic, ie, if the value exceeds the range of its type (int or long), it is reduced modulo the range (it wraps), so that the integer arithmetic never results in an overflow or an underflow.

Integer division truncates toward zero (5/2 = 2, and -5/2 = -2). Division and remainder obey this rule:

(x/y)*y + (x%y) == x // hence 5%2 = 1 and -5%2 = -1

Division (or remainder) by zero is illegal for integer arithmetic and throws ArithmeticException.

Character arithmetic is an integer arithmetic after the char is implicitly converted to int.

Floating point arithmetic is more lengthy subject (you may explore this subject yourself, or take COMP2300/6300 course).

String concatenation: The addition operator also applies to two String objects, returning a string with the joint content:

String s1 = "French Connection"; String s2 = " UK";
System.out.print("s1 + s2 = " + s1 + s2); 

Unlike for primitive types, the string “addition” is not commutative (ie, the result depends on the order of the two operands).

Operators — 3, Other operators

The difference between the bitwise and conditional operators: the latter “short-circuit”.

Operators — 4, object related

Objects (wait till Block 2 for more detail) need to be created and operated upon.

Object creation operator:

String str = new String("I love Perignon!");

The operator new allocates memory for a new object and return a reference to it

Method invocation: Objects represent chunks of data and operations which involve these data. These operations are carried out by invoking or calling a corresponding method by using the object reference o — the invocation operator is a dot which follows the object reference:

o.methodname(x); // x is a parameter passed to the method
System.out.println("I am confused"); // the object here is output stream "out"

Field access: Data associated with the object o (if allowed) can be read by using the same dot operator followed by the name which represent this data inside the object, so called field:

int age = o.age;
System.out.println("The object name is " + o.name);

Operators — 5, Bit manipulation operators

Unary bitwise operator (applies to ints):

Binary bitwise operators (also apply char) : perform their operation on each pair of bits in operands:

Bit shift operators (the bit pattern is given by the left-hand operand, and the number of positions to shift by the right-hand operand):

Operator Precedence

Precedence Type Operators
1. postfix operators [] . (params) expr++ expr--
2. unary operators ++expr --expr +expr -expr ~ !
3. creation or cast new (type)expr
4. multiplicative * / %
5. additive + -
6. shift << >> >>>
7. relational < > <= >= instanceof
8. equality == !=
9. bitwise AND &
10. bitwise exclusive OR ^
11. bitwise inclusive XOR |
12. conditional AND &&
13. conditional OR ||
14. conditional ternary ?:
15. assignment = += -= *= /= %= >>= <<= >>>= &= ^= |=

Getting input into program: command-line arguments

It is vital to control the data used and produced by a program, so called input-output, I/O. The simplest way to get your data into the program is to use the command-line arguments. They are picked up by those parameters args (the array of Strings) which are passed to the main:

public class ArgsTest {
   public static void main( String[] args ) {
      System.out.println("first arg = " + args[0]);
      System.out.println("second arg = " + args[1]);
      System.out.println("third arg = " + args[2].toUpperCase());
   }
}
output produced by 'java ArgsTest I love perignon'
first arg = I
second arg = love
third arg = PERIGNON    

args are Strings, but if you need to provide an integer (or float), the input string can be converted:

String s = "123"; int i = Integer.parseInt(s); 
String s = "-123.45"; double d = Double.parseDouble(s); 

Getting input into program: Scanner

The command-line arguments is a very rigid way to control input. It can be done only once, at the start of program execution.

Reading text from the terminal’s standard input (stdin, or console) provides a better way: It allows you to control the moment of execution when the program will receive input data. One convenient way to provide such input channel into the program, is to create a Scanner object to read form stdin stream (stdin is the object System.in, and stdout is System.out — standard input and standard output). Details will be discussed in Block 3.

import java.util.Scanner;

public class ConsoleTest {
   public static void main(String[] args) {
      Scanner sc = new Scanner(System.in);
      // the user is expected to type something and hit "return"    
      System.out.print("Enter some text: "); 
      String input = sc.next();
      System.out.println("You entered " + input + 
                ", it has " + input.length() + " characters");
   }
}

demo! (the code is available in ConsoleTest.java)

Getting input into program: simple GUI

A simple input from the user can be alternatively provided using a Graphical User Interface:

import javax.swing.JOptionPane; // to make JOptionPane available

public class GUITest {
   public static void main( String[] args ) {
      String input = JOptionPane.showInputDialog("The answer to Exam's Q5");  
      System.out.println( "Your answer to Q5 is " + input );
   }
}

joptionpane

Running the program:

abx% java GUITest
Your answer to Q5 is 42

Expressions

An expression consists of operators and their operands, which are evaluated to produce a result, which may be a single value, or a variable (“lvalue”), or nothing when the expression is an invocation of a method that is declared void (in which case, the result is implicit change in the computational environment, like a state of an object or something else). An expression may be simple (atomic), like a single variable, or complex — a sequence of subexpressions like method invocations, variable access, object creations, and combination of results of various subexpression involving arithmetic and other operators, more method invocations and variable access etc, etc, etc.

// simple (atomic) expressions
int x = 2;
int y = 2 * x;
double θ = Math.PI / 6;
// expressions with sub-expression
double d = Math.sqrt(Math.pow(Math.cos(θ),2) + Math.pow(Math.sin(θ),2));
boolean b = !(d == 1);
// really concocted one
// notice that the wite space between token does not matter
String str = new String((new Integer(x)).toString() + " + " + y + " = " +
            new String((new Scanner(System.in)).
                 next().equals("smart") ? "4" : "5"));

Note: the last statement above is completely gratuitous — do not write you code with unnecessary expression, break them into shorter ones and combine at the last step.

Expression practice

Which expressions are legal and what do they evaluate to?

3 << 2L - 1;
(3L << 2) -1;
10 < 12 == 6 > 17;
10 << 12 == 6 >> 17;
13.5e-1 % Float.POSITIVE_INFINITY;
Float.POSITIVE_INFINITY + Double.NEGATIVE_INFINITY;
Double.POSITIVE_INFINITY - Float.NEGATIVE_INFINITY;
0.0 / -0.0 == -0.0 / 0.0; // very important why the answer is what it is?
Integer.MAX_VALUE + Integer.MIN_VALUE;
Long.MAX_VALUE + 5;
(short) 5 * (byte)  10;
(i < 15) ? 1.72e3f : 0; // assuming int i is declared, but not initialised
i++ + i++ + --i;   // i = 3 before statement executed, what is i value after? 

(hint the precedence table on Operator Precedence_ slide can help; if perplexed, type them into a program, run and try to understand why the results are what they are)

Statements

Statements are what a program is built from — a sequence of expressions and declarations interleaved with control flow statements. Compared to a natural language, statements in a computer program correspond to sentences in an English (Chinese, Turkish, Martian,…) text. In Java, statements are terminated with semicolons (the semicolon itself is a statement, an empty one). The following colon-terminated expressions constitute statements:

Blocks

A block statement is a zero or more statements grouped inside the braces (curly brackets), { block-statement }. Whenever a single statement can be used, a block statement can be used too. The scope of variables declared inside a block is bounded by the delineating curly brackets. Blocks occur in:

class BlockDemo {
     public static void main(String[] args) {
          boolean condition = true;
          if (condition) { // begin block 1
               System.out.println("Condition is true.");
          } // end block 1
          else { // begin block 2
               System.out.println("Condition is false.");
          } // end block 2
     }
}

Methods and return key word — 1

A method is a separate piece of code which can be called from a main program or another method to achieve some well defined computational task. In Java, each method must be defined within the body of a class and each class can have several methods. Copies of the data from the calling program are passed down to a method in its argument list. In Java, each method returns a single value (the result) to the calling program. This returned value can be a value of a primitive type, or an object, including an array (discussed below). Methods can do all sorts of other things, such as writing to files or databases, and often they do not return any value at all — in this case the return type is given the special designation void.

The syntax of method definition

[@annotation-type] [modifier]* return-type method-name(argument list) {
   ... method-body (block statement) ....
   [return expr;]* // where expr type must be compatible with the method return-type
}

The method return-type can be a name of any primitive type, or a class name (standard Java API class or user’s defined), or void, in which case the method does not return any value. If a method has non-void return-type (it does return a value), its body must contain one or more return statements: return expression.

Unlike in some languages, in Java one cannot define a method inside a body of another method (this can be circumvented by using local inner classes).

Methods and return key word — 2

Modifiers of methods (just like modifiers of variables) determine their characteristics in terms of relationship with other parts of the program: can they be used by other classes (private, protected, public), can they change implementation (final), can they be called without creating an object of the class in which they are defined (static). The argument list is a comma separated list of parameter declarations surrounded by a pair of matching parentheses:

methodname(type1 name1, type2 name2, type3 name3, ...)

Method parameters are used to pass data to the method during the invocation, and they are used inside the method body.

public class SquareInt { // Calculates the squares of the numbers from 1 to 10   
   // main method
   public static void main(String[] args) {
      for (int i =1; i<=10; i++) {
         System.out.println(" number= " + i + " square= " + square(i));
      } 
   }
   // that other method   
   static int square(int x) { // static because is called by static (main)
      return x*x;
   }
}

Method benefits

Here is an example of a class which contains a method definition:

Well designed methods (and classes) reduce the amount of effort required on a large programming project. Their benefits include

Method invocations

If the object x is an instance of a class X, which contains methods doThis(), doThat(type-name param) and returnSmth(), where the last method is declared to return a value of the type return-type, then other parts of the program which are aware of the object x, can call these methods:

X x = new X(…);
type-name y = expr; // expr must have compatible type with type-name
return-type z;
... and z ...
x.doThis();
x.doThat(y);
z = x.returnSmth();

Depending on the type of the object of the X class, and the type of the client (the object from within which the method call is made) Y, the calling is possible if:

Methods: pass-by-reference or pass-by-value?

When methods with parameters are invoked, how the argument values are treated by the method during its execution?

pass-by-ref

pass-by-val

Pass-by-reference

Pass-by-value

The actual parameter is a reference to a value, The method affects the value if the actual parameter is modified by the method. (Happens for functions with pointers as parameters in C/C++). The actual parameter is a copy of the passed value. Even if the actual parameters is modified by the method, the original value is not affected.

Passing parameters in Java: both ways

Java uses pass-by-value rule, but its effect is different for primitive and reference types of parameter.

Primitive type

a value modified (squared) by the method square(i) in SquareInt.java has no relation to the value of i (the value used by the method does not live outside method’s body, and disappear after its execution). How to make use of the data calculated by the method? Make the method to return value: SquareIntV2.java.

Reference type

a copy of the value passed as an actual parameter is the alias of the same object! The method modifies the object all the same (side effect): SquareIntV3.java. The original reference cannot be modified (unlike for pass-by-reference). If a method returns a reference to an object which was constructed anew during the method execution, take care of the object components — are they new or recycled objects? A newly created object made of recycled components may open a possibility (which did not exist prior to the method call) to change the environment. (Swing library has design defects of such kind.)

As general principle — when designing a method try to make it cause as less changes in other variables of your program as possible. If a methods disturbs its environment, it can (against programmer’s knowledge, or intention, or both) break it, what will cause serious problems, and may result in execution failure. Such side-effects are dangerous. In older languages, functions with side-effects were a common thing (due to undisciplined use of global variables), creating difficult debugging problems. Ideally, methods should not change their surrounding at all; such methods are called side-effects free (or, pure). In functional programming all methods are pure (and FP have no variables, only values).

Control Flow: if-else

Often (almost always, and many times over) one needs to organise the program execution in different ways depending on the data. The selection expression, as it’s known, is the if-else statement with the syntax:

if (b) // b is a boolean expression
  statement_1;
else // this part is optional, it covers all logic alternatives to b, !b
  statement_2;

If the set of logical alternatives is more complex, then the extended form if-[else if]*-else is used:

if (b1) 
  statement_1;
else if (b2)
  statement_2;
   ...  
else if (bn)
  statement_n;
else // the remaining alternatives go — !(b1 ∨ ... ∨ bn)
  statement;

Statements statement_i can be one-liners or multi-line blocks ({…}). if-else statements can nest — if-else statement can be a part of a statement_i. You should not nest too deep, and to avoid the dangling else error (like in DanglingElses.java).

Control Flow: switch

If there are too many else-ifs in a situation when the boolean expression is an equality test for integer values, int_expr==valuei, i=1,2,…k,, the switch statement can be used:

int var = int_expr; //can be number type, or Enum (and String in JDK 7)
switch (var) {
  case value_1: //this is a statement label
    statement_1; // label is just a label, not a condition for execution!
    break; // to prevent execution of default_statement
  case value_2: case value3: ...
    statement_23; // multiple case labels may require the same statement 
    break;
  case value_k: // not all range of values needs to be explicitly checked
    statement_k;
  default: 
    default_statement; 
       // flow control jumps here if no matching case found. default is 
       // optional: in its absence, the entire switch statement is skipped
}

The case or default label does not force the break out of the switch, and it does not imply the end of execution of statements. The falling through can be avoided explicitly by break statement, or, if the switch is used inside a method which returns a value, by return. Some people advise not to use switch, but it may be useful for processing multiple command-line options. Traps of the control flow falling through in switches (unintentionally) exemplified in IfTest.java.

Control Flow: while and do-while

Need to repeat the same statement multiple times depending on a value of the boolean expression, and the number of repetitions isn’t known in advance (indeterminate loop)? Use either while loop

while (boolean-expr)// if boolean-expr evaluates to true, then execute
   statement;

or, to execute the statement once, no matter what, use do-while loop

do 
   statement;
while (boolean-expr);

statement (especially in do-while case) is almost always a block.

class WhileDemo {
     public static void main(String[] args){
          int count = 1;
          while (count < 11) {
               System.out.println("Count is: " + count);
               count++;
          }
     }
}

Control Flow: for

When the number of repetitions is known in advance (determinate loop), it can be controlled by a counter, most often, when the looping is done over a range of values, like an array elements, from beginning to end, usage of for loop is recommended:

for (init-expr; boolean-expr; incr-expr) // all expressions are optional
   statement; 

which is equivalent to

{ //mind this block !
  int-expr;
  while (boolean-expr) {
    statement;
    incr-expr; //  it often has that (in/dec)cremental form i++ / j--
  }
}

Java 1.5 has a simplified form of for loop, the so called for-each loop (though there is no such keyword):

for (ElementType var: objectOfCollectionType) 
   statement; 

Control Flow: break and continue

break is often important to control the exit from a loop or a block:

while (years <= 100) {
    balance += payment;
    double interest = balance * interestRate/100;
    balance += interest;
    if (balance >= goal) break; //at this moment, while loop is exited
    years++;
}
System.out.println("No of years = " + years);

continue: instead of breaking out of the loop, it only skips remaining statements in a current iteration and goes to the next one.

while (sum < goal) {
   String input = ...;
   n = Integer.parseInt(input);
   if (n < 0) continue;
   sum +=n;  // not executed if n<0
}
for (count=0; count < 100; count++) {
     String input = JOptionPane..........;
     n = Integer.parseInt(input);
     if (n < 0) continue; //jump to count++
     sum += n; // not executed if n < 0
}

Structured Programming and SE/SE principle

A version of break (and continue) with a label transfers the control flow to the statement immediately outside the labeled statement which can be a nested block (eg, a loop). Unlike an ordinary break which terminates the innermost block, a labeled break transfers the control flow to the statement immediately outside the labeled statement (one can escape a nested loops at once.)

label_one:
for (int i = 0; i < 99; i++) {
    ... break label_one ...
}

When labeled break and continue are used in multi-nested blocks, they violate the basic tenet of structured programming known as Single Entry — Single Exit principle:

Every block of code must have a single entry point and a single exit point. It should be impossible to enter or exit such a block in the middle. Entry is at the top, exit is at the bottom (like in the program text).

Labeled break allows you to exit deeply nested blocks (like double for-loop) from the middle. This is bad because the enclosing blocks (outer loop) may not know that their are being exited; if such outer blocks assume that they control their execution and exit (which is normal), this can lead to errors which will be hard to pinpoint. The example — Labeled.java .

Labeled break and continue considered harmful (if possible, avoid simple break and continue, too).

Control Flow: return

return statement terminates execution of a method and returns to the invoker.

double nonNegative(double val) {
   if (val < 0) return 0; //an int constant, but it's promoted to double
   else return val; // a double
}

Even if the methods does not return a value, a simple return; with no following expression will terminate execution of the method code (leaving whatever is left out of execution):

void reportIfNegative(double val) {
   if (val < 0) {
      System.out.println("It's negative, alright.");
      return; 
   }
   System.out.println("This will not seen if val < 0");
}

Syntax oddities — 1: increments

For primitive numerical types, the operators

+, -, *, / and %

can be combined with the assignment operator  =  when used to increment (decrement, multiply, divide,remainder) an old value :

value = value + delta; // is same as
value += delta;  // same for other -, *, /, %

In a particular case of incrementing (decrementing) by 1, commonly used in determinate for-loops, a more cryptic expression is used :

i = i + 1; // is identical to
i++; // similarly, i = i - 1 is identical to i--;

The expression i++ is post-incrementing (first evaluates then increments), the expression ++i is pre-incrementing (first increments then evaluates).

Quirks like ++i++ are syntax errors.

The + operator also works for strings (concatenation), so it’s also possible to use it like this:

String s1 = "French Connection "; String s2 = "UK"; s1 += s2; // s1 evaluates to …

Syntax oddities — 2, ternary operator ( ? : )

Often the content of the conditional if-else statement is assigning a value to the variable, depending on the state of the system. A familiar statement

if (wonAOFinal)
    prize = 1000000;
else
    prize = 500000;

can equivalently be written in a more succinct but expressive form :

prize = (wonAOFinal ? 1000000 : 500000);

The ? : operator has a value and can be used in an expression. The general form :

prize = (booleanExpr ? value1 : value2);

where value1 and value2 must have compatible types (if types are not the same, the return type depends on assignable (on LHS) type; automatic in-boxing and out-boxing can be carried out; in the case of incompatible references, the first common parent is returned, up to the Object type — brrrr! Error definitely occurs if the type is void).

In JDK 7: Elvis Operator helps to avoid testing for nullity of a reference used in assignment:

Value v = val1 ?: val2; // if val1 is null, val2 will be used to assign v

Varags

To write a method with an arbitrary number of parameters requires usage of an array. The new Java 1.5 feature variable arguments (varags), which allows an alternative way of dealing with such situation. The last parameter in a method (or constructor) can be declared as a sequence — finite but otherwise undetermined set of parameters — of a given type. The syntax :

public static R foo(T1 t1, T2 t2, T... t);

(only one varags is allowed which must be the last declared parameter) which is treated by the compiler as

public static R foo(T1 t1, T2 t2, T[] t);

the access and scope modifiers are arbitrary; T1, T2, T and R are parameters and return type. When the method foo() is called, the third parameter can be replaced by any number of actual parameters of the same type T, including no parameters, ie, passing no arguments is a legal (while the “old-fashioned” array form assumes that the last argument is always included, and it’s an array).

foo(a1, a2);
foo(a1, a2, v1);
foo(a1, a2, v1, v2,...,vn);

main can be also declared with varags : public static void main(String... args);

printf(): “March of Progress” (after Cay Hortsmann)

printf(String format, Object... args) // the syntax of printf() formatting function: 
System.out.printf("%s: %d, %s%n", name, idnum, address); // the usage 

1980: C

printf("%10.2f", x);

1988: C++

cout << setw(10) << setprecision(2) << showpoint << x;

1996: Java

java.text.NumberFormat formatter = java.text.NumberFormat.getNumberInstance(); 
formatter.setMinimumFractionDigits(2); 
formatter.setMaximumFractionDigits(2); 
String s = formatter.format(x); 
for (int i = s.length(); i < 10; i++) 
    System.out.print(' '); 
System.out.print(s);

2004: Java

System.out.printf("%10.2f", x);

2008 and beyond: Java.next

printf("%10.2f", x) // Scala and Groovy
println(f"$x%10.2f") // Scala 2.10

Method design: top-down approach

When a computing problem gets more complex, the ad hoc approach to solving it may not be possible. Therefore, you have to postpone coding and first design the solution.

The most often used design method is top-down approach:

  1. Formulate the problem (in plain language)
  2. Define the inputs and outputs
  3. Decompose the program into classes and their associated methods
  4. [ Design the algorithm that you intend to implement for each method using the so-called pseudo-code ]+
  5. Turn the pseudo-code into Java statements
  6. Test the resulting Java program

top-down

Abstraction, decomposition and step-wise refinement make the most essential abilities for writing computer programs, regardless of a language. So far, we have been learning syntax rules (and a few other things), but we hardly learnt how to apply those rules for creating programs! But how can you teach it?

Pseudo-code and structured programming — 1

The pseudo-code is a loose mixture of Java and English (or your native tongue). As you refine your design more and more, the “English” statements get replaced by Java’s. Your pseudo-code algorithms should clearly show the steps which are sequential, those which are conditional (if blocks) and those which are iteration (loops). Any algorithm can be composed of these three elements. A computer language which has these constructs is Turing-complete. Thinking about algorithms in this way is called structured programming (E. Dijkstra).

A step-wise refinement approach to a problem of finding roots. The problem: Find a point x inside the interval between points a and b, x∈[a,b], where the function f(x) turns zero; it is given that f(a) < 0 and f(b) > 0.

  • Top level: gist of the algorithm

    1. Evaluate f in the middle, choose that half of the original interval on which f changes its sign; continue until the interval shrinks to zero
  • Refinement: elaborate on essential steps

    1. Take a,b,f and precision ε as inputs
    2. Divide (a,b)-interval in the middle, and evaluate function f(m), m = (b-a)/2
    3. If f(m)=0 — Bingo! otherwise choose [a,m] or [m,b] on which f has opposite signs
    4. Repeat the above steps until the interval becomes shorter than ε

bisection

Pseudo-code and structured programming — 2

The next step in implementing the root-finding function — the pseudo-code:

Finally, the pseudo-code is converted into the language code (whatever the language is used for implementation). In Java, one cannot pass a function to a method as argument. We shall ignore the f argument, assuming that f(x) denotes an already defined function (method), and bisect calculates its root:

double bisect(final double a, final double b, final double e) {
   // complete this code as a exercise

}

Developing code using step-wise refinement — 1

This is a more mundane problem (used for one of the yesteryear assignments): Create a program which reads in a file containing a list of student names and IDs, then asks the user (“lecturer”) to enter marks which every student earned in a course, calculates final mark and grade for every student, and displays the results in different order (alphabetical, ranking etc).

  1. Input: a plain text data file with student (one per line) information, and user prompted input during execution

  2. Output: sorted lists of students, and program messages which guide the user

  3. Define a Student class, initialise a list structure to contain all student objects, open an input file for reading, read in line by line, determine names and ids, use them to create a student object, add it to the student list.

  4. Go through the list, prompt user to enter correct marks (this is an indefinite loop because the user should be given a choice of asking for help, he can enter a wrong value and the program must be able to correct him etc — the dialog must be robust against incorrect input). When all marks are collected, calculate final marks and grades.

  5. Sort the list in alphabetical or in ranking order.

  6. Prompt user to choose a student to correct marks; repeat until user terminates the program (this dialog must be equally robust).

  7. Save data in a file.

Developing code using step-wise refinement — 2

The program skeleton (high-level pseudocode expressed in Java) may look like this:

List students = new ArrayList<Student>(); // Define Student class separately
Scanner sc = new Scanner(infile);
while (sc.hasNextLine()) {
   studentData = sc.nextLine();
   student = new Student(studentData); // Define appropriate constructor in Student class
   students.add(student);
}
procureMarks(); // first dialog session with the user
process(students); // represent every method first by its stab
collectErrata(); // second dialog to collect corrections
saveData(outfile, students);

First, methods can be defined as stabs; stabs have empty body or single return statement. When the top-level structures are complete, implement every stab method as necessary.

Data-Driven Approach

Another approach to designing a complex program is a bottom-up, which is better known as data-driven. It is not full alternative — in real-life programming problems, the top-down and data-driven approaches do not exclude each other. They are often used simultaneously; problems appear when the two finally meet (glue layers are often necessary; the modern software bane is too many glue layers).

Data-driven programming makes strong separation between code (operations) and data structures on which the code acts. In design, the data are given priority, they are defined as the first step, after which the necessary operations follow. Change in logic of the program is achieved by editing the data structures, not the code. Transformation of data structures in the process of computation determine the program control flow (here is the difference between data-driven and object-oriented programming; also, if OO is also concerned with encapsulation, DD’s goal is to minimise writing the actual coding instructions; in this aspect, DD programming is related to functional programming).

Data-driven approach often involves automatic code generation (writing code using programs). DD programming with code generation is used to produce visual components of graphical user interfaces (applications which allow the user to interact with it through a set of graphical elements, so called widgets, and other means of receiving “physical” input). Java was late to adopt such technology — XAML for Microsoft Windows, Glade for X/Gnome; Qt had QtDesigner, and recently added QtQuick and QLM language; Apple uses this technology heavily in InterfaceBuilder to create GUI for MacOSX/iOS applications/apps. Java has finally caught up with them with JavaFX GUI library and tools. We shall learn about it later in the course.

Testing and Debugging

Three types of errors:

  1. compile-time errors — use Java compiler error messages to find and correct them (usually mere syntax errors)
  2. run-time errors — dangerous illegal ops; must be eliminated, some can be dealt with by Java exception
  3. logical errors (eg, dangling if-else and other kinds) — most insidious, eliminated by

    1. code review (code reading)
    2. testing (unit testing etc)
    3. formal verification (special tools, mathematical logic representation)

    Advice on how to avoid the logical errors:

    1. break long statements into smaller ones
    2. check the correct use of methods in API docs
    3. check balance of parentheses, expression type in mixed arithmetic, etc
    4. Static Code Analysis tools (like FindBugs for Java) analyse your code (or bytecode) against a standard list of errors without running the program; bugs, potential performance and other problems are highlighted with suggestions for fixing them (I may show a demo later!)
    5. some tools (Eclipse and other IDEs) help to spot many errors (eg, by marking them “red”)
    6. etc, etc etc — code, code, code — it comes with practice