PPJ 20230112
PPJ 20230112
Tomasz R. Werner
Rules: Points for the LAB: classes (tasks, activity) — 15 points, 3 tests — 3 × 25 = 75 points,
assignments (homework) — 10 points.
In order to pass the classes and be admitted to the exam, the sum must be at least 50.
Students with the score at least 75 do not need to take the exam and their final note will be calculated
as stated below.
For those who will take the exam, its result will be scaled to the range [0, 100]. A score below 40
means a failure. . . If the score for the exam is at least 40, the total score will be calculated as half of
the sum of the results obtained for the LAB and for the exam (i.e., max. will be 100).
The final note will be then claculated as follows:
Grades:
[50 − 60) ⇒ 3.0 [60 − 70) ⇒ 3.5 [70 − 75) ⇒ 4.0 [75 − 85) ⇒ 4.5 [85 − 100] ⇒ 5.0
ii
Contents
Page
1 General introduction . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 1
1.1 Computers and programming languages . .. . . . . . . . . . . . . . . . . 1
1.2 What is Java? . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 2
2 Compiling and running a Java program . . . .. . . . . . . . . . . . . . . . . 4
3 Types, variables, literals . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 6
3.1 Primitive types . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 6
3.1.1 Integral types . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 6
3.1.2 Floating point types . . . . . . . . .. . . . . . . . . . . . . . . . . 9
3.2 Object types . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 10
3.3 Variables and literals . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 10
3.4 Conversions . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 14
3.5 The stack and the heap . . . . . . . . . . .. . . . . . . . . . . . . . . . . 15
4 Quick introduction to I/O . . . . . . . . . . .. . . . . . . . . . . . . . . . . 16
5 Instructions and operators . . . . . . . . . . .. . . . . . . . . . . . . . . . . 18
5.1 Operators . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 18
5.1.1 Assignment operator . . . . . . . . .. . . . . . . . . . . . . . . . . 18
5.1.2 Arithmetic operators . . . . . . . . .. . . . . . . . . . . . . . . . . 19
5.1.3 Conditional and relational operators.. . . . . . . . . . . . . . . . . 21
5.1.4 Bit-wise operators . . . . . . . . ... . . . . . . . . . . . . . . . . 24
5.1.5 Casting operator . . . . . . . . . ... . . . . . . . . . . . . . . . . 26
5.1.6 Precedence and associativity . . . ... . . . . . . . . . . . . . . . . 26
5.2 Instructions . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . 29
5.2.1 Conditional statements . . . . . . ... . . . . . . . . . . . . . . . . 29
5.2.2 Switch statement and expression . . .. . . . . . . . . . . . . . . . . 30
5.2.3 Loops . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 35
6 Arrays . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . 43
6.1 Creating arrays . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 43
6.2 Arrays of references to objects . . . . . . .. . . . . . . . . . . . . . . . . 46
6.3 Multi-dimensional arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
7 Static functions . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . 51
8 Classes . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . 59
8.1 Basic concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
8.2 Classes and objects . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 59
8.3 Access to classes and their members . . . . . . . . . . . . . . . . . . . . . 62
8.4 Constructors and methods . . . . . . . . . . . . . . . . . . . . . . . . . . 63
8.5 Static members . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
8.6 Initializing blocks . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . 72
8.7 Singleton classes . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 75
9 Basic data structures . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 77
9.1 Singly linked lists . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . 77
9.2 Stacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
9.3 Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
10 Strings and StringBuilders . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
10.1 Class String . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 85
10.2 Class StringBuilder . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . 88
11 Introduction to inheritance . . . . . . . . . . .. . . . . . . . . . . . . . . . . 90
11.1 Importance of equal and hashCode methods . . . . . . . . . . . . . . . . . 96
iii
12 Exceptions . . . . . . . . . . .. . . . . .. .. .. . . . . . . . . . . . . . . . 100
12.1 try-catch blocks . . . . .. . . . . .. .. .. . . . . . . . . . . . . . . . 101
12.2 finally block . . . . . . .. . . . . .. .. .. . . . . . . . . . . . . . . . 102
12.3 Propagating exceptions . .. . . . . .. .. .. . . . . . . . . . . . . . . . 103
12.4 Throwing exceptions . . .. . . . . .. .. .. . . . . . . . . . . . . . . . 103
12.5 Examples . . . . . . . . .. . . . . .. .. .. . . . . . . . . . . . . . . . 104
12.6 Assertions . . . . . . . .. . . . . .. .. .. . . . . . . . . . . . . . . . 110
13 IO streams primer . . . . . .. . . . . .. .. .. . . . . . . . . . . . . . . . 111
13.1 Binary and text IO streams . . . . .. .. .. . . . . . . . . . . . . . . . 111
13.2 StreamTokenizer class . . . . . . . .. .. .. . . . . . . . . . . . . . . . 118
13.3 IO cheat sheet . . . . . . . . . . . .. .. .. . . . . . . . . . . . . . . . 120
13.3.1 Reading/writing binary files byte-by-byte . . . . . . . . . . . . . . . . 120
13.3.2 Reading a binary file into an array of bytes . . . . . . . . . . . . . . . 120
13.3.3 Reading/writing text files line-by-line . . . . . . . . . . . . . . . . . . 121
13.3.4 Reading/writing text files character-by-character . . . . . . . . . . . . . 121
14 List of listings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
iv
Section 1
General introduction
1
6. Compiler: — a program which reads one or more source files (just text files) and
transforms them into machine code which can then be passed to the processor.
Sometimes the result is not an executable, but has some intermediate form, which
is then compiled ‘to the end’ and passed to the processor by another, additional,
program. This, for example, is the case for Java, as we will see. Some languages
are not compiled — there is a program, called interpreter, which reads the
source file line by line and transforms it into machine code ‘on the fly’ in memory,
without creating separate executable files (this is the case, for example, for the
Python programming language).
7. Programming languages:
• Low-level: machine code, assembly language.
• High-level: interpreted or compiled. There are many attempts to categorize
languages, but it seems not to be possible to do it. Broadly speaking, we
can divide languages into categories like
– imperative: procedural, object-oriented;
– declarative: relational, functional, logic.
Currently, the most popular programming languages are Java, C/C++, Python
and Java Script. On the other hand, popularity of languages depends on the sub-
ject domain; for scientific and engineering calculations, the Fortran and Mathe-
matica programming languages would be closer to the top of this list, while for
statistical applications the language called R is extremely useful and popular.
8. Algorithm: — “an unambiguous specification of how to solve a class of problems”
(Wikipedia). Therefore, an algorithm is a kind of a recipe which tells us how to
obtain the result we want in finite number of steps. For example: given a collec-
tion of, say, 3 million, numbers, how to sort them in ascending order? Or, given
two whole positive numbers, how to find their greatest common divisor (there is
a famous solution of this problem given by Euclid in his Elements). Generally,
when writing a program or a part of it, what we have to consider first is just an
algorithm which should be used to achieve our goal correctly and efficiently.
The word algorithm has been derived from the name of a IX century Persian
scholar Muh.ammad ibn Mūsā al-Khwārizmı̄ and Greek word αριθμός (which
means number). [By the way, the term algebra comes form the Arabic word
al-jabr, appearing in the title of al-Khwārizmı̄’s main work.]
Java — high level imperative programming language, object-oriented with some ele-
ments of functional programming. In the design of Java, emphasis has been put on
1. platform independence;
2. simplicity;
3. safety (no direct access to memory, as in C/C++, garbage collector, managing
security issues, etc);
4. efficiency;
5. very rich standard library.
2
3. Executed by (platform dependent) JVM — Java Virtual Machine, which inter-
prets byte code, transforms it into machine code (dynamically, without storing
it on disk) which is passed to the processor(s).
4. Simple syntax very close to that of C/C++.
5. Built-in (in the form of a platform independent standardized library):
• graphics (building GUIs — graphical user interfaces);
• multithreading (concurrency); ;
• network programming;
• working with data bases;
• multimedia (processing images, sound);
• support for various security issues;
• support for microprogramming — for ‘small’ devices, mobile phones, etc.
• handling various data formats, like XML, JSON, etc.;
• . . . and much, much more. . .
Installation: Oracle web page1 — install something called JDK (Java Development
Kit). It installs the JVM (allowing to run Java programs) but also various tools which
allow the developer to create Java programs (in particular, the compiler).
Documentation — as one big zip file — can also be downloaded, or it can be viewed
online2 .
1
https://www.oracle.com/technetwork/java/javase/downloads/index.html
2
https://docs.oracle.com/en/java/javase/19/docs/api/java.base/module-summary.html
3
Section 2
Compiling and running a Java program
• Program in Java is a collection of classes (what class really means, we will learn
shortly). Normally, each class is written in a separate (text) file with extension
.java.
• Whatever we write must be in a class; there must be at least one class declared
as public and containing public function main;
• Names are important: (public) class Person must be defined in file Person.java.
Names of classes (and therefore of files containing their definition) should start
with a capital letter; strictly speaking, it is not required, but to avoid vexing
trouble, we should always stick to this convention.
Let us look at a very simple example: a program which consists of only one class
(and, consequently, one file) which contains nothing but the function main. The pro-
gram just prints (i.e., writes to the screen) “Hello, World”.
Listing 1 AAC-HelloWorld/Hello.java
1 /*
2 * Program Hello
3 * It prints "Hello, World"
4 */
5
4
source files (or ’*.java’ to compile all .java files in the current directory). The compiler
creates the so called class files: one for each class defined in our source files. The
names of these files are the same as the names of the classes, but with extension .class
instead of .java. They contain the so called byte code corresponding to classes. This
is not the machine code for any real processor, but rather for a virtual processor which
doesn’t physically exist but is standarized and platform independent. Hence, it doesn’t
matter on which platform the process of compilation takes place — the resulting byte
code can be run on any platform where Java is installed.
As the byte code is not yet in the form of the machine code, we still need another
program to run (execute) the compiled Java application. This program is called JVM
— Java Virtual Machine — program which is named just java (java.exe on Windows).
Invoking it, we pass, as the argument, the name of the class which is the entry point
to our application (without any extension) — this class must contain the function
main. The program reads the byte code, compiles it ‘to the end’ into the form of
machine code appropriate for a given platform and executes it (without creating any
additional files). Strictly speaking, JVM (or its sub-process called JIT — just-in-time
compilation) compiles the byte code constantly ‘on the fly’; it can dynamically detect
‘bottle necks’ of the program and optimize these parts of the code because it has access
to dynamic run-time information (which is not the case for traditional compilers).
Continuing our example, the process of compiling and running our application might
look like this
$ ls what’s in the current directory?
Hello.java
$ javac Hello.java we compile...
$ ls what do we have now?
Hello.class Hello.java
$ java Hello we run the program
Hello, World and get its output
5
Section 3
Types, variables, literals
Any piece of data must have a type. In Java, the types that may correspond to named
variables are called primitive (or fundamental) types. We may think of a variable
of a primitive type as of a named piece of memory containing a single value of a well
defined type. The type of a variable determines its length (number of bytes it occupies)
and the way its contents is interpreted. In Java, only variables of primitive types can
be created locally, on the stack — objects of all other types can only be created on
the heap and never have names (identifiers). We will explain what stack and heap are
shortly.
The primitive types are (number of bytes is given in parentheses):
• Numerical types:
– integral types correspond to integer (whole) numbers. Their possible values
belong to interval [−2N −1 , 2N −1 − 1], where N is the number of bits (one
byte = 8 bits). The exception is char whose values are always interpreted
as non-negative and belong to interval [0, 216 − 1].
∗ byte (1) — values in range [−128, 127];
∗ short (2) — values in range [−32 768, 32 767];
∗ char (2) — values in range [0, 65 535] interpreted as Unicode code points
of characters (always non-negative);
∗ int (4) — values in range [−2 147 483 648, 2 147 483 647];
∗ long (8) — values in an astronomical range [−9 223 372 036 854 775 808,
9 223 372 036 854 775 807].
– floating point types correspond to real numbers (with fractional parts).
There are two such types with different ranges and precision.
∗ float (4) — values in range [≈ 1.4 · 10−45 , ≈ 3.4 · 10+38 ] positive or
negative, with roughly 7 significant decimal digits – rarely used;
∗ double (8) — values in range [≈ 4.9 · 10−324 , ≈ 1.8 · 10+308 ] positive or
negative, with roughly 16 significant decimal digits.
• Logical type boolean — has only two possible values: true and false. They are
not convertible to numerical values, neither are numerical values convertible to
boolean (as they are in many other languages);
• References to objects: — variables of these types hold as their values addresses
of objects (of the so called object types, there are no references to variables of
primitive types in Java). In C/C++ such variables are called pointers. [Note
that C++ also uses references, but they do not have much in common with what
we call a reference in Java.]
6
the right) are coefficients at consecutive powers of 2. There is a quirk, however: in
order to be able to represent also negative numbers, the term with the highest power
of 2 is taken with negative sign
−b7 · 27 + b6 · 26 + b5 · 25 + b4 · 24 + b3 · 23 + b2 · 22 + b1 · 21 + b0 · 20
Therefore, to get the highest possible value of a byte, we should set this negative part
to zero, and all remaining terms with coefficient 1 — for numbers represented by one
byte it would be
01111111
which is 12710 . Expressing groups of four bits as hexadecimal digits (see below) the
same number is 7F. The smallest (negative) byte will have 1 at the negative part and
all zeros at the positive ones
10000000
what in hexadecimal notation would be -80 (it corresponds to −12810 ). This reasoning
applies to the remaining integral types, except char — here we count all terms (char
in Java is 16 bits long) with plus sign.
There are special operators that operate on integers treating them not as numbers
but rather as sequences of bits which can convey some information (not necessarily
numerical). We will show these operators in Sec. 5.1.4 on page 24. For now just few
examples.
If we have two integer numbers (we will use only eight-bit numbers for simplicity), we
can AND them: the result will be a sequence of bits where there is 1 whenever there
is 1 in both numbers at the corresponding position, and 0 otherwise — we can interpret
it as the logical conjunction bit-by-bit with 1 corresponding to true and 0 to false:
0 1 1 0 1 1 0 0
0 1 0 1 0 1 0 1
---------------
& 0 1 0 0 0 1 0 0
The symbol of AND is ’&’, so interpreting these sequences of bits as numbers, we get
108 & 85 = 68.
Similarly, ORing corresponds to alternative, or logical disjunction (denoted by a vertical
bar) — to be true, at least one operand must be true:
0 1 1 0 1 1 0 0
0 1 0 1 0 1 0 1
---------------
| 0 1 1 1 1 1 0 1
0 1 1 0 1 1 0 0
0 1 0 1 0 1 0 1
---------------
^ 0 0 1 1 1 0 0 1
or 108 ^ 85 = 57. XORing has several interesting (and useful) features. For example,
let’s consider XORing a number with 1’s:
7
0 1 1 0 1 1 0 0
1 1 1 1 1 1 1 1
---------------
^ 1 0 0 1 0 0 1 1
A we can see, the result is like the original value, but with all bits flipped: 0 → 1,
1 → 0. Now let’s XOR the same number with 0’s:
0 1 1 0 1 1 0 0
0 0 0 0 0 0 0 0
---------------
^ 0 1 1 0 1 1 0 0
Now the original number has been exactly reproduced. So, XORing with 1 flips the
bit, XORing with 0 – leaves it intact. It follows, that XORing any bit with the same
bit-value (whether it’s 0 or 1) twice always reproduces the original value:
0 1 1 0 1 1 0 0
0 1 0 1 0 1 0 1
---------------
^ 0 0 1 1 1 0 0 1
0 0 1 1 1 0 0 1
0 1 0 1 0 1 0 1
---------------
^ 0 1 1 0 1 1 0 0
~ 0 1 1 0 1 1 0 0 -> 1 0 0 1 0 0 1 1
Other useful operations on sequences of bits are shifts — to the left or to the right by
a given number of bits. When we shift bits to the left (symbol <<), all bits coming
out on the left edge just disappear and zeros come in from the right side:
0 1 1 0 1 1 0 0 << 2 -> 1 0 1 1 0 0 0 0
0 1 1 0 1 1 0 0 >>> 2 -> 0 0 0 1 1 0 1 1
There is also >> operator — here, what comes in from the left is the value of the
highest (leftmost) bit: if that is 0 (was corresponds to positive numbers), then zeros
come in while if its is 1 (negative numbers), then ones will come in. More details can
be found in Section 5.1.4 on p. 24.
8
3.1.2 Floating point types
The representation of floating point numbers (that represent real numbers known from
mathematics) is completely different. Let us consider float (although it is not much
used). Numbers of this type are stored in 4 bytes, i.e., 32 bits. The highest bit is just
the sign bit: 0 for positive values, 1 for negative. Then we have eight bits of the so
called exponent, and 23 bits of the mantissa
seeeeeeeefffffffffffffffffffffff
Eight bits of the exponent are collectively denoted by E and described by the number
from the range [0, 255] that these eight bit represent in the binary system. The 23 bits
of the mantissa are denoted collectively as F. Then the value is
The 127 (the so called bias) is needed, because otherwise it would be impossible to
represent numbers smaller than one. The first significant digit of any number expressed
in binary system must be 1, so this 1 is implicitly added at the beginning, before the
binary dot, and is followed by 23 bits of the mantissa. Consequently, we have not 23,
but 24 significant binary digits, what corresponds to precision of 24 · log10 2 ≈ 7.2
decimal digits.
For example, in
00111110001000000000000000000000
9
3.2 Object types
Besides primitive types, there are also the so called object types. They are defined by
the user, although many such types are already defined by implementers of the standard
library and we can use them in our programs. Names of object types should always
start with an upper case letter (it’s not enforced by the compiler, but is a convention
we should always observe).
Objects of these types (variables) cannot be created locally on the stack and never have
names. They are created on the heap and can be automatically removed from memory
when not needed anymore. We have access to such objects only through references
(pointers) to them which physically contain only their addresses, not values. There is
a special process, called garbage collector, which detects object on the heap which
are not referenced anymore by any reference variable in the program and removes these
unnecessary objects from memory. Object types are defined by classes which we will
cover in the following chapters. Generally, they describe objects more complicated
than just a single value: the object may contain several numbers, Boolean values, and
references (addresses) to other objects (e.g., of type String); moreover, the class also
defines operations that may act upon all this data.
Let us emphasize again that a variable (called object) of an object type is always
anonymous — there is no way to give it any name. Only variables of reference types
(pointers), which hold addresses of objects as their values, may have names (identifiers).
int a = 7;
int b = a + 5;
For local variables, instead of declaring a type explicitly, one can use a special keyword
var: then the compiler will figure out the type itself. Of course, we have to give it
a chance to do so, so the variable being defined must be initialised. For example, the
definitions above could have been written as
var a = 7;
var b = a + 5;
because the initialisers on the right tell the compiler that a and b should be of type
int.
We can add a letter ’L’ (or lower-case ’l’, but that could be easily confused with digit 1)
at the and if we want the compiler to treat it as a long
10
(note that 2147483648 without the ’L’, would be treated as an int, but that would be
wrong, because it is too big for an int!). Literal integers may also be written in octal (0
at the beginning), hexadecimal (0x at the beginning) or binary (0b at the beginning)
form:
int a = 189, b = 0275, c = 0xBD, d = 0b10111101;
Variables a, b, c and d all have the same value 18910 , because (taking digits from right
to left)
189 = 9 · 100 + 8 · 101 + 1 · 102 = 9 + 80 + 100 = 189
0275 = 5 · 80 + 7 · 81 + 2 · 82 = 5 + 56 + 128 = 189
0xBD = 13 · 160 + 11 · 161 = 13 + 176 = 189
0b10011101 = 1·20 +1·22 +1·23 +1·24 +1·25 +1·27 = 1+4+8+16+32+128+ = 189
Hexadecimal notation is especially convenient, because there are 16 hexadecimal digits
(0-9, A-F) and exactly 16 possible values of any four-bit group of bits. Therefore, one
byte can always be described by two hexadecimal digits and vice versa — any two
hexadecimal digits describe uniquely one byte. For example, the greatest short has
representation
0111 1111 1111 1111
(see above) which is 0b0111111111111111 or 0x7FFF, while the smallest
1000 0000 0000 0000
which is 0b1000000000000000 or 0x8000. When writing such literal values, leading
zeros may be omitted: instead of 0b0000000000101010, one can write just 0b101010.
Additionally, you can insert underscores between digits: they will be ignored by the
compiler, but improve readability. The number 1_123_343_198 is much easier to read
for human than 1123343198.
A number written literally, but with a decimal point is understood to be a double
double x = 1.5;
double y = x + 0.75;
or
var x = 1.5;
var y = x + 0.75;
We can add a letter ’F’ (or ’f’) at the end if we want the compiler to treat a literal as
a float
float x = 1.5F;
Floating point numbers can also be written in the so called scientific notation. In this
notation we have a number (possibly with a decimal dot), then the letter ’E’ (lower- or
uppercase) and then a integral number indicating the power of 10. For example 1.25E2
means 1.25 · 102 (i.e., 125), while 1E-7 means 1 · 10−7 (0.0000001).
Literal of type char may be written as a single character in apostrophes
char c = 'A';
As char is a numerical type, the value will be a number, namely the Unicode code of
a given character (which for ’A’ is 65). As char occupies two bytes and is treated as
a non-negative number, its values are in the range [0, 216 − 1] = [0, 65 535] which is
enough for all letters (and other symbols) in almost all languages to be represented.
Characters that are not present on our keyboard may be entered like this
11
char c = '\u03B1';
by writing, after a backslash and letter ’u’, the Unicode code of a character in hex-
adecimal notation. In this case, the code 0x03B1 corresponds to the Greek letter α.
There are also some special characters that cannot be entered from the keyboard, like
CR (carriage return), LF (line feed), etc. They can be specified using the Unicode no-
tation, as above, but they also correspond to special symbols: a backslash and a letter
or another symbol
• \a – (BEL) alert;
• \b – (BS) backspace;
• \f – (FF) form-feed (new page);
• \n – (LF) new line (linefeed);
• \r – (CR) carriage return;
• \t – (HT) horizontal tab;
• \v – (VT) vertical tab;
• \’ – apostrophe;
• \" – quotation mark;
• \\ – backslash;
Listing 2 AAG-Literals/Literals.java
1 public class Literals {
2 public static void main(String[] args) {
3 System.out.println(22); // decimal
4 System.out.println(022); // octal
5 System.out.println(0x22); // hexadecimal
6 System.out.println(0b1001); // binary
7 System.out.println(22.22); // double
8 System.out.println(2.22e-1); // "scientific"
9 System.out.println(1/3 ); // this is 0 !
10 System.out.println(1/3.); // one third
11 System.out.println(1/3D); // 3D -> double
12 System.out.println(2147483648L);// long
13 System.out.println(2147483647 + 1 ); // ooops!
14 System.out.println(2147483647L + 1 );
15 System.out.println('A'); // char
16 System.out.println('A'+2); // char
17 System.out.println((char)('A'+2));
18 System.out.println('\u0042'); // also char
19 System.out.println("Hello, World");
20 System.out.println("\u017b\u00F3\u0142w");
21 System.out.println("number = " + 2+2);
22 System.out.println("number = " + (2+2));
23 System.out.println(false);
24 System.out.println(2*3 == 6);
25 System.out.println("\"TAB\"s and 'NL'\n"+
26 "a\tb\tc\te\tf\n\tg\th\ti\tj");
27 System.out.println("C:\\Program Files\\java");
12
28 }
29 }
which prints
22
18
34
9
22.22
0.222
0
0.3333333333333333
0.3333333333333333
2147483648
-2147483648
2147483648
A
67
C
B
Hello, World
Żółw
number = 22
number = 4
false
true
"TAB"s and 'NL'
a b c e f
g h i j
C:\Program Files\java
and examples of creating and using variables in the program below:
Listing 3 AAL-Variables/Variables.java
1 public class Variables {
2 public static void main(String[] args) {
3 int ifour = 4;
4 double xhalf = 0.5;
5 double four = ifour; // automatic conversion
6 // int badFour = 4.0; // WRONG
7 int k = 1, m, n = k + 3;
8 m = 2;
9 final double two = xhalf*ifour;
10 // two = two + 2; // WRONG
11 boolean b = true;
12 if (b) System.out.println(
13 "k=" + k + ", m = " + m + ", n = " + n +
14 "\nSum by 4 is equal to " + (k+m+n)/ifour);
15 String john, // does string john exist?
13
16 mary = "Mary";
17 john = "John";
18 System.out.println(john + " and " + mary);
19 john = mary;
20 // reference copied, string "John" lost!
21 System.out.println(john + " and " + mary);
22 }
23 }
which prints
k=1, m = 2, n = 4
Sum by 4 is equal to 1
John and Mary
Mary and Mary
Note that variables john and mary are not objects of type String — they are references
(pointers) whose values are addresses of such objects! Therefore, john=mary means
that we copy the address of the object corresponding to "Mary" to the variable john;
from now on both john and mary refer to exactly the same object somewhere in memory.
Object which was before referred to by the variable john is now lost (because we have
lost its address) and can be garbage collected.
3.4 Conversions
Sometimes a value of one type should be used as a value of another type. Creating a
value of one type based on a value of another type is called conversion or casting.
Of course, it is impossible to change the type of a variable: conversions always involve
values. For example, in
int a = 7;
double x = a + 1;
the value of the right-hand side in the second line is of type int and a double is
needed to initialize the variable x; however, the compiler will silently convert int value
to the corresponding double value and assign it to x. Such conversions, performed
automatically by the compiler, are called implicit conversions. Generally, they will
be performed if they don’t lead to a loss of information. Conversion in the opposite
direction
double x = 7.7;
int a = x; // WRONG
will not be performed; the snippet above wouldn’t be even compiled. This is because an
int occupies four bytes and has no fractional part, while doubles have fractional part
and occupy eight bytes. Hence, conversion from double to int would lead to inevitable
loss of information. We can, however, enforce the compiler to perform such conver-
sions (taking the responsibility for possible consequences). We do it by specifying, in
parentheses, name of the type we want to convert to:
double x = 7.7;
int a = (int)x; // now OK
14
Of course, after conversion, a will be exactly 7, as there is no way for an int to have
a fractional part.
Note also that this conversion does not affect the variable x as such: its type is still
double and its value is still 7.7.
The exact rules of conversions are more complicated, but general principle is that
conversions from “narrow” types to “wider” are performed implicitly (byte,char,short
→int, int,float→double, etc.), while conversions in the opposite direction must be
explicit.
Let us briefly explain what the stack and the heap are.
Both are parts of memory that are available to the program at runtime. The stack
is simpler: when a new value (e.g., of a variable) is to be added, it is always added at
the top of the stack. We say that the value is ‘pushed’ onto the stack. Whenever we
create a new variable inside a block of code (e.g., inside a function), its value is pushed
onto the stack. Then every time the flow of control leaves the block (e.g., a function
exits), all the variables pushed onto the stack in this block are freed (deleted) in the
reverse order as they were pushed, and stack is, as we say, rewound — the top of the
stack is again at the position it had before entering the block. This means that all
variables declared inside a block are lost forever when we leave it: they are all local.
The process of removing values from the stack is called ‘popping off the stack’.
The address of the current top of the stack is always available at runtime: actually,
there is a special register of the processor dedicated only to store information on the
current location of the top of the stack.
The advantage of using the stack to store variables is that memory is managed for you
automatically. We don’t have to allocate memory by hand, or free it once we don’t
need it any more. Also, the CPU organizes stack memory very efficiently: reading from
and writing to stack is very fast.
The heap is a region of memory that is not managed automatically. When the
program asks the system to store some data on the heap, the system searches for a free
space there, writes this data in this location, marks this region as occupied and return
its address — this address can be stored in a variable of a reference type (which itself
may be on the stack). Note that if the value of this variable is lost, for example,
because it was a local variable in a block, the data on the heap it referred to is no
longer available, as we have lost its address! In Java, such unavailable object on the
heap may be eventually freed automatically by the process of the so called garbage
collector .
Unlike the stack, variables created on the heap are accessible not only locally, but
wherever their address is known. Heap memory is slower to be read from and written
to, because one has to use pointers (which contain addresses) and follow them to access
memory on the heap. Also, the process of allocating memory on the heap is rather
complicated and expensive.
15
Section 4
Quick introduction to I/O
We will show here, how data can be read from the console (standard input by default
is connected to the keyboard) and from a graphical window. First, let us consider
a console. The simplest way to read from the standard input is by creating an object
of class Scanner, as shown in the example below. The import statements at the
beginning are not necessary, but without them, one would have to use full, qualified
names of classes, e.g., java.util.Scanner instead of just Scanner.
There is a little problem with reading values of floating-point types: whether to use
a dot or a comma as the decimal separator. This depends on the locale; for example,
for Polish locale a comma should be used, for the American one – a dot. The current
locale may be changed, as explained below in comments.
Listing 4 AAI-ReadKbd/ReadKbd.java
1 import java.util.Scanner;
2 import java.util.Locale; // see below
3
16 Locale.setDefault(Locale.US);
17 Scanner scan = new Scanner(System.in);
18
We used here nextInt (to read an int), nextDouble (to read a double), and next
(to read a String): there are analogous functions nextByte, nextShort, nextLong,
16
nextBigInteger, nextFloat, nextBigDecimal, and nextBoolean. Note: when all
data has been read, the scanner should be closed (by invoking scan.close(), as shown
in the example).
In order to read data from a graphical widget, or to display a message (string), one can
use static functions from class JOptionPane, as shown below (the first argument of
these functions is null for reasons we will learn about later). Note that showInputDi-
alog returns a string (strictly speaking the reference to a string); if we know that this
string represents a number and we want this number as an int or a double, we have
to parse this string to get numbers using Integer.parseInt, or Double.parseDouble):
Listing 5 AAJ-ReadGraph/ReadGraph.java
1 import javax.swing.JOptionPane;
2
17 s = JOptionPane.showInputDialog(
18 null,"Enter a string (spaces OK)");
19 JOptionPane.showMessageDialog(null,
20 "Data entered: int=" + k + ", double=" +
21 x + ", string=\"" + s + "\"");
22 System.exit(0); // kills JVM
23 }
24 }
17
Section 5
Instructions and operators
5.1 Operators
Operators are usually expressed by symbols (like +, *, etc.) and represent operations
to be performed on their operands (arguments). Most operators are binary, i.e., they
operate on two operands; some operators act on one operand only (they are called
unary operators). Finally, there is one ternary operator.
b = expr;
evaluates the value of the right-hand side and stores the result in a variable appearing
on the left-hand side. The type of the value of expr must be the same as the type of b,
or be implicitly convertible to this type. It is important to remember that the whole
expression a=b has a type and a value: the type of this expression is the type of the
left-hand operand, and its value is equal to the value of the left-hand operand after
the assignment. Therefore, after
int a, b = 1, c;
c = (a = b+1) + 1;
int a = 5, b = 1;
b = 2*(a + 1) + b;
the value of a is still 5; when calculating a+1 we got a value (in this case 6) which
is subsequently multiplied by 2 and added the current value of b, i.e., 1. We haven’t
assigned any new value to a, so it remains as it was. However, the value of the whole
expression on the right-hand side of the assignment (13) has been assigned to b (erasing
its previous value), so b is modified here.
There is a special form of assignment, the so called compound assignment opera-
tor. It has the form
a @= b
where @ stands for any binary operator, like +, *, %, >>, etc. and is (almost)
equivalent to
a = a @ b
18
// shift operator
a <<= 2;
a = a << 2;
// bitwise ANDing
a &= 0xFF;
a = a & 0xFF;
Incrementing and decrementing integral values by 1 is so often used that it has a special
syntax. If a is a variable of an integral type, then ++a and a++ cause a to be
incremented by 1 (with - instead of + — decremented). However, there is a difference
between these two forms.
Preincrementation or decrementation (++a or --a) are ‘immediate’. The value of a is
incremented (decremented) and the value of the expression ++a or --a will be equal to
this already incremented or decremented value.
However, when a variable (say, a) is postincremented (or postdecremented), the value
of the variable a is also modified, but the value of the expression a++ (or a--) is equal
to the ’old’, unmodified value of a.
For example, after
int a = 5, b = 1, c;
c = ++a + b--;
c will be 7, as on the right-hand side a has been incremented and the value of ++a is
equal to this incremented value (which is 6 in the example), while b is decremented,
but the value of the expression b-- is equal to the ’old’, not decremented, value of b
(i.e., still 1). However, after
int a = 5, b = 1, c;
c = ++a + --b;
c will be 6, as values of ++a and --b will reflect ’new’ values of these variables. In both
cases, after the assignment to c, values of a and b are 6 and 0, respectively.
19
int i1 = 7;
char c1 = 'A', c2 = 'x';
short s1 = 3;
long l1 = 9;
double d1 = 3.5;
taking the remainder yields remainder of absolute values of operands with the sign of
the first operand
For example
System.out.println(" 7 / 2 = " + ( 7 / 2 ));
System.out.println(" 7 / -2 = " + ( 7 / -2 ));
System.out.println("-7 / 2 = " + (-7 / 2 ));
System.out.println("-7 / -2 = " + (-7 / -2 ));
System.out.println(" 7 % 2 = " + ( 7 % 2 ));
System.out.println(" 7 % -2 = " + ( 7 % -2 ));
System.out.println("-7 % 2 = " + (-7 % 2 ));
System.out.println("-7 % -2 = " + (-7 % -2 ));
20
prints
7 / 2 = 3
7 / -2 = -3
-7 / 2 = -3
-7 / -2 = 3
7 % 2 = 1
7 % -2 = 1
-7 % 2 = -1
-7 % -2 = -1
if (a % 2 == 1) ...
to check, if a is odd (because if it’s odd but negative, the remainder will be −1). Use
rather
if (a % 2 != 0) ...
Instead of remembering all these rules about negative operands, it is better just not to
use negative numbers in remainder operations.
Note also that modulus operator works also for floating point types; for example
7.75 % 0.5 is 0.25.
Logical values may be combined: the logical conjunction (AND) is denoted by &&
and alternative (OR) by | |. An exclamation mark denotes negation (NOT); for
example
The first condition corresponds to checking if b belongs to the [a, c] interval, while the
second if b is outside this interval. The third condition is just a negation of the first,
so is equivalent to the second (de Morgan’s law).
Very important: && and || operators are short-circuited (this feature is also
known as McCarthy evaluation) — if, after evaluation of the first term, the result
is already known, the second term will not be evaluated (and this is guaranteed).
Therefore, if an expression to be evaluated is of the form
21
then if expr1 is false, then the result is already known (must be false) and expr2 will
not be evaluated at all. Similarly, for
expr1 || expr2
if expr1 is true, then the result must be true and expr2 will not be evaluated. For
example, if a and b are integers, an expression like
if ( b != 0 && a/b > 5) ...
will never lead to division by zero: if b is zero, the condition b != 0 is false, the whole
condition must be therefore false and division by b will not be even tried. Note that
changing the order
if ( a/b > 5 && b != 0 ) ...
could result in divide-by-zero error!
In very rare situations, we do want both operands of an OR or AND operator to
be evaluated, regardless of the result of the evaluation of the first operand. In these
cases, we can use operators | and & (single, not doubled). Then both operands are
always evaluated. Note, that these symbols stand for logical AND and OR only if their
operands are themselves of type boolean. If operands are integer numbers, then the
same symbols mean something different: they denote bit-wise AND and bit-wise OR
operators which result in integer values (see the next section).
There is also the so called ‘exclusive OR’ (XOR) operator, denoted by ˆ. If expr1 and
expr2 have values of type boolean, then the expression
expr1 ^ expr2
is true if, and only if, the values of expr1 and expr2 are different, i.e., true and false or
false and true. For example, if x is a double, after
boolean b = (x >=2 && x <= 5) ^ (x >=3 && x <= 7);
b will be true if x ∈ [2, 5] ∪ [3, 7] but x ∈
/ [2, 5] ∩ [3, 7], i.e., when x belongs to the
symmetric difference of the two intervals (their sum but without their intersection).
Listing 6 ABY-RelOps/RelOps.java
1 public class RelOps {
2 public static void main (String[] args) {
3 int a = 1, b = 8, d = 8;
4 System.out.println(
5 "Is d in [a,b]: " +
6 ( a <= d && d <= b ));
7 System.out.println(
8 "Is d in (a,b): " +
9 ( a < d && d < b ));
10 System.out.println(
11 "Is d outside (a,b): " +
12 ( d < a || d > b ));
13 System.out.println(
14 "Is d outside (a,b): " +
15 ( !(d >= a || d <= b) ));
22
16 System.out.println(
17 "Is d == b: " + (d == b));
18 System.out.println(
19 "Is d != b: " + (d != b));
20 }
21 }
The conditional expression is the only ternary operator, i.e., an operator with
three operands. It looks like this
cond ? expr1 : expr2
Here cond is an expression of type boolean. If it is true, then the value of expr1 will
become the value of the whole expression and expr2 will not be evaluated; if cond is
false, then the value of expr2 becomes the value of the whole expression and expr1 is
not evaluated. The simplest example would be (a and b are ints):
int mx = a > b ? a : b;
which initializes mx with the bigger of a and b values. The ternary operator resem-
bles the if...else if construct, but is not equivalent! In particular, expression
b ? e1 : e2 has a value, while if...else if has not. For example,
a > b ? System.out.print("a") : System.out.print("b"); // NO!!!
doesn’t make sense, because print has no value.
Another example:
Listing 7 ACB-CondOp/Largest.java
1 import java.util.Scanner;
2 import java.util.Locale;
3
9 System.out.print(
10 "Enter three numbers (with " +
11 "DOT as dec. separator!) -> ");
12
13 var a = scan.nextDouble();
14 var b = scan.nextDouble();
15 var c = scan.nextDouble();
16
23
23 System.out.println("Number " + largest + " is " +
24 "the largest and " + smallest +
25 " is the smallest");
26 }
27 }
Shifting
Shift operators act on values of integral types: they yield another value, which
corresponds to the original one but with all bits shifted by a specified number of
positions to the left or to the right.
Left shift (<<) moves the bit pattern to the left: bits on the left which go out of
the variable are lost, bits which enter from the right are all 0. The value to be shifted
is given as the left-hand operand while number of positions to shift – by the right-hand
operand. For example (we use only eight bits to simplify notation, in reality there
are 32 bits in an int):
a 1 0 1 0 0 1 1 0
a << 3 0 0 1 1 0 0 0 0
The unsigned right shift operator (>>>) does the same but in the opposite direction
a 1 0 1 0 0 1 1 0
a >>> 3 0 0 0 1 0 1 0 0
The signed right shift operator (>>) behaves in a similar way, but what comes in
from the left is the sign bit: if the leftmost bit is 0, zeros will come in, if it is 1, these
will be ones
a 1 0 1 0 0 1 1 0
a >> 3 1 1 1 1 0 1 0 0
b 0 0 1 0 0 1 1 0
b >> 3 0 0 0 0 0 1 0 0
24
a 1 0 1 0 0 1 0 0
b 1 0 0 0 0 1 1 0
a & b 1 0 0 0 0 1 0 0
For bit-wise OR operator (|), each bit of the result is 1 if there is at least one 1 at the
corresponding position in operands, and 0 if both bits in the operands are 0
a 1 0 1 0 0 1 0 0
b 1 0 0 0 0 1 1 0
a | b 1 0 1 0 0 1 1 0
The bit-wise XOR operator (^), sets a bit of the result to 1 if bits at the corresponding
position in operands are different, and to 0 if they are the same
a 1 0 1 0 0 1 0 0
b 1 0 0 0 0 1 1 0
a ^ b 0 0 1 0 0 0 1 0
As can be expected, negating operator (~) just reverses (flips) the bits
a 1 0 1 0 0 1 0 0
~a 0 1 0 1 1 0 1 1
Let us notice here, that XORing has an interesting and useful feature. Let us see the
result of XORing a bit sequence a with all ones:
a 1 0 1 0 0 1 0 0
b 1 1 1 1 1 1 1 1
a ^ b 0 1 0 1 1 0 1 1
We see that XORing the value a with all ones gives negation of a — wherever there
was 1 in a, we get 0, and vice versa.
Now let us try to XOR our a with all zeros:
a 1 0 1 0 0 1 0 0
b 0 0 0 0 0 0 0 0
a ^ b 1 0 1 0 0 1 0 0
This time what we got is exactly the same as a! So, XORing a bit with 1 flips its value,
XORing it with zero – reproduces the same value.
Listing 8 BAA-Bits/Bits.java
1 public class Bits {
2 public static void main (String[] args) {
3 int a = 0b11111111; // 255 or 0xFF
4 System.out.println("a = " + a);
5 int b = 0x7F; // 127
6 System.out.println("b = " + b);
7
8 a = 3; // 00...011
9 System.out.println(a + " " + (a << 1) + " " +
10 (a << 2) + " " + (a << 3));
11 a = -1;
12 int firstByte = a & 255;
25
13 int secondByte = (a >> 8) & 0xFF;
14 System.out.println("-1: " + secondByte + " " +
15 firstByte);
16 a = 0b1001;
17 b = 0b0101;
18 System.out.println("AND: " + (a & b) + "; " +
19 "OR: " + (a | b) + "; " +
20 "XOR: " + (a ^ b) + "; " +
21 " ~a: " + (~a) + "; " +
22 " ~b: " + (~b));
23 }
24 }
int a = 7;
double x = a;
If the conversion is possible, but would lead to a loss of information, we have to enforce
it explicitly
double x = 7.5;
int a = (int)x;
(see also sec. 3.4 on page 14). Of course, not every conversion makes sense, for example
double x = 7.5;
String s = (String)x; // WRONG
is impossible. Generally, conversions apply to numeric types; they also can be used to
references, but we will postpone this discussion to the next chapters.
Unlike in many other languages, there are no conversions between numeric (and refer-
ence) types and logical values.
d = a + b * c;
b could be the right operand of addition or the left operand of multiplication, but we
know that multiplication has higher priority, so it is equivalent to
d = a + (b * c);
rather than
d = (a + b) * c;
26
Sometimes, however, it is not so obvious; is the statement
correct or not? If << has higher priority than <, than it is equivalent to
would not make any sense. If in doubt, just add parentheses to make your intentions
clear (in this particular case, << is ‘stronger’).
Another property of operators is associativity: it tells, whether in a series of
operations with equal priority (without parentheses), they will be performed from left
to right or from right to left. All binary operators are left-associative (from left to
right) with the exception of assignment, which is right-associative; something like this
int a, b = 1, c;
c = a = b;
int a, b = 1, c;
c = (a = b);
while
int a, b = 1, c;
(c = a) = b; // WRONG
while
would be incorrect, as the expression to the left of the second ’ ?’ doesn’t evaluate to
a Boolean value.
Listing 9 AAP-BasicOps/BasicOps.java
1 public class BasicOps {
2 public static void main(String[] args) {
3 int x = 2, y = 2*x, z = x + y;
4 // precedence, associativity
27
5 System.out.println(x + x + z * y - z / 3); // 26
6 System.out.println(x + (x + z) * (y - z) / 3); // -3
7
15 u %= 7; // compound assignment
16
28
Table 1: Precedence of operators (cont.)
Operator type Operators
bitwise XOR (→) ^
bitwise OR (→) |
logical AND (→) &&
logical OR (→) ||
ternary (←) cond ? expr1 : expr2
assignment (←) = += -= *= /= %= &= ^= |= <<= >>= >>>=
5.2 Instructions
// or
if (cond1) {
// ... (code 1)
} else if (cond2) {
// ... (code 2)
} else if (cond3) {
29
// ... (code 3)
} else {
// ... (code 4)
}
where cond are expressions whose values are of type boolean (i.e., true or false).
The else if clauses are optional, as is the else clause; however, if they are used,
the else clause must be the last. Conditions are checked in order, and for the first
which evaluates to true, the corresponding block of code will be executed (subsequent
blocks will be ignored). If the else clause is present, the corresponding block will be
executed if none of the previous conditions is true. If there is only one instruction in
the block corresponding to a condition, the curly braces are not obligatory (although
recommended).
In the following example, we check if a given year is a leap year or not:
Listing 10 ABX-Ifs/LeapYear.java
1 import java.util.Scanner;
2
10 boolean is_leap;
11
12 if (year%400 == 0)
13 is_leap = true;
14 else if (year%100 == 0)
15 is_leap = false;
16 else if (year%4 == 0)
17 is_leap = true;
18 else
19 is_leap = false;
20 // ?: operator used below!
21 System.out.println("Year " + year + " is " +
22 (is_leap? "" : "not ") + "a leap year");
23 }
24 }
switch (expr) {
case val1 :
30
statement1;
// ...
statementN;
break;
case val2 :
statement1;
// ...
statementN;
// should we break?
// ...
default:
statement1;
// ...
statementN;
break;
}
The value of expr must be either of an integral type (but not long), or the reference
to a String, or an enumeration constant (which we will introduce later). Symbols val
stand for values with which the value of expr will be compared (in the specified order).
They have to be literal values (not variables). If the value of expr is equal to any of
vals, the code in the corresponding arm is executed and then execution continues (falls
through) with all the arms below; to avoid it, use break, which transfers control flow
out of the switch block (sometimes this “falling through” may be just what we want,
as in the example below). At the end of this example, we used default arm, where
we don’t make any comparisons: this will be the arm selected if all other comparisons
yield false. The default clause is optional and doesn’t have to appear as the last. For
example:
Listing 11 ACC-SimpleSwitch/SimpleSwitch.java
1 import java.util.Scanner;
2
10 switch (initial) {
11 case 'A':
12 case 'a':
13 System.out.println("Amelia");
14 break;
15 case 'B':
16 case 'b':
17 System.out.println("Barbra");
18 break;
19 case 'C':
20 case 'c':
31
21 System.out.println("Cindy");
22 break;
23 case 'D':
24 case 'd':
25 System.out.println("Doris");
26 break;
27 default:
28 System.out.println("Invalid input");
29 }
30 }
31 }
The next example shows how to find a numerical value of a hexadecimal digit(0-9 or
a-z lower or upper case). Here, we take advantage of the “fall through” feature; note
that if ch is any lower case letter from the range [a, f ], due to “falling through”, we will
end up in line 24 and then break out. The same applies to digits (see lines 26-27):
Listing 12 ACD-Switch/Switch.java
1 import java.util.Scanner;
2
11 // toLower by hand...
12 if (ch >= 'A' && ch <= 'Z')
13 ch = (char)(ch + 'a' - 'A');
14
15 int num;
16
17 switch (ch) {
18 case 'a':
19 case 'b':
20 case 'c':
21 case 'd':
22 case 'e':
23 case 'f':
24 num = 10 + ch - 'a';
25 break;
26 case '0': case '1': case '2': case '3': case '4':
27 case '5': case '6': case '7': case '8': case '9':
28 num = ch - '0';
29 break;
30 default:
32
31 num = -1;
32 }
33
Starting from Java 14, instead of the colon, one can use the “arrow”, i.e., the two-
character ’->’ symbol (but one cannot mix colons and arrows in one switch statement).
Then
• the “fall through” feature is “turned off”;
• to the right of the arrow, one can only use:
1. single statement or expression,
2. a block (enclosed in braces),
3. a throw statement.
For example in the following example, we don’t use any break statements:
Listing 13 ACE-SwitchArrow/SwitchArrow.java
1 import java.util.Scanner;
2
14 switch (k % 4) {
15 case 0 -> part2 = "4n";
16 case 1 -> part2 = "4n+1";
17 case 2 -> part2 = "4n+2";
18 case 3 -> part2 = "4n+3";
19 default-> {
20 part2 = " ... probably negative";
21 System.out.println("You were expected to " +
22 "enter a *natural* number!");
23 }
24 }
25 System.out.println(part1 + part2);
26 }
27 }
33
Also starting from the Java 14 version, one can supply more than one values (comma
separated) in one arm of the switch
Listing 14 ACF-SwitchMult/SwitchMult.java
1 public class SwitchMult {
2 public static void main(String[] args) {
3 String n = "BMW", country = null;
4 switch (n) {
5 case "BMW", "Opel", "Audi" -> country = "Germany";
6 case "Peugeot", "Citroen" -> country = "France";
7 default -> country = "unknown";
8 }
9 System.out.println(n + " -> " + country);
10 }
11 }
Another kind of switch is the so called switch expression. It is also available since
Java 14. This is an expression, what means that the switch as the whole has a value.
Therefore, each arm of the switch expression must itself be an expression, i.e., have
a value (of the same type, or at least the types must be convertible to one common
type). It can be just an expression, or a block ending with yield value – then the value
indicated after the yield statement will be the value of the corresponding arm. As the
switch expression must have a value, it has to be exhaustive, i.e., for all possible values
there must be a matching switch label. For integral types and Strings the number of
possible values is practically infinite, so there is no way to avoid the default arm, as
in the example below:
Listing 15 ACG-SwitchExpr/SwitchExpr.java
1 public class SwitchExpr {
2 public static void main (String[] args) {
3 int i = 7;
4 var res = switch(i) {
5 case 1, 2, 3 -> "First quarter";
6 case 4, 5, 6 -> "Second quarter";
7 case 7, 8, 9 -> {
8 System.out.println("And here a block...");
9 System.out.println("still we need a value...");
10 yield "Third quarter";
11 }
12 case 10, 11, 12 -> "Fourth quarter";
13 // must be exhaustive
14 default -> "Something wrong";
15 };
16 System.out.println("res = " + res);
17 }
18 }
34
We will talk about enumerations later, but here we will just mention that for them the
number of values is usually small, so we just have to ensure that they all have been
taken into account:
Listing 16 ACH-SwitchEnum/SwitchEnum.java
1 public class SwitchEnum {
2 enum Country {FRANCE, GERMANY, MEXICO, CANADA, CHINA};
3 public static void main (String[] args) {
4 Country c = Country.CANADA;
5
5.2.3 Loops
while loop
The simplest form of a loop is the so called while-loop. It looks like this
while (condition) {
// ...
if (cond1) break; // optional
// ...
if (cond2) continue; // optional
// ...
}
where condition is an expression yielding Boolean value (true or false). The loop is
executed as follows:
1. condition is evaluated, and if it is false, the flow of control jumps out of the loop;
2. if condition evaluates to true, the body of the loop (everything inside the block)
is executed and then the flow of control goes back to item 1.
Inside the loop, you can (but not have to) use break and continue instructions. When
break is executed, the flow of control goes out of the loop immediately. When continue
is encountered, the current iteration of the loop is considered completed, and we go
back to next iteration (so the condition is checked again).
The following loop will print square roots of integers read from the keyboard, but only
of positive ones; the loop ends when the user enters 0:
35
if (n < 0) {
System.out.println("Negative - try again!");
continue;
}
System.out.println("square root of " + n +
" is " + Math.sqrt((double)(n)));
}
Note that assignment is an expression — it has a value. Therefore, we could have
rewritten the above snippet as
Scanner scan = new Scanner(System.in);
int n;
while ((n = scan.nextInt()) != 0) {
if (n < 0) {
System.out.println("Negative - try again!");
continue;
}
System.out.println("square root of " + n +
" is " + Math.sqrt((double)(n)));
}
In the following example, we read numbers and print the information if they are prime
or not:
Listing 17 AFA-While/Prime.java
1 import java.util.Scanner;
2
7 while (true) {
8 System.out.print(
9 "Enter natural number (0 to exit) -> ");
10 int n = scan.nextInt();
11
12 if (n == 0) break;
13
16 if (n == 1) {
17 System.out.println("Number 1 is neither " +
18 "prime nor composite");
19 continue;
20 } else if (n > 2 && n%2 == 0) {
21 prime = false;
22 } else {
23 int p = 3;
24 while (p*p <= n) {
36
25 if (n%p == 0) {
26 prime = false;
27 break;
28 }
29 p += 2;
30 }
31 }
32 System.out.println("Number " + n + " is " +
33 (prime ? "prime" : "composite"));
34 }
35 scan.close();
36 }
37 }
Listing 18 AFB-WhileBis/Prime.java
1 import java.util.Scanner;
2
7 MAIN_LOOP:
8 while (true) {
9 System.out.print(
10 "Enter natural number (0 to exit) -> ");
11 int n = scan.nextInt();
12
37
13 if (n == 0) break;
14
15 if (n == 1) {
16 System.out.println("Number 1 is neither " +
17 "prime nor composite");
18 continue;
19
25 } else {
26 int p = 3;
27 while (p*p <= n) {
28 if (n%p == 0) {
29 System.out.println("Number " + n +
30 " is composite");
31 continue MAIN_LOOP;
32 }
33 p += 2;
34 }
35 System.out.println("Number " + n +
36 " is prime");
37 }
38 }
39 scan.close();
40 }
41 }
Suppose now that the user enters numbers until he/she enters zero; we want to find
the sum of all these numbers and the number of them:
int sum = 0, num = 0, n;
while ( (n = scanner.nextInt()) != 0) {
sum += n;
++num;
}
after the loop we have what we were intersted in in variables sum and num.
do-while loop
Loops of this type are similar to while-loops, but checking a condition is performed
after execution of the body of the loop.
do {
// ...
if (cond1) break; // optional
// ...
if (cond2) continue; // optional
// ...
} while(condition);
38
Therefore,
1. body of the loop is executed;
2. the value of condition is evaluated; if it is true, flow of control goes to item 1, if
it is false, the flow of control jumps out of the loop.
Let us consider an example. We will use here the random from class Math. The
expression Math.random() returns a (pseudo) random value from the half-open interval
[0, 1). This is sufficient to generate random values from any range, not necessarily
[0, 1). Suppose we want a random integer from the interval [a, b]. There are b − a + 1
consecutive values in this interval. Multiplying Math.random() by this number, we
get a double from the half-open interval [0, b − a + 1). Casting it to int will give us
a number from the interval [0, b − a] (we will never get b − a + 1, as the interval [0, 1)
was half-open). Adding a to the result, we get an int from the closed interval [a, b], as
desired. So, to generate a pseudo random integer from the closed interval [a, b], we can
write
a + (int)(Math.random()*(b-a+1))
It is important to use parentheses around the product Math.random()*(b-a+1) be-
cause casting has higher precedence than multiplication. Without the parentheses
a + (int)Math.random()*(b-a+1) // WRONG
casting to int would be applied to Math.random() before multiplication, but this is
always zero, as Math.random() is always less than 1.
The program below illustrates what we have just said demonstrating also the do-while-
loop: here, we roll two dice until two sixes are thrown:
Listing 19 AFE-DoWhileDice/Dice.java
1 public class Dice {
2 public static void main (String[] args) {
3 int a, b;
4 do {
5 a = 1 + (int)(Math.random()*6);
6 b = 1 + (int)(Math.random()*6);
7 System.out.println("a=" + a + " b=" + b);
8 } while (a + b != 12);
9 }
10 }
39
a=4 b=5
a=6 b=6
As we can see, the main difference between while and do-while loop is that in the
first case the condition is checked before executing the body of the loop, while in the
second case — after.
for loop
• init: one declaration (possibly of several variables of the same type), or zero or
more comma-separated expressions;
• condition: expression with a value of type boolean; if left empty – interpreted
as true;
• incr: zero or more comma-separated expressions.
Any (or even all) of the three parts may be empty, but exactly two semicolons are
always required.
The execution of a loop proceeds as follows:
1. The init part will be executed once only, before entering the loop. If it is a
declaration of one or more variables of the same type, they will exist only within
the body of the loop — they are not visible (in fact, they don’t even exist) when
the flow of control goes out of the loop.
2. The condition part will be evaluated next (if left empty, it will be assumed true).
If it evaluates to false, the loop ends and the flow of control goes out of the loop.
If it evaluates to true then the body of the loop is executed.
3. When execution of of the body of the loop is completed, the inc part is executed,
and then we go back to item 2 (evaluation of the condition).
For example, the following snippet calculates and prints the sum of all natural
numbers from the range [1, 1000]
int sum = 0;
for (int i = 1; i <= 1000; ++i) {
sum += i;
}
System.out.println("Sum is " + sum);
In the example above, the braces could have been omitted, because the body of the
loop consists of only one statement:
int sum = 0;
for (int i = 1; i <= 1000; ++i) sum += i;
System.out.println("Sum is " + sum);
40
Note that we cannot declare sum in the init part of the loop
for (int i = 1, sum = 0; i <= 1000; ++i)
sum += i;
System.out.println("Sum is " + sum); // no 'sum' here!
because it would not exist after exiting the loop, so we would not be able to print its
value.
In the following example we have nested for-loops: in each iteration of the main (outer)
loop, the program executes two inner loops which print first some spaces and then
some asterisks in such a way that a “pyramid” is formed with number of asterisks in
the bottom line equal to a number read from input:
Listing 20 AFH-ForPyram/Stars.java
1 import java.util.Scanner;
2
Listing 21 AFJ-ForWhileEuler/ForWhileEuler.java
1 import java.util.Scanner;
2 /*
41
3 * Finding and printing values of Euler's totient
4 * function, i.e., number of positive integers up
5 * to a given integer n that are relatively prime to n.
6 */
7
12 while (true) {
13 System.out.print(
14 "\nEnter a natural number (0 to exit) -> ");
15 int n = scan.nextInt();
16
17 if (n == 0) break;
18
19 int count = 0;
20 for (int p = 1; p <= n; ++p) {
21 int a = n, b = p;
22 // Euclid's algorithm for GCD
23 while (a != b) {
24 if (a > b) a -= b;
25 else b -= a;
26 }
27 if (a == 1) ++count;
28 }
29 System.out.print("\u03c6(" + n + ") = " + count);
30 }
31 }
32 }
42
Section 6
Arrays
Arrays are the simplest data structure. An array can be viewed as a fixed-sized collec-
tion of elements of the same type in which elements are ordered and can be accessed
by specifying their index. Indices start with 0 (first element), so the last element has
index size-1, where size is the size (length, dimension) of the array, i.e., number of its
elements. In Java, arrays are objects — this means that they carry not only information
on their elements but also some other information, in particular on their size. It also
means that they are always created on the heap and never have names: we can refer
to them only using references to them (i.e., in C/C++ language, pointers — variables
which hold, as their values, addresses of objects).
Arrays can be created in several ways, illustrated in the example below. Points to note:
• When an array is created, its size must be specified and then cannot be modified.
• The type reference to array of elements of type Type is denoted by Type[]. State-
ment int[] arr; means that arr is a reference to array of ints — only a reference
(with value null) is created, not an array! One can also write int arr[]; but
this notation is not recommended.
• If arr is a reference to an array, the expression arr.length is of type int and its
value is the length (size, dimension) of the array referenced to by arr.
• Individual elements of an array can be accessed by their indices — if arr is the
reference to an array, the expression arr[i] denotes its ith element; remember,
however, that indexing starts from zero!
• There is a special kind of loop which can be used to iterate over elements of an
array (and, as we will see later, over elements of other types of collections as
well). It’s sometimes called a ‘for-each loop’ and has the form:
for (Type elem : arr) { ... }
where Type is the type of elements of the array arr, elem is any identifier, and
arr is the reference to an array. In consecutive iterations of the loop, elem will be
a copy of the consecutive element of the array.
For example, this is how we can create an 10-element array of chars and fill it with
random capital Latin letters (note the keyword new). Then we print its elements,
separated by spaces, in one line.
Note that the condition in the for-loop is i < arr.length with strict inequality, as
the index arr.length (i.e., 10) would be illegal: indexing starts with 0, so the last
element has index 9. Note also the use of the for-each loop to print the elements.
43
This is how we can create an array and initialize it at the same time; note that we
don’t specify its size, because the compiler will be able to infer it from the initializer
in braces; after creation, we print it in reverse order:
int[] arr = {1, 2, 8, 9};
for (int i = arr.length-1; i >= 0; --i)
System.out.print(arr[i] + " ");
System.out.println();
Next example shows still another way to create and initialize an array:
int[] a0 = new int[]{1, 2, 3};
int[] a1 = {4, 5, 6};
int[] arrsum = new int[a0.length]; // contains zeros
for (int i = 0; i < arrsum.length; ++i)
arrsum[i] = a0[i] + a1[i];
for (int n : arrsum)
System.out.print(n + " ");
prints
5 7 9
The example below shows how to find the value of the maximum element of an array
int[] arr = {1, -6, 9, -2, 7};
int mx = arr[0];
for (int i = 1; i < arr.length; ++i)
if (arr[i] > mx) mx = arr[i];
System.out.println("Maximum element is " + mx);
Sometimes what we want is rather the index of the maximum element:
int[] arr = {1, -6, 9, -2, 7};
int indmax = 0;
for (int i = 1; i < arr.length; ++i)
if (arr[i] > arr[indmax]) indmax = i;
System.out.println("Maximum element has index " + indmax);
How to reverse the order of elements in an array arr of, say, ints? It’s simple:
for (int i = 0, j = arr.length-1; i < j; ++i, --j) {
int tmp = arr[i]; // swapping two elements
arr[i] = arr[j];
arr[j] = tmp;
}
Note that the condition i < j cannot be replaced by i < arr.length — we would
then reverse the order of elements twice thus restoring the original order!
And now – how to randomly shuffle elements of an array?
int[] arr = {1, 2, 3, 4, 5, 6};
// shuffling
for (int i = 0; i < arr.length-1; ++i) {
// random index from interval [i, arr.length-1]
44
int r = i + (int)((arr.length-i)*Math.random());
// swapping
int tmp = arr[i];
arr[i] = arr[r];
arr[r] = tmp;
}
Let us consider the following example. Note that printArr is a function which just
prints the content of an array passed to it as the first argument (just after the opening
parenthesis); it will also print the string passed as the second argument. More about
functions in the next section (Sec. 7, p. 51).
Listing 22 CYD-BasicArray/BasicArr.java
1 public class BasicArr {
2
20 // ad hoc array
21 printArr(new int[]{7,8,9}, "Ad hoc array");
22 }
23
24
25 /**
26 * prints an array of integer numbers
27 */
28 private static void printArr(int[] a, String message) {
29 System.out.print(message + ": [");
30 for (int i : a)
31 System.out.print(" " + i); // <-- i unmodifiable
32 System.out.println(" ]; size = " + a.length);
33 }
34 }
45
Array a1: [ 1 2 3 ]; size = 3
Array a2: [ 4 5 6 ]; size = 3
After a1=a2, a1 is: [ 4 5 6 ]; size = 3
After modifications a1 is: [ 44 5 66 ]; size = 3
After modifications a2 is: [ 44 5 66 ]; size = 3
Ad hoc array: [ 7 8 9 ]; size = 3
Arrays of objects do not exist in Java (as they do exist in C++). Instead, we can create
arrays of references (pointers) to objects. If not initialized otherwise, all elements of
such an array will initially be null:
46
int[][] arr = { { 1, 2, 3, 4},
{ 11, 22, 33, 44},
{ -1, -2, -3, -4} };
Individual arrays ({1,2,3,4}, {11,22,33,44} and {-1,-2,-3,-4} in this example)
are called rows of this array — they correspond to arr[0], arr[1], arr[2]. Elements with
the same second index are called columns: in the above example elements arr[i][1]
where i=0, 1, 2, are the second (with index 1) column of the array (these are numbers
2, 22, -2).
In particular, the number of columns in a rectangular array may be equal to the
number of rows — it is then a square array. For example, the following array is
a square array 4 × 4:
int[][] squ = { { 1, 2, 3, 4},
{ 5, 6, 7, 8},
{ 9, 10, 11, 12},
{ 13, 14, 15, 16} };
For square arrays it makes sense to talk about its diagonals. The main diagonal (or
just ‘diagonal’) consists of the elements for which the row and column indices are the
same, or in other words these are elements on the diagonal going from the upper-left
corner to the lower-right one (in the example above, these are elements 1, 6, 11, 16).
The antidiagonal are elements for which the sum of row and column indices is fixed
and equal to size-1, where size is the number of columns (equal to the number of rows).
In other words, these are elements on the diagonal going from the upper-right corner
to the lower-left one (in the example above, these are elements 4, 7, 10, 13).
Let us consider another example:
Listing 23 CYB-SimpleArrays/SimpleArrays.java
1 public class SimpleArrays {
2 public static void main(String[] args) {
3
4 // ================================================
5 int[] a = {1,2,3};
6 System.out.println("a.length = " + a.length);
7 for (int i = 0; i < a.length; ++i)
8 a[i] = (i+1)*(i+1);
9 for (int i = 0; i < a.length; ++i)
10 System.out.print(a[i]+" ");
11 System.out.println('\n');
12
13 // ================================================
14 int[][] b = { {1,2,3}, {4,5,6,7,8}, {11,12} };
15 System.out.println("b.length = " + b.length);
16 for (int row = 0; row < b.length; ++row)
17 System.out.println("b["+row+"].length = " +
18 b[row].length);
19 System.out.println();
20
47
23 System.out.print(b[row][col]+" ");
24 System.out.println();
25 }
26 System.out.println('\n');
27
28 // ================================================
29 int[] c = new int[]{1,2,3}; // <- size inferred
30 System.out.println("c.length = " + c.length);
31 for (int i = 0; i < c.length; ++i)
32 System.out.print(c[i]+" ");
33 System.out.println('\n');
34
35 // ================================================
36 int[] d = new int[5]; // <- elements are 0
37 System.out.println("d.length = " + d.length);
38 for (int i = 0; i < d.length; ++i)
39 System.out.print(d[i]+" ");
40 System.out.println('\n');
41
42 // ================================================
43 int[][] e = new int[3][2]; // <- a 3x2 matrix
44 System.out.println("e.length = " + e.length);
45 for (int row = 0; row < e.length; ++row)
46 for (int col = 0; col < e[row].length; ++col)
47 e[row][col] = row+col;
48
49 // ================================================
50 int[][] f = new int[3][];
51 for (int row = 0; row < f.length; ++row)
52 System.out.print(f[row]+" ");
53 System.out.println();
54
48
Note that the array b has rows of different lengths, so, in the loop which prints its
elements, we have to use, in the inner loop, col < b[row].length.
Note also the array f. It is a 2D array, that is one-dimensional array of references
to one-dimensional arrays. Therefore, in order to create it, we have to specify only
number of its elements (that is number of ‘rows’); all these elements have initially the
value null, and their type is reference to an array of ints. Of course, we can later
assign to them references to any arrays of integers (of any lengths).
In the next example, we use a three-dimensional array of ints: the first index corre-
sponds to a student, the second to a course he/she is taking and the third to the grades
of this student and this course:
Listing 24 CYJ-Arr3D/Arr3D.java
1 public class Arr3D {
2
8 String[] students = {
9 "John", "Mark", "Jim", "Henry",
10 "Peter", "Kevin", "Jack"
11 };
12 // three-dimensional array of grades:
13 // first index - student
14 // second index - subject for a given student
15 // third index - grades for a given student
16 // and a given subject
17 int[][][] grades = {
18 // Math Programming English
19 { {3,4,3}, {4,3,3,4,4,3}, {4,3,3} }, // stud. 0
20 { {3,5}, {5,2,3,3,4}, {2,4} }, // stud. 1
21 { {5,4,4}, {5,5,5,4}, {3} }, // stud. 2
22 { {3,4,3}, {4,3,3,3,3}, {3,3,4} }, // stud. 3
23 { {4,3}, {4,3,3}, {5,3} }, // stud. 4
24 { {5,3}, {4,2,3}, {3,3} }, // stud. 5
25 { {5,4}, {4,4,5}, {5,3} }, // stud. 6
26 };
27
49
38 for (int g = 0; g < grades[s][c].length;++g)
39 ave += grades[s][c][g];
40 }
41 ave /= num;
42 if (ave > 4) pom[count++] = students[s];
43 }
44
In a similar way, having such a three-dimensional array, we could ask other questions,
like
50
Section 7
Static functions
Classes usually contain, besides fields, constructors and methods (that we will cover
later, when talking about classes) also static functions (or static methods). We can
think about a function as a piece of code that can be executed (invoked) many times
from other functions (for example, from main) in such a way that in each invocation
some data that the function operates on may be different.
The definition of a static function is of the form
[access] static Type funName(Type1 parName1, Type2 parName2, ...) {
// body of the function
}
and consists of
• The keyword static (there are also functions which are not static — we will be
talking about them in Sec. 8, p. 59). Before or after this keyword we could place
an access specifier of the function (public or private) — we will discuss it later.
• The name of the type of a result which this function yields (the so called return
type); void, if the function doesn’t return any result.
• A name of the function; it should always start with a lower-case letter but is
otherwise arbitrary.
• In round parentheses, a list of parameters: these are comma separated pairs Type
parName where Type is the name of a type and parName is an (arbitrary) name of
this parameter (it should start with a lower-case letter). The list of parameters
can be empty, but parentheses are always required.
• The body of the function enclosed in braces.
Names of parameters are arbitrary and have nothing to do with names declared in other
functions (as their parameters or variables defined inside them). We invoke (call) the
function just by its name with arguments corresponding to the function’s parameters
enclosed in round parentheses:
... funName(arg1, arg2, ...) ...
What happens is:
• Copies of the values of arguments are pushed (placed) on the program’s stack
(a special region of memory).
• These copies of the arguments, laying on the stack, can be accessed inside the
body of the functions by names of the corresponding declared parameters. Their
names in the calling function are completely irrelevant, because a function can see
only values laying on the stack. We can say that it doesn’t even know ‘who calls’
it and where from. It only knows that there must be values of the appropriate
type (as declared as the type of parameters) lying on the stack that can be
associated with its parameters. In particular, we can use literals as arguments
— copies of their values will be then pushed on the stack.
• All variables defined inside a function are local to this function — they are
also located on the stack (in the example below, mx is such a local variable).
This region of the program stack, associated with an invocation of a function, is
called this function’s stack frame or activation record. Note that names of
local variables are arbitrary and unrelated to local variables of the same name
appearing in other functions.
51
• Instructions in the body of the function are executed. If the function is void,
execution stops when the end of the definition is reached or a return; statement
is encountered. If it is non-void, execution ends when statement return expr;
is encountered, where expr is an expression whose value is of type declared as the
return type of the function (or is convertible to this type).
• When the function returns, the stack is ’unwound’ or ’rewound’: it is reverted
to the state it had before invocation. In particular, all local variables, including
those corresponding to parameters, cease to exist.
• If the function returns a value, the invocation expression (something like fun(a))
may be considered to be a temporary, unmodifiable variable whose type is the re-
turn type of the function and value is that of expr appearing in the return expr;
expression. It is a temporary variable, so normally we have to do something with
it: print it, assign its value to a variable, or use it in another expression.
double u = 1, v = 2, w = 2;
// ...
double result = maxOf3(u, v+1, w-1);
As we can see, the arguments do not need to be variables — what matters are their
values, copies of which will be put on the stack and will be available for the function
under names a, b and c (and will ‘disappear’ when the function exits).
It is very important to realize how the arguments are passed to the function. For
example, suppose we have a function
int a = 1;
int res = fun(a);
the value of res will be 100, but the value of a will still be 1, as only the copy of its
value was accessible to the function; this copy was modified, but it disappeared after
the return anyway.
All this applies also to passing objects, for example arrays (which are objects.)
There is one important fact that we have to remember, though. Variables declared
as arrays (or variables of any other object type) are really pointers (in Java called
references) to anonymous objects representing these arrays (or other objects). Their
values are addresses of objects. This means, in particular, that when we pass an array
52
to a function (or return an array from a function), what we are really passing is a copy
of the address of the array, not the array (or another object) itself. This copy will be
put on the stack and will disappear after the function returns, so modifying it usually
doesn’t make much sense. However, the value of this copy is the address of the original
object (e.g., of an array); consequently, having this address, functions which receive it
can modify the original object (as they ‘know where it is’).
Examples can be found in the following program:
Listing 25 CYS-PassArr/PassArr.java
1 public class PassArr {
2
8 // passing r e f e r e n c e to function
9 reverseArr(a);
10 printArr(a, "Array a reversed");
11 }
12
13 /**
14 * prints an array of integer numbers
15 */
16 private static void printArr(int[] a, String message) {
17 System.out.print(message + ":\n [");
18 for (int i : a) System.out.print(" " + i);
19 System.out.println(" ]; size = " + a.length);
20 }
21
22 /**
23 * returns first n triangular numbers
24 */
25 private static int[] getArr(int n) {
26 int[] arr = new int[n];
27 for (int i = 0; i < n; ++i)
28 arr[i] = (i+1)*(i+2)/2;
29 return arr;
30 }
31
32 /**
33 * modifies input array (reversing order of elements)
34 */
35 private static void reverseArr(int[] a) {
36 for (int i = 0, j = a.length-1; i < j; ++i,--j) {
37 int p = a[i];
38 a[i] = a[j];
39 a[j] = p;
40 }
53
41 }
42 }
Let us now consider an example of two static functions: isPrime and primesBetween.
Of course, as always in Java, they must be placed in a class. The first of these two
functions checks if a given number is prime, the second prints prime numbers in a given
interval invoking, for each number from the range, the first one.
Listing 26 BHK-StatFun/StatFun.java
1 public class StatFun {
2
2 is prime
3 is prime
4 is NOT prime
5 is prime
6 is NOT prime
7 is prime
8 is NOT prime
9 is NOT prime
10 is NOT prime
11 is prime
54
12 is NOT prime
13 is prime
14 is NOT prime
15 is NOT prime
16 is NOT prime
17 is prime
18 is NOT prime
19 is prime
20 is NOT prime
Functions can be recursive, which means that they can call itself: each such
invocation creates independent frame on the stack. Of course, we have to ensure that
such recursive invocations will not be executed forever — somewhere in the body of
the function, usually at the beginning, some condition must be checked telling if the
function should return without calling itself. A classic example is the factorial function:
factorials 0! and 1! are returned without recursive invocation, for other values recursion
n! = n · (n − 1)! is used:
Listing 27 BHL-RecFun/RecFun.java
1 public class RecFun {
2
which prints
Factorial of 0 is 1
Factorial of 1 is 1
Factorial of 2 is 2
Factorial of 3 is 6
Factorial of 4 is 24
Factorial of 5 is 120
Factorial of 6 is 720
Factorial of 7 is 5040
Factorial of 8 is 40320
Factorial of 9 is 362880
Factorial of 10 is 3628800
Factorial of 11 is 39916800
Factorial of 12 is 479001600
(note that 13! is already too big to fit in an int!)
55
The next example illustrates the well known algorithm of sorting arrays, the so
called selection sort. The idea is simple: for each consecutive element of the array find
the smallest element in the portion of the array to the right of the element chosen and,
if it is smaller than this element, just swap these two elements. We can implement
this trivial algorithm both iteratively or recursively (usually the iterative version will
be more efficient):
Listing 28 AFR-SelSort/SelSort.java
1 public class SelSort {
2 private static void selSortIte(int[] a) {
3 for (int i = 0; i < a.length-1; ++i) {
4 int indmin = i;
5 for (int j = i+1; j < a.length; ++j)
6 if (a[j] < a[indmin]) indmin = j;
7 int temp = a[i];
8 a[i] = a[indmin];
9 a[indmin] = temp;
10 }
11 }
12
56
Functions — both static and non-static — can be overloaded. This means that
in one class we can define several functions with the same name. When an invocation
of one of these functions is encountered in the program, the compiler must know which
one of them is meant: we say that it has to resolve the method call . For this to be
possible, the overloaded functions have to differ in number and/or types of parameters
in such a way, that just by ’looking’ at the arguments of an invocation, the compiler
can determine unambiguously the method to be called.
In the example below, there are three functions with the name sum.
Listing 29 AFM-Overload/Overload.java
1 public class Overload {
2 static int sum(int a, int b) {
3 return a + b;
4 }
5 static int sum(int a, int b, int c) {
6 return a + b + c;
7 }
8 static String sum(String s, int a) {
9 return s + a;
10 }
11 public static void main (String[] args) {
12 int a = 4, b = 3, c = 5;
13 String s = "a=";
14 System.out.println(
15 "Sum of a,b: " + sum(a, b) +
16 "\nSum of a,b,c: " + sum(a, b, c) +
17 "\nSum of s,a: " + sum(s,a));
18 }
19 }
The first two overloads differ in number of parameters/arguments. The first and the
third have both two parameters, but the first of these parameters is of different type
(int and String, respectively) Therefore, the compiler will always know which function
to call just by ’looking’ at number and types of arguments. The program prints
Sum of a,b: 7
Sum of a,b,c: 12
Sum of s,a: a=4
The types of an argument and its corresponding parameter do not have to match
exactly. The compiler considers all overloads and looks for the best match. The first
criterion is simple: the number of arguments must be equal to the number of parameters
(we disregard the so called var-args here). Then types are analyzed. If there is no
perfect match, the compiler tries to find one by widening the type of arguments: for
example, the value of an argument of type byte may be widened to short, short to
int etc. This is illustrated by the following program, which defines three overloads of
function f: with the parameter of type short, int and double.
57
Listing 30 AFN-Resol/Resol.java
1 public class Resol {
2 static void f(short a) {System.out.println("s=" + a);}
3 static void f(int a) {System.out.println("i=" + a);}
4 static void f(double a) {System.out.println("d=" + a);}
5
which prints
As one can see, byte has been widened to short (as there is no overload for bytes).
There is no overload for chars either, so char has been widened to int. But not to
short! Both types have the same size, but char is unsigned, so there are values of this
type that are not representable as shorts. Note also that, as there is no overload for
longs, it was widened to double, although it changes the type from integral to floating
point.
Precise rules of method call resolution are a bit more complicated, as they have to
take into account also inheritance, the so called boxing/unboxing, var-args, etc. — we
will cover these topics later.
58
Section 8
Classes
In object oriented programming we deal with objects that we can think of as aggre-
gates of some pieces of information together with actions which can be performed on
them. Similar objects (sharing the same type of information and the same actions)
are described by the definition of a class which encapsulates properties of all object
of this class that will be created in our program. Each object of a class can contain
some information in the form of fields — fields have fixed types and names and will be
present in any object of the class; of course their values are usually different in different
objects. All these values, which can be subject to modifications during the execution
of a program, define current state of an object. Values of fields in an object which are
accessible to the user, at least for reading, are sometimes called its attributes.
Actions are represented by functions (called methods) which operate on objects —
methods can return information about the state of an object, modify it, etc. They are
always invoked on a specific object and have direct access to all fields of this particular
object they were invoked on, and also to all fields of any object of the same class.
Invoking a method on an object is sometimes described as sending a message to this
object.
Fields and methods of a class are collectively called its members, or, strictly
speaking, its non-static members; there are also static members: static fields and static
functions (methods).
• fields (non-static);
• static fields;
• constructors;
• methods (non-static);
• static functions (methods);
• static and non-static initializer blocks;
• definitions of other classes (the so called inner classes) or enumerations.
Objects (instances) of classes are always created on the heap (in free memory). They
are always anonymous — there is no way to give a name to an object. It is also
not possible to create an object locally on the stack — only references can be local
and have names. Operator new creates an object and returns a reference (which in
C/C++ corresponds to a pointer, i.e., address) to the object created. We can store
this reference (address) in a named reference variable. If there are no references to
an object left, the object may be removed by the garbage collector sub-process of the
JVM (although we don’t know if and when it will happen).
In the example below, we define a new type (class) TrivPoint, which is supposed
to describe points on a plane:
• x and y are (non-static) fields. Each object of the class will contains two such
ints — of course their values may be different for each object (point).
59
• translate, scale, getX, getY, setX, setY and info are (non-static) methods;
• infoStatic is a static function (static method).
There are no static fields here. Also, there is no constructor defined, but in fact there
is one, called default constructor, created by the compiler (more about constructors in
a moment).
Listing 31 BGO-TrivPoint/TrivPoint.java
1 public class TrivPoint {
2 public int x, y;
3
14 // setters
15 public void setX(int x) {
16 this.x = x; // this required
17 }
18 public void setY(int yy) {
19 y = yy; // this.y assumed
20 }
21
22 // getters
23 public int getX() {
24 return this.x;
25 }
26 public int getY() {
27 return y; // this assumed
28 }
29
30 // static
31 public static void infoStatic(TrivPoint p) {
32 System.out.println("[" + p.x + "," + p.y + "]");
33 }
34
35 // non-static
36 public void info() {
37 System.out.println("[" + this.x + "," + y + "]");
38 }
39 }
The class Main (below) by itself doesn’t serve any purpose — it is just a wrapper for the
main function which must be defined (with a signature exactly as shown) somewhere
60
and is the entry point to any Java application. In main we create an object of type
TrivPoint; it will contain fields x and y — we could have created several objects of this
type: each would contain ‘its own’ fields x and y, independent of x and y of any other
object of the same class. Note the syntax used to create an object: we have to write
round parentheses, as when calling a function. In parentheses, we can pass arguments
to a constructor. In our case, there is only the default constructor (by definition, it
doesn’t take any arguments) created by the compiler, as we haven’t defined any custom
constructor ourselves. Note that p is not the name of any object; it is the name of
a separate local reference variable whose value is the address of the object proper
(which itself is anonymous). In C++ we would call such a variable a pointer; in Java
it is called a reference (although is has nothing in common with references in C++.)
Listing 32 BGO-TrivPoint/Main.java
1 public class Main {
2 public static void main(String[] args) {
3 TrivPoint p = new TrivPoint();
4 p.x = 1;
5 p.y = 2;
6 p.info();
7 p.setX(3);
8 p.info();
9 System.out.println("x=" + p.getX() + "; " +
10 "y=" + p.getY());
11 TrivPoint.infoStatic(p);
12 p.infoStatic(p); // not recommended!
13
14 p.scale(2,3);
15 p.info();
16 p.translate(1,-3);
17 p.info();
18 }
19 }
Let us briefly explain the difference between static functions and methods (non-
static). The method scale seems to have two parameters. However, it’s a method
(there is no static keyword in its declaration). This means that it has one additional
parameter, not shown in the list of parameters (as it would be, e.g., in Python). This
‘hidden’ parameter is of type TrivPoint, i.e., it is a reference to an object of this type.
Therefore, scale has in fact three parameters and hence, invoking it, we have to specify
three arguments. Let’s look at its invocation in line 10
p.scale(2, 3);
Two arguments are given explicitly and they correspond to parameters sx and sy of the
method. What about the third? It will be the reference to the object on which the
method is invoked, in our case the value of the variable p which holds the reference
(address) to the object created in line 3. So, conceptually, the invocation is equivalent
to something like
TrivPoint::scale(p, 2, 3);
61
Therefore, methods are always called on a specific object (which, of course, must exist).
The reference to this object is passed (pushed on stack) to the method as one of the
arguments. Inside the body of a method we can refer to it — but what is its name
there? As it was not mentioned in the list of parameters, we were not able to give it
any name. Therefore, the name is fixed once for all and is this.
Now look at the definition of the method scale. We use the name x there. There are
variables sx and sy declared there, but no x, so compilation should crash. In such situ-
ation, however, the compiler will automatically add this in front of undeclared variable
(x in this case) to obtain this.x. But this means ‘field x of the object referenced to
by this’ — this is exactly the field x in the object the method was invoked on (i.e., the
one referenced to by p in the main function). The same, of course, applies to y.
The situation is different for static functions. Here, we don’t have any hidden param-
eters (therefore this doesn’t exist inside static functions). If a static function is to
have access to an object of type TrivPoint, the reference to this object must be passed
explicitly — as in function infoStatic in our example.
More details and more examples will be presented in the following sections.
Generally, fields of a class should be somehow protected from unrestricted access from
other classes (we call it hermetization, or encapsulation). They should be ma-
nipulated only by methods, which can ensure that they are operated upon in a safe
and consistent way. We have control over accessibility of members by adding, at the
beginning of their declaration, an appropriate keyword: private, protected, public or,
not specifying any of these, we get the default. They have the following meaning:
• default (or package private) — no special keyword — the field can be accessed
from functions (static or non-static) of the same class and also from functions of
all classes belonging to the same package;
• private — the field can be accessed only from functions of the same class;
• protected — the field can be accessed from functions of the same class, form
classes in the same package, and also from classes which extend this class (inherit
from it) even if they belong to a different package (inheritance will be covered
later);
• public — the field can be accessed from functions of all classes where our class
is visible.
The same rules apply to all members: also constructors and functions. It is recom-
mended to declare all fields (data members) as private (or protected). But then
a problem arises: if we do not provide public methods which modify the fields, it will
never be possible to assign any useful value to them! Of course, there is a way to do it
— by defining constructors (see the next section).
We can also declare whole classes as public or having default accessibility. If a class
is declared with the default accessibility, its name will be visible only in other classes,
but only those from the same package. There also exist the so called inner classes,
i.e., classes defined inside definition of other classes, which will be covered later. Such
classes may also be declared as private or protected.
The ‘entry’ class, the one which contains main, must always be declared as public
(and the main function itself must also be public).
62
8.4 Constructors and methods
Listing 33 BGI-VerySimple/VerySimple.java
1 public class VerySimple {
2 private int age;
3 private String name;
4
5 // constructor
6 public VerySimple(int age, String n) {
7 this.age = age;
8 name = n;
9 }
10 //getter
11 public int getAge() {
12 return age;
13 }
14 // setter
15 void setAge(int a) { // package private
16 age = a;
17 }
18 // getter (with no corresponding setter)
19 public String getName() {
20 return name;
21 }
22 }
and in Main we can create objects of this class, and even modify them (because both
constructor and setAge are not private or protected. Of course, in our main func-
tion, which is a function of another class, we cannot directly access the fields of class
VerySimple, but we can use public constructors and methods, which, being members
of the class VerySimple, do have access to all members of all objects of this class. In
particular, constructor is a very special function:
• its name must be the same as the name of the class;
• it does not declare any return type (even void);
• it will be automatically invoked only at the very end of the process of constructing
an object — there is no way to invoke it on an object which has already been
created before.
Normally, constructor are used to initialize fields of the object being created, but
generally they can do whatever we wish.
Listing 34 BGI-VerySimple/Main.java
1 public class Main {
2 public static void main(String[] args) {
3 VerySimple alice = new VerySimple(23,"Alice");
4 VerySimple bob = new VerySimple(21,"Bob");
63
5 alice.setAge(18);
6
7 System.out.println(
8 alice.getName() + " " + alice.getAge());
9 System.out.println(
10 bob.getName() + " " + bob.getAge());
11 }
12 }
To use a specific constructor during creation of an object, we just write, after the name
of the class, arguments — as many of them as expected by the constructor, and of
appropriate types. The program, as the previous one, prints
Alice 18
Bob 21
In a class, one can define many methods or constructors with the same name (in case
of constructors there is no way to avoid it, as all constructors have to be named as the
class they are defined in). This is called overloading. However, overloaded functions
must differ in number of parameters and/or these parameters’ types, so when they
are invoked, the compiler knows (by looking at arguments) which one is meant. The
precise rules used by the compiler to resolve which method or constructor should be
used in a given context are rather complicated. However, there should be no problem
if the differences are sufficiently obvious (different number of parameters, difference in
types like between String and int etc.).
If we don’t define any constructor, the compiler will add one — parameterless and doing
nothing. Then all fields will be initialized with their default values: zero for numeric
types, null for references, false for booleans. A parameterless constructor, whether
it is created by the compiler or defined by ourselves, is called default constructor.
However, the compiler will not create any default constructor if there is at least one
constructor in a class defined by us.
When a method is invoked, it must always be invoked on an object (in the example,
it was the object pointed to by reference alice). As we have already mentioned, the
reference (address) to this object is passed to the function as an additional, ‘hidden’
argument. As it is not mentioned on the parameter list of the function, its name is
once for all fixed: this. Inside the function, when we refer to a name (e.g., age) without
specifying of which object it is a member, it is understood that it is this.age that is
meant.
Any constructor can invoke — but only in its first line — another constructor of the
same class using this with arguments, as if it were the name of a method. Such
a constructor is said to be a delegating constructor. We can see an example in the
following program:
Listing 35 BGP-Point/Point.java
1 public class Point {
2 private int x, y;
3
64
6 x + " and " + y);
7 this.x = x;
8 y = yy;
9 }
10
11 public Point(int x) {
12 this(x,0);
13 System.out.println("Point(int) with " + x);
14 }
15
16 public Point() {
17 this(0);
18 System.out.println("Point()");
19 }
20
36 /**/
37 @Override
38 public String toString() {
39 return "[" + x + "," + y + "]";
40 }
41 /**/
42
53 p3.translate(4,4).scale(2,3).translate(-1,-5);
54
65
56 p1.getY() + "]");
57
After executing another constructor, the control flow returns to the invoking (delegat-
ing) constructor. It can be seen from the output of the above program
*** Creating point p1 (1,2)
Point(int,int) with 1 and 0
p1: [1,2]
Listing 36 BGR-BasicClass/Person.java
1 public class Person {
2
66
14
Listing 37 BGR-BasicClass/Main.java
1 import javax.swing.JOptionPane;
2
9 System.out.println(
10 "Two Persons created: " +john + " and " + mary);
11
18 // JOptionPane.showMessageDialog(null,s);
19 System.out.println(s);
20 System.exit(0);
21 }
22 }
67
Any class can also declare static members — both fields (data) and methods (but
not static constructors!) We can imagine a static members as belonging the class as
a whole, not to objects. As such, they can be used even if we haven’t created any
object of our class — they are brought into existence and initialized when the class is
loaded by the JVM (if the class happens to be an enum, after all the enum values have
been created).
Inside a static function there is no this, which normally exists and is the reference
to the object the function was invoked on — static functions are not invoked on any
object and consequently cannot use this. From the outside of a class, we refer to its
static members using just the name of the class. We can, although it’s very confusing,
use reference to any object of this class for the same purpose. For example, in the last
line of
// somewhere else
AnyClass.stat = 7;
AnyClass a = new AnyClass();
// ...
a.stat = 28;
we refer to static member stat just by putting a before the dot, but the compiler will use
only the information about the type of a — which particular object of class AnyClass
is used in this context is completely irrelevant. Looking at this line alone, one is not
able to tell whether stat is a static or non-static member of the class; for this reason it
is recommended to always use the first syntax, with the name of a class, as here, it is
obvious that stat must be static.
In the following example:
Listing 38 BHE-StatEx/StatExample.java
1 public class StatExample {
2 private static double rate = 1;
3 private static char ID = 'A';
4
11 StatExample(double a) {
12 id = ID++;
13 amount = a;
14 }
15
16 @Override
68
17 public String toString() {
18 return "I'm " + id + ", I have $" + amount +
19 " = " + rate*amount + " PLN";
20 }
21
Listing 39 AFL-FunRecur/SimpleRec.java
1 public class SimpleRec {
2
69
21 if (from == 0) System.out.println();
22 }
23
70
71 }
72
84 // GCD
85 System.out.println("Greatest common divisor of" +
86 " 5593 and 11067 is " + gcd(5593,11067));
87
88 // Factorials
89 System.out.println("10! = " + fact(10));
90 System.out.println("12! = " + fact(12));
91 // NO ERROR BUT WRONG!!!
92 System.out.println("13! = " + fact(13) + " WRONG!");
93
94 // Primes
95 System.out.println("Primes up to 100");
96 for (int n = 2; n <=100; ++n)
97 if (isPrime(n,2)) System.out.print(n+" ");
98 System.out.println();
99
13 3 55 7 9 11
11 9 7 55 3 13
3 7 9 11 13 55
55 13 11 9 7 3
Max. in a: 55
Greatest common divisor of 5593 and 11067 is 119
10! = 3628800
12! = 479001600
13! = 1932053504 WRONG!
71
Primes up to 100
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
Fibo(40) = 102334155; counter = 331160281
Fibo(42) = 267914296; counter = 866988873
Fibo(44) = 701408733; counter = 2269806339
Fibo(46) = 1836311903; counter = 5942430145
Inside the definition of a class, we can put static and non-static initialization blocks.
They have the form of a block of code enclosed in curly braces; in case of static
initializer block we add keyword static if front of it. The block must be put outside of
any functions!
The body of the non-static initializer block will be executed in the process of cre-
ating any object after the object itself has been created and after its members have
been initialized with default values but before entering a constructor. Therefore, we
can put into the initializer a code which should be executed at the beginning of any of
the overloaded constructors.
Static initializer block is executed only once: when a class is loaded by the JVM
(after creating enum constants, if the class happens to be an enum.) If a class contains
also static fields, initialization of static fields comes first, then initializer blocks are
executed in the order they appear in the definition. In a static block we can initialize
fields whose proper initialization requires some code to be executed.
The order of initialization can be seen from the output of the following program:
Listing 40 BHG-StatOrd/Stats.java
1 public class Stats {
2 private static int sino;
3 private static int siyes = 2;
4 private static final int fin;
5
16 public Stats() {
17 show(" constructor");
18 }
19
72
24 " fin=" + fin);
25 }
26 }
Listing 41 BHG-StatOrd/Main.java
1 public class Main {
2 public static void main(String[] args) {
3 Stats e = new Stats();
4 }
5 }
which outputs
static init: sino=0 siyes=2 fin=0
nonstatic init: sino=0 siyes=2 fin=3
constructor: sino=1 siyes=2 fin=3
In another, a little more complicated example
Listing 42 BHF-StatBlocks/StatBlocks.java
1 import java.io.BufferedReader;
2 import java.io.InputStreamReader;
3 import java.net.URL;
4 import java.util.Locale;
5 import java.util.regex.Matcher;
6 import java.util.regex.Pattern;
7 import java.util.stream.Collectors;
8 import javax.swing.JOptionPane;
9
73
27 bw.close();
28 Matcher m =
29 Pattern.compile(".*USD.*?(\\d+,\\d+)" +
30 ".*UAH.*?(\\d+,\\d+).*")
31 .matcher(txt);
32 m.matches();
33 rateUSD = Double.parseDouble(
34 m.group(1).replace(",","."));
35 rateUAH = Double.parseDouble(
36 m.group(2).replace(",","."));
37 } catch (Exception e) {
38 if (JOptionPane.showConfirmDialog(
39 null, "Fetching data failed; continue" +
40 " anyway with default rates = 1?",
41 "WARNING! FETCHING DATA FAILED!",
42 JOptionPane.YES_NO_OPTION
43 ) != JOptionPane.YES_OPTION
44 ) System.exit(1);
45 }
46 }
47
66 @Override
67 public String toString() {
68 return String.format(Locale.US,
69 "My id = %d. I have %5.2f " +
70 "PLN = $%5.2f = %6.2f UAH",id,
71 amount, amount/rateUSD,amount/rateUAH);
72 }
73
74
77 System.out.println("Current rates are:\n" +
78 " USD/PLN = " + rateUSD + '\n' +
79 " UAH/PLN = " + rateUAH);
80 StatBlocks sa = new StatBlocks();
81 StatBlocks sb = new StatBlocks(40);
82 StatBlocks sc = new StatBlocks(80);
83 System.out.println(sc + "\n" + sb + "\n" + sa);
84 }
85 }
initializing static fields rateUSD and rateUAH requires getting connection with the site
of the National Bank of Poland and fetching the current rates. It is done in static
initializer block. If fetching rates from the internet fails for some reason, we display
a warning asking the user if he/she wants to continue with all rates set to default
values 1. As in the previous example, each created object gets unique identifier id; this
time we do it in non-static initializing block. Here, to avoid identifiers ending with 13,
what might attract a disaster, we skip such numbers, what requires some code to be
executed. As we do it in the initializer block, we don’t have to repeat the same code in
all constructors, what could be easily forgotten about when adding other constructors.
The program prints something like (the result depends on current rates):
Current rates are:
USD/PLN = 4.5066
UAH/PLN = 0.1258
My id = 3. I have 80.00 PLN = $17.75 = 635.93 UAH
My id = 2. I have 40.00 PLN = $ 8.88 = 317.97 UAH
My id = 1. I have 20.00 PLN = $ 4.44 = 158.98 UAH
Very often we encounter the situation when we have a class of which at most one
object should ever be created; such objects are called singletons. There are at least
two approaches to produce such objects. If the object in question is expensive to create
and it is possible that it will not be needed at all, we can use lazy evaluation approache:
Listing 43 BGX-Singlet/Connect.java
1 public class Connect {
2
5 private Connect()
6 { } // private - no one can create any other object
7
75
14 }
15 }
Or, if we know that such an object almost surely will be required, one can use eager
evaluation:
Listing 44 BGX-Singlet/Config.java
1 public class Config {
2 // eager evaluation
3 private final static Config config = new Config();
4
The program convinces us that there is only one object of each of the classes. Note,
however, that this will be a lot more complicated in the face of multithreading!
Listing 45 BGX-Singlet/Main.java
1 public class Main {
2 public static void main(String[] args) {
3 Connect con1 = Connect.getInstance();
4 Connect con2 = Connect.getInstance();
5 if (con1 == con2) System.out.println("con1==con2");
6 Config cnf1 = Config.getInstance();
7 Config cnf2 = Config.getInstance();
8 if (cnf1 == cnf2) System.out.println("cnf1==cnf2");
9 }
10 }
con1==con2
cnf1==cnf2
76
Section 9
Basic data structures
Computer programs are all about processing information. The amount of information
that has to be handled is very often really huge and grows rapidly from year to year.
Therefore, it is very important to organize the data in such a way that processing it is
fast and efficient. Generations of scientists have been working (and are still working)
on that problem. Generally, designing a way in which the data is structurized is related
to the way in which it will be used; both aspects are studied by the branch of computer
science called Algorithms and Data Structures.
One important, and very useful, data structure is already known to us: arrays. Rep-
resenting data in the form of arrays has many advantages, perhaps the most important
being the speed of access to individual elements (using indices), which is “immediate”
(O(1)) and does not depend on the size of the array. The downside, however, is the
fact that the size of arrays must be fixed at their creation and no elements can be then
“deleted” or added.
In this section, we will mention just a few most fundamental data structures based
on lists (ordered sequences of elements): lists as such, queues and stacks. Of course,
there are many other equally useful structures – like dictionaries (maps), trees, graphs
etc. — you will study them later, although we will say a few words about maps in the
section on collections in Java.
A singly linked list represents a sequence of pieces of data of a certain type (in an
extreme case, references to object of type Object which may represent anything).
Each such piece of data is “wrapped” in an object of some type (conventionally called
Node) as its field, the other field being the reference to the next node of the list. In
this way, we can build a chain of nodes, where each node contains information about
the next. We need some kind of a marker which will mark the last node as being the
last: for example, we can adopt the convention that the last node’s next field is null.
Thus, the situation looks like this:
head null
Note that having the reference to the first node (conventionally called head), we can
access all the nodes, because in each of them we will find the reference to the next one.
Let us consider an example. The class Node represents a node containing data –
in this case just an int – and a reference to the next node:
77
Listing 46 BGU-SingList/Node.java
1 // could be private static inner class of MyList
2 public class Node {
3 int data;
4 Node next;
5 Node(int data, Node next) {
6 this.data = data;
7 this.next = next;
8 }
9 Node(int data) {
10 this(data,null);
11 }
12 }
The class representing the whole list will contain only one field: the head of the list,
i.e., the reference to its first node. We will add methods which add new nodes at the
beginning (addFront) and at the end (addBack) of the list. Notice that adding a node
at the end requires traversing the whole list. Also, to count elements of the list (the
size method), we have to traverse the whole list.
The showListReversed method prints the list in the reverse order by calling private,
recursive function showRev. The main idea here is that the function first calls itself
for the next node (if it exists), and only then, when the flow of control returns to it,
prints information on this element.
Listing 47 BGU-SingList/MyList.java
1 public class MyList {
2 private Node head;
3
4 public MyList() {
5 head = null;
6 }
7
78
24 Node tmp = head;
25 while(tmp != null) {
26 System.out.print(tmp.data + " ");
27 tmp = tmp.next;
28 }
29 System.out.println("]");
30 }
31 public void showListReversed() {
32 System.out.print("[ ");
33 showRev(head);
34 System.out.print("]");
35 }
36 private void showRev(Node h) {
37 if (h.next != null) showRev(h.next);
38 System.out.print(h.data + " ");
39 }
40 public int size() {
41 // inefficient!
42 int count = 0;
43 Node tmp = head;
44 while(tmp != null) {
45 ++count;
46 tmp = tmp.next;
47 }
48 return count;
49 }
50 public boolean empty() {
51 return head == null;
52 }
53 }
Listing 48 BGU-SingList/Main.java
1 public class Main {
2 public static void main(String[] args) {
3 MyList list = new MyList();
4 list.addBack(4);
5 list.addBack(5);
6 list.addFront(3);
7 list.addFront(2);
8 list.addFront(1);
9 list.showList();
10 list.showListReversed();
11 System.out.println("\nsize = " + list.size());
12 }
13 }
79
The program prints
[ 1 2 3 4 5 ]
[ 5 4 3 2 1 ]
size = 5
Of course, for our list to be useful, we would have to add more methods: for example
to insert new node in the middle of the list, to remove its elements etc.
9.2 Stacks
A stack is a very simple but extremely useful data structure. It provides three opera-
tions
• push — adding new element;
• pop — removing one element (and getting its value);
• empty — checking if the stack is empty.
However, adding (pushing) and removing (popping) elements must be organized in
such the way that what we pop is always what we pushed most recently. For example,
if we push first A, then B and then C, three consecutive “pops” will give us first C,
then B and lastly A. A structure organized in this way is called LIFO (last in, first
out).
A stack may be easily implemented as a singly linked list: we always add (push)
an element at the front (so it becomes the new head) and pop elements also from the
front. Note that traversing the list will never be necessary!
Let us consider an example. Wrapping class Node looks the same as before
Listing 49 BGW-Stack/Node.java
1 // could be private static inner class of MyStack
2 public class Node {
3 private int data;
4 private Node next;
5 Node(int data, Node next) {
6 this.data = data;
7 this.next = next;
8 }
9 Node(int data) {
10 this(data,null);
11 }
12 int getData() { return data; }
13 Node getNext() { return next; }
14 }
Listing 50 BGW-Stack/MyStack.java
1 public class MyStack {
2 private Node head = null;
80
3 public void push(int data) {
4 head = new Node(data,head);
5 }
6 public int pop() {
7 int d = head.getData();
8 head = head.getNext();
9 return d;
10 }
11 public boolean empty() {
12 return head == null;
13 }
14 }
Listing 51 BGW-Stack/Main.java
1 public class Main {
2 public static void main(String[] args) {
3 MyStack st = new MyStack();
4 st.push(4);
5 st.push(5);
6 st.push(6);
7 while (!st.empty())
8 System.out.println("popping " + st.pop());
9 }
10 }
popping 6
popping 5
popping 4
The output shows that, indeed, what we have pushed last (value 6) has been popped
first.
Listing 52 BGZ-ArrStack/ArrStack.java
1 public class ArrStack {
2 private int[] elems;
81
3 private int top = 0;
4 public ArrStack(int maxSize) {
5 elems = new int[maxSize];
6 }
7 public void push(int e) {
8 // throws if stack full
9 elems[top++] = e;
10 }
11 public int pop() {
12 // throws if stack empty
13 return elems[--top];
14 }
15 public boolean empty() {
16 return top == 0;
17 }
18 }
Listing 53 BGZ-ArrStack/Main.java
1 public class Main {
2 public static void main (String[] args) {
3 ArrStack stack = new ArrStack(5);
4 for (int i = 1; i <= 5; ++i)
5 stack.push(i*i);
6 while (!stack.empty())
7 System.out.print(stack.pop() + " ");
8 System.out.println();
9 }
10 }
and we get
25 16 9 4 1
Note that we do not handle the situations when an attempt is made to pop from an
empty stack or to push onto a full stack: an exception will be thrown anyway, as an
out-of-bounds index will then be used.
9.3 Queues
A queue is similar to a stack, but with the reversed order of “pops”: now, what we pop
is not the most recent element pushed, but the “oldest” — this is called FIFO (first in,
first out). For example, if we enqueue first A, then B and then C, three consecutive
“dequeues” will give us first A, then B and finally C.
A very simple implementation is again based on a singly linked list: we always add ele-
ments at the end and remove them from the beginning. This will not require traversing
82
the list, if we always keep the reference to the last element (called tail in the example
below).
Let us consider an example. The Node class is now a nested class: it is declared
as static and private class inside the MyQueue class, so it will be invisible for the
user but can be used inside the enclosing class MyQueue (we could have done it in
previous examples as well):
Listing 54 BGY-SimpQueue/MyQueue.java
1 public class MyQueue {
2 // nested class
3 private static class Node {
4 String data;
5 Node next = null;
6 Node(String d) { data = d; }
7 }
8
11 public MyQueue() {
12 head = tail = null;
13 }
14 public void enqueue(String s) {
15 if (head == null)
16 head = tail = new Node(s);
17 else
18 tail = tail.next = new Node(s);
19 }
20 public String dequeue() {
21 String s = head.data;
22 if ((head = head.next) == null) tail = null;
23 return s;
24 }
25 public boolean empty() {
26 return head == null;
27 }
28 }
Listing 55 BGY-SimpQueue/Main.java
1 public class Main {
2 public static void main (String[] args) {
3 MyQueue qS = new MyQueue();
4 for (double d = 0.5; d < 5; d += 1)
5 qS.enqueue("" + d);
6 while (!qS.empty())
7 System.out.print(qS.dequeue() + " ");
83
8 System.out.println();
9 }
10 }
so the elements are printed in the same order in which they have been enqueued.
84
Section 10
Strings and StringBuilders
The type String is an object type (not a primitive type). This means that there is no
way go give a string a name — we always operate on strings using references to strings
(variables holding, as their values, addresses of objects allocated on the heap).
There are several ways of creating objects of type String (and references to them). As
String is so often used, there exist some syntactic simplifications when dealing with
objects of this type. Normally, objects are created using new operator, but for Strings
we can use:
String s = "Pernambuco";
or, if we have an int named a,
String s = "a = " + a;
Of course, we can also use ‘normal’ form, for example
String s = new String("Pernambuco");
A string can also be created from an array of characters:
char[] arr = {'A', 'B', 'C'};
String s = new String(arr);
and in a few other ways (see documentation).
Objects of type String are objects (as opposed to variables of primitive types), so we
can invoke methods on them. They will never modify an existing string (in particular
the one a method is invoked on), but many such methods return the reference to a newly
created objects, created on the basis of the original one. Examples — out of many, see
documentation — include (we assume that s is the reference to a String object):
• s.length() — returns the size (number of characters) of s ("ABC".length()
is 3) — note that this is a method, not a field as for arrays;
• s.equals(anotherstring) — returns true if and only if the string s is equal
(contains the same characters) as anotherstring; this is the correct way to compare
strings for equality — note that s == anotherstring or s != anotherstring
compares addresses of objects, not their contents;
• s.equalsIgnoreCase(anotherstring) — as equals, but the case of characters
is ignored (’A’ and ’a’ are considered equivalent);
• s.trim() — returns the reference to a newly created string which is as s but
with all leading and trailing white spaces removed (" ABC " → "ABC");
• s.toLowerCase(), s.toUpperCase() — return the reference to a newly created
string which is as s but with all upper- (lower-) case letters replaced by lower-
(upper-) case ones — non-letters are not modified ("aBC".toLowerCase() →
"abc");
• s.substring(from, to) — returns the reference to a newly created string which
is a substring of s from character with index from (inclusive) to character with
index to exclusive (indexing starts with 0), for example "abcd".substring(1,3)
→ "bc";
85
• s.substring(from) — equivalent to s.substring(from, s.length());
• s.charAt(ind) — returns (as value of type char) the character with index ind
(indexing starts with 0), for example "abcd".charAt(2) → ’c’;
• s.contains(anotherstring) — returns true if s contains as its substring the
string anotherstring, and false otherwise;
• s.startsWith(anotherstring), s.endsWith(anotherstring) — returns true
if s starts (ends) with the substring anotherstring, and false otherwise;
• s.indexOf(anotherstring), s.indexOf(char) — returns the first index where
the substring anotherstring (character char) occurs in s, or −1 when such substring
(character) doesn’t occur in s ("abcdcd".indexOf("cd") → 2);
• s.lastIndexOf(anotherstring), s.lastIndexOf(char) — returns the last in-
dex where the substring anotherstring (character char) occurs in s, or −1 when
such substring (character) doesn’t occur in s ("abcdcd".lastIndexOf("cd") →
4);
• s.indexOf(anotherstring, ind), s.indexOf(char, ind) — as
s.indexOf(anotherstring) and s.indexOf(char), but searching starts at in-
dex ind;
• s.lastIndexOf(anotherstring, ind), s.lastIndexOf(char, ind) — as
s.lastIndexOf(anotherstring) and s.lastIndexOf(char) – searching starts
at index ind;
• s.toCharArray() — returns the reference to an array of characters (char[]) of
length s.length() and containing individual characters of the string s;
• s.split(regex) — returns the reference to an array of strings (String[]) con-
taining substrings of s where regex is treated as a separator between elements.
This is a regex (regular expression) which we don’t know about yet: very often
you can use a string containing a single character, or "\\s+" which means any
nonempty sequence of white characters. For example, if s is the reference to string
"aa:bb:cc", then s.split(":") will give us an array of Strings containing three
elements: "aa", "bb" and "cc".
Listing 56 BHH-Strings/Strings.java
1 public class Strings {
2 private static void pr(String m, String a, String b) {
3 System.out.println(m + ": \"" + a +
4 "\" -> \"" + b + "\"");
5 }
6 public static void main (String[] args) {
7 // length()
8 String a = " Shakespeare ";
9 String b = a.trim();
10 System.out.println("a.length() -> " + a.length());
11 System.out.println("b.length() -> " + b.length());
12 pr(" trim()", a, b);
13
14 // substring
15 a = "abcdefgh";
16 b = a.substring(3,6);
86
17 pr("substring(3,6)", a, b);
18 b = a.substring(3);
19 pr(" substring(3)", a, b);
20
21 // toUpperCase, toLowerCase
22 b = a.toUpperCase();
23 pr(" toUpperCase()", a, b);
24
25 // split
26 String[] arr = "ONE:TWO:THREE".split(":");
27 for (String d : arr)
28 System.out.print(d.toLowerCase() + " ");
29 System.out.println();
30
36 // charAt
37 String ny = "New York";
38 for (int i = 0; i < ny.length(); ++i)
39 System.out.print(ny.charAt(i) + " ");
40 System.out.println();
41
42 // toCharArray
43 char[] ca = ny.toCharArray();
44 for (int i = ca.length-1; i >= 0; --i)
45 System.out.print(ca[i]);
46 System.out.println();
47
48 }
49 }
which prints
a.length() -> 15
b.length() -> 11
trim(): " Shakespeare " -> "Shakespeare"
substring(3,6): "abcdefgh" -> "def"
substring(3): "abcdefgh" -> "defgh"
toUpperCase(): "abcdefgh" -> "ABCDEFGH"
one two three
ONE TWO THREE
N e w Y o r k
kroY weN
87
The fact that Strings are immutable has many advantages but the downside is that
manipulating strings always involves creation of new objects of type String. For ex-
ample, when we want to append something to a string using s=s+"something", we
actually do not append anything to an existing string; we just create a completely
new object and the reference to this new object assign back to s erasing its previous
contents (but the string that was referenced to by s before still exists on the heap in
memory).
Problems related to string being immutable are addressed by the class StringBilder.
Object of this type are similar to String objects but are modifiable. Internally, they
allocate an array of characters and operate on it; when it becomes too small, another,
twice as big array, is allocated and all existing elements are copied to it. As at each
reallocation much bigger array is allocated, its size grows rapidly and hence not many
such reallocations will ever be required. Probably the most useful methods of String-
Bilder are append, which appends to a string another string, insert, which inserts
a string on an arbitrary position into an existing string; as the mater of fact almost
anything can be inserted, in particular numbers and also objects (in that case, the
result of invocation of toString will be inserted). Also, the method delete is often
useful: it can remove a fragment of the string. Invoking toString on a StringBuilder
object gives us the final, immutable String object. Many of the methods mentioned
above return this object, so their invocations may be chained, as in the example below:
Listing 57 BHI-StrBuilder/StrBuilder.java
1 public class StrBuilder {
2 public static void main(String[] args) {
3 StringBuilder sb = new StringBuilder("three");
4 sb.append(",four")
5 .insert(0,"twoxx,")
6 .insert(0,"one,")
7 .delete(sb.indexOf("x"), sb.indexOf("x")+2);
8 System.out.println("sb = " + sb.toString());
9
13 startTime = System.nanoTime();
14 String s = "0";
15 for (int i = 1; i <= NUMB; ++i)
16 s = s + i;
17 long elapsedS = System.nanoTime() - startTime;
18 System.out.printf(" String: %10d ns = %.3f sec%n",
19 elapsedS, elapsedS*1e-9);
20
21 startTime = System.nanoTime();
22 StringBuilder builder = new StringBuilder("0");
23 for (int i = 0; i <= NUMB; ++i)
24 builder.append(i);
25 long elapsedB = System.nanoTime() - startTime;
26 System.out.printf("Builder: %10d ns = %.3f sec%n",
27 elapsedB, elapsedB*1e-9);
88
28
29 System.out.printf("elapsedS/elapsedB = %.2f%n",
30 (double)elapsedS/elapsedB);
31 }
32 }
sb = one,two,three,four
String: 735807986 ns = 0,736 sec
Builder: 2263932 ns = 0,002 sec
elapsedS/elapsedB = 325,01
which shows that operations on StringBilder objects can be orders of magnitude faster
than analogous operations on Strings.
89
Section 11
Introduction to inheritance
Inheritance applies to situations when one type describes objects which behave as
objects of another (‘base’) type but in a different way or they possess some additional
properties. For example dog is an animal, so a class describing dogs may inherit from
(extend) class describing animals. This is called ‘is-a’ relation: dog is-a animal (but not
necessarily the other way around — not all animals are dogs). By using inheritance,
we can reuse the code written in the base class (Animal) and not repeat it in class
Dog. What is more important, however, everywhere where an animal is expected (any
animal: maybe dog, maybe cat), we can use a dog, because dog is-a animal.
In particular, you can create an object of the derived class (Dog) and assign the
address (reference) obtained from new to a reference variable declared as having the
type of the base class (Animal). In such a situation, we say that the static (declared)
type of the refernce is Animal, but the dynamic (‘real’) type is Dog. Through this
reference, the compiler will allow you to use all methods from Animal, but not those
which exist in Dog but are not inherited from Animal. However, if, in class Dog, you
have overridden (redefined) a method inherited from Animal, then at runtime this
redefined (coming from Dog) method will be used even though you refer to the object
by a reference whose static type is Animal — this is called “late binding” and is the
essence of polymorphism. Methods with such behavior are called virtual — in Java,
therefore, all methods are virtual. The only exception is a method declared as final:
any attempt to override such final method will fail.
Any class can directly extend (inherit from) only one base class (also called its
superclass). If, defining a class, we do not specify its superclass, it is assumed that
it inherits from a special class Object; it follows, that every class inherits directly
or indirectly, from Object. Class Object defines a few methods which are therefore
inherited (and, consequently, exist) in any class. One important example of such
a method is toString (shown in the example below). Its purpose is to return a String
describing somehow the object it was invoked on. Usually, we redefine toString in our
classes: otherwise the inherited version will be used, what will always succeed but may
return a rather useless result. When we redefine (override) an inherited method, it
is recommended (although not required) to annotate this redefinition with Override,
as in the example below; this makes our intention explicit to the compiler which can
therefore verify if indeed the declaration of the method is correct. Also, note the use
of instanceof operator:
a instanceof AClass
answers (at runtime) the question is the object referred to by a of type AClass or any of
its subclasses. We can also find or compare types of objects refereed to by a reference
using getClass method (which is defined in Object, so all classes inherit it). It returns
an object of class Class which represents the real type of the object it was invoked
on: such a single object is created for each class loaded by the JVM. As there is only
one such object for any class, one can use == operator to compare types of any two
objects by using the objects of class Class representing their types.
Let us consider an example: we define a simple class Point
90
Listing 58 DJV-Inherit/Point.java
1 public class Point {
2
3 protected int x, y;
4
10 public Point(int x) {
11 this(x,0);
12 }
13
14 public Point() {
15 this(0,0);
16 }
17
38 @Override
39 public String toString() {
40 return "[" + x + "," + y + "]";
41 }
42 }
and then class Pixel which extends Point (all pixels are points, but they have also
a color):
91
Listing 59 DJV-Inherit/Pixel.java
1 import java.awt.Color;
2
20 public Pixel() {
21 this(0,0,Color.BLACK);
22 }
23
24 // n o t inherited
25 public Color getColor() {
26 return color;
27 }
28
29 @Override
30 public String toString() {
31 return super.toString() + "(r=" + color.getRed() +
32 ",g=" + color.getGreen() + ",b=" +
33 color.getBlue() + ")";
34 }
35 }
Listing 60 DJV-Inherit/Main.java
1 import java.awt.Color;
2
92
9 System.out.println("is 'pp' a Point? " +
10 (pp instanceof Point));
11 System.out.println("is 'pp' a Pixel? " +
12 (pp instanceof Pixel));
13 System.out.println("is 'pt' a Point? " +
14 (pt instanceof Point));
15 System.out.println("is 'pt' a Pixel? " +
16 (pt instanceof Pixel));
17 System.out.println("is 'px' a Point? " +
18 (px instanceof Point));
19 System.out.println("is 'px' a Pixel? " +
20 (px instanceof Pixel));
21 System.out.println("class of pp: " +
22 pp.getClass().getName());
23 System.out.println("class of pt: " +
24 pt.getClass().getName());
25 System.out.println("class of px: " +
26 px.getClass().getName());
27 // Pixel is a Point; we can translate or scale it
28 px.translate(5,4).scale(2,3).translate(-1,-3);
29 System.out.println("pp: " + pp);
30 System.out.println("pt: " + pt);
31 System.out.println("px: " + px);
32 System.out.println("Color px : " + px.getColor());
33 // casting required!
34 System.out.println("Color pt : " +
35 ((Pixel)pt).getColor());
36 }
37 }
93
only the static type is taken into consideration at compile time — as there is no
getColor method in Point, the compilation would fail. Therefore, we have to cast the
pt reference (see the last line of the program). By writing ((Pixel)pt), we are saying
to the compiler ‘this is just a Point for you, but believe me, I know and I promise you
that its real type is Pixel’. The compiler cannot check it, but at runtime the program
will crash if it turns out that the object pointed to by pt is in fact not a Pixel.
Listing 61 DJW-InherAnimal/Animal.java
1 public class Animal {
2 protected String name;
3 protected double weight;
4 // no default constructor!
5 public Animal(String n, double w) {
6 name = n;
7 weight = w;
8 }
9 public String getVoice() {
10 return "?";
11 }
12 public static void voices(Animal[] animals) {
13 for (Animal a : animals)
14 System.out.println(a + " " + a.getVoice());
15 }
16 @Override
17 public String toString() {
18 return name + "(" + weight + ")";
19 }
20 }
which defines the getVoice method. It also defines a static method voices which
traverses an array of (references to) Animals (not knowing what the real type of objects
pointed to by the elements of the array are). However, when calling toString or
getVoice, the methods from the real type will be selected (therefore, it is a polymorphic
invocation).
We then create two classes which extend Animal: class Dog
Listing 62 DJW-InherAnimal/Dog.java
1 public class Dog extends Animal {
2 public Dog(String name, double weight) {
3 super(name, weight);
4 }
5 @Override
6 public String getVoice() {
7 return "Bow-Wow";
8 }
9 @Override
94
10 public String toString() {
11 return "Dog " + super.toString();
12 }
13 }
Listing 63 DJW-InherAnimal/Cat.java
1 public class Cat extends Animal {
2 public Cat(String name, double weight) {
3 super(name, weight);
4 }
5 @Override
6 public String getVoice() {
7 return "Miaou-Miaou";
8 }
9 @Override
10 public String toString() {
11 return "Cat " + super.toString();
12 }
13 }
In both classes, we override (redefine) the getVoice method. Note that there is no
default constructor in the base class Animal – therefore, using super in constructors
of the derived classes is obligatory.
Now in another class
Listing 64 DJW-InherAnimal/Main.java
1 public class Main {
2 public static void main (String[] args) {
3 Animal[] animals = {
4 new Dog("Max", 15), new Cat("Batty", 3.5),
5 new Dog("Ajax", 5), new Cat("Minnie", 4)
6 };
7 Animal.voices(animals);
8 }
9 }
we create an array of various animals — some are dogs, some are cats — and pass it
to the voices method from Animal. As we can see from the output
Dog Max(15.0) Bow-Wow
Cat Batty(3.5) Miaou-Miaou
Dog Ajax(5.0) Bow-Wow
Cat Minnie(4.0) Miaou-Miaou
invocations in System.out.println(a + " " + a.getVoice()) inside the voices method
are polymorhic: both toString and getVoice have been taken from the real type of
95
objects pointed to by elements of the array animals, although the declared type of these
elements was Animal and there was no way for the compiler to know their real type.
Listing 65 AAR-EquToString/EquToString.java
1 public class EquToString {
2 public static void main(String[] args) {
3 PersonGood johny = new PersonGood("John",1980);
4 PersonGood john = new PersonGood("John",1980);
5 PersonBad billy = new PersonBad("Bill",1980);
6 PersonBad bill = new PersonBad("Bill",1980);
7
8 if (johny.equals(john))
96
9 System.out.println("johny == john");
10 else
11 System.out.println("johny != john");
12
13 if (billy.equals(bill))
14 System.out.println("billy == bill");
15 else
16 System.out.println("billy != bill");
17
23 class PersonBad {
24 private String name;
25 private int byear;
26 PersonBad(String n, int y) {
27 name = n;
28 byear = y;
29 }
30 public String getName() { return name; }
31 public int getYear() { return byear; }
32 }
33
34 class PersonGood {
35 private String name;
36 private int byear;
37 PersonGood(String n, int y) {
38 name = n;
39 byear = y;
40 }
41
45 @Override
46 public String toString() {
47 return name + "(" + byear + ")";
48 }
49
50 @Override
51 public boolean equals(Object ob) {
52 if (ob == null || getClass() != ob.getClass()) return false;
53 PersonGood p = (PersonGood)ob;
54 return name.equals(p.name) && byear == p.byear;
55 }
56 }
97
The program prints
johny == john
billy != bill
johny: John(1980)
billy: PersonBad@659e0bfd
As we can see, with toString and equals methods not overriden (as in class Person-
Bad), the program works but uses the versions of these methods from class Object.
To get the expected results, we have to override these methods, as we did in class
PersonGood.
The next example illustrates the effect of overriding the hashCode method:
Listing 66 HUM-HashEquals/Person.java
1 public class Person {
2
11 /*
12 @Override
13 public boolean equals(Object other) {
14 if (other == null ||
15 getClass() != other.getClass()) return false;
16 Person p = (Person)other;
17 return idNumber.equals(p.idNumber) &&
18 name.equals(p.name);
19 }
20 /**/
21
22 /*
23 @Override
24 public int hashCode() {
25 return 17*name.hashCode() + idNumber.hashCode();
26 }
27 /**/
28
29 @Override
30 public String toString() {
31 return name + "(" + idNumber + ")";
32 }
33 }
98
Listing 67 HUM-HashEquals/AHash.java
1 import java.util.HashMap;
2 import java.util.Map;
3
8 map.put(new Person("Sue","123456"),"Sue");
9
14 if (map.containsKey(sue))
15 System.out.println(sue + " has been found");
16 else
17 System.out.println(sue + " has NOT been found");
18 }
19 }
99
Section 12
Exceptions
Exceptions are situations when program does not know what to do next. For example,
we say read data from this file, but there is no file to read from. Or, we try to print an
element of an array with a given index, but this index is negative or bigger then the
size of our array, so the required element doesn’t exist. In all such situations, normal
flow of control is disrupted and an exception is thrown: the JVM creates an object
representing the error and checks, if we have supplied a code to somehow handle this
kind of error (we will see in a moment, how to supply such code).
All possible exceptions fall into one of two categories:
• Checked exceptions: these are exceptions that must be somehow dealt with by
our program, otherwise the program will not even compile. We can deal with
checked exceptions
1. by using try-catch blocks;
2. by declaring the function in which a given exception may be thrown as
throwing it — then the caller of the function will have to handle it.
• Unchecked exceptions: these are exceptions that we can, but not have to, handle.
If we don’t handle it and this kind of exception occurs, the program terminates
(after printing some information about the exception that has been encountered).
Exceptions are represented by objects of types extending Throwable (the top of the
hierarchy tree). It has two ‘children’, one of them, Exception, represents checked
exceptions, except the subtree rooted at RuntimeException, which are unchecked.
The second child subtree, rooted at Error, represents unchecked exceptions only used
internally by JVM; normally, they shouldn’t (although they can, if we insist) be handled
at all, as they indicate serious problems beyond our control. The general structure of
the Throwable class hierarchy looks like this (image taken from javamex page3 ), where
red color denotes subtrees of unchecked exceptions:
3
http://www.javamex.com
100
12.1 try-catch blocks
try {
// ...
} catch(ExcType1 ex) {
// handling the exception of type ExcType1
} catch(ExcType2 ex) {
// handling the exception of type ExcType2
}
// ... other catch blocks
The code in the try block is executed. If everything goes smoothly and there is no
exception, the catch blocks are ignored. However, suppose that at some point when
executing the code in the try block, an exception occurs. Then
Note that it doesn’t make sense to place a catch block corresponding to exceptions
of type Type2 after the catch block corresponding to Type1, if type Type2 extends
Type1. This is because exceptions of type Type2 would be ‘caught’ by the first catch,
making the second unreachable. For example, there is a class FileNotFoundException
which is derived (inherits, extends) from IOException. Something like
try {
// ...
} catch(FileNotFoundException e) {
// ...
} catch(IOException e) {
// ...
}
does make sense: if FileNotFoundException occurred, then the first catch will be
executed, if it was any other exception derived from IOException — the second one.
However
try {
// ...
} catch(IOException e) {
// ...
101
} catch(FileNotFoundException e) { // NO!!!
// ...
}
is wrong, because FileNotFoundException and all other exceptions derived form
IOException will be caught by the first catch; the second is unreachable and hence
useless.
It is possible for one catch block to handle two (or more) types of unrelated exceptions
(they are unrelated if none of them is a subtype of the other). We just specify these
types separating their names with a vertical bar (|); for example in
// ...
catch (IOException | SQLException e) {
// ...
throw e; // rethrowing exception
}
}
the catch will catch exceptions of type IOException and of type SQLException
as well (and their subclasses). This example also shows that handling an exception,
we can, after doing something, rethrow the same exception (or, as a matter of fact,
another one) to be handled elsewhere.
After all catch blocks, we can (but we don’t have to) use a finally block
try {
// ...
} catch(ExcType1 e) {
// ...
} catch(ExcType2 e) {
// ...
} finally {
// ...
}
The code in finally block will always be executed. If there is no error in the try block,
then all catch blocks will be ignored, but finally block will be executed; if there is an
error in the try block, then the corresponding catch block (if any) will be executed and,
afterwards, the finally block (even if there is a return statement in try and/or catch
blocks!) Finally blocks are very useful when we want to ensure that some resources
(open files, open internet or data-base connections, locks etc.) are released always —
whether an exception occurred or not. It is even possible to use a finally block when
no exception is anticipated. For example, the code below can be dangerous
// get resources
// ...
if ( condition1 ) return;
// ...
if ( condition2 ) return;
// ...
// release resources
102
because if any of conditions holds, the resources acquired at the beginning will not be
released. However, using finally
try {
// get resources
// ...
if ( condition1 ) return;
// ...
if ( condition2 ) return;
// ...
} finally {
// release resources
}
Sometimes we know that an exception (in particular, a checked one) can occur but we
don’t know how to handle it. Then we can declare the whole function as throwing this
kind of exception (or even, comma separated, exceptions of two or more types):
In this way we propagate the exception ‘up the stack’ to the caller (the function which
calls our fun), so there the invocation of fun will have to be handled
Of course, the caller function can itself be declared as throwing and then exceptions
will be propagated to the caller of caller, and so on. In this way, we can even propagate
exceptions up to the main function, which, although it is not recommended, itself may
be declared as throwing (propagating exceptions to the caller of main, i.e., the JVM
itself).
103
void setAge(int age) {
this.age = age;
}
We may decide that trying to set a negative age is a user’s error. We can signal such
invalid attempt by throwing an exception of some type. Java defines many types of
exceptions in its standard library — in this particular case IllegalArgumentException
would be probably most appropriate. We thus rewrite out method like this
As we said, many classes representing exceptions have already be written and are part
of the standard library. However, we can also create such classes ourselves. Normally, it
is very simple; all we have to do is to define a class extending a library class: a subclass
of RuntimeException if we want an unchecked exception or a subclass of Exception
if we want our class to represent a checked exception. All library exception classes have
two constructors: the default one and one taking a string representing a message that
can be queried on the object. So we can write
and that’s basically enough, although, of course, we can add more constructors, fields
and methods if we find it appropriate for our purposes. This is seldom necessary,
because the main message passed by an exception is just its type.
12.5 Examples
104
Listing 68 AXW-ScanExcept/ScanExcept.java
1 import java.io.IOException;
2 import java.nio.file.Paths;
3 import java.util.Scanner;
4
27 System.out.println("Enter anything...");
28 String s = console.next();
29 System.out.println("Read from console: " + s);
30
When the input file file.txt exists, reading it succeeds, the catch clause is not
entered, but the finally clause is executed:
1: Line 1
2: Line 2
3: Line 3
FINALLY executed
Enter anything...
something
Read from console: something
105
However, when the input file (file.txt) is missing, the FileNotFoundException is
thrown, variable scfile remains null and the catch clause is entered. The exception is
now considered handled, so the program continues (and the finally clause is executed
anyway):
Exception: java.nio.file.NoSuchFileException: file.txt
**** Now the stack:
java.nio.file.NoSuchFileException: file.txt
at java.base/sun.nio.fs.UnixException
.translateToIOException(UnixException.java:92)
at java.base/sun.nio.fs.UnixException
.rethrowAsIOException(UnixException.java:106)
at java.base/sun.nio.fs.UnixException
.rethrowAsIOException(UnixException.java:111)
at java.base/sun.nio.fs.UnixFileSystemProvider
.newByteChannel(UnixFileSystemProvider.java:218)
at java.base/java.nio.file.Files
.newByteChannel(Files.java:380)
at java.base/java.nio.file.Files
.newByteChannel(Files.java:432)
at java.base/java.nio.file.spi.FileSystemProvider
.newInputStream(FileSystemProvider.java:422)
at java.base/java.nio.file.Files
.newInputStream(Files.java:160)
at java.base/java.util.Scanner
.makeReadable(Scanner.java:622)
at java.base/java.util.Scanner.<init>(Scanner.java:759)
at java.base/java.util.Scanner.<init>(Scanner.java:741)
at ScanExcept.main(ScanExcept.java:13)
**** CONTINUING...
FINALLY executed
Enter anything...
something
Read from console: something
Exception in thread "main"
java.lang.AssertionError: apparently no file
at ScanExcept.main(ScanExcept.java:34)
At the end of the program, we used an assertion statement; we will say a few words
about this topic in a moment.
In the example below, we read data from the user and use (unchecked) exception
of type NumberFormatException to ensure that the data is valid:
Listing 69 AXX-TryCatch/TryCatch.java
1 import javax.swing.JOptionPane;
2
106
7
8 while (noGood) {
9 String s = JOptionPane.showInputDialog(
10 null,"Enter an integer","Entering data",
11 JOptionPane.QUESTION_MESSAGE);
12
13 if (s == null) break;
14 if (s.length() == 0) continue;
15 try {
16 number = Integer.parseInt(s);
17 noGood = false;
18 } catch(NumberFormatException e) {
19 JOptionPane.showMessageDialog(
20 null,"<html>This is not an integer!" +
21 "<br />Try again!!!",
22 "ERROR",JOptionPane.ERROR_MESSAGE);
23 }
24 }
25
26 System.out.println(
27 noGood ?
28 "Program cancelled" : "Entered: " + number);
29 }
30 }
The following example demonstrates the use of two different unrelated exceptions:
Listing 70 AYC-Excpts/Excpts.java
1 import javax.swing.JOptionPane;
2
8 while (noGood) {
9 String s = JOptionPane.showInputDialog(
10 null,"Enter two integers","Entering data",
11 JOptionPane.QUESTION_MESSAGE);
12
13 if (s == null) break;
14 s = s.trim();
15 if (s.length() == 0) continue;
16
107
21 "<br />Try again!!!",
22 "ERROR",JOptionPane.ERROR_MESSAGE);
23 continue;
24 }
25
26 try {
27 int n1 = Integer.parseInt(
28 s.substring(0,spac));
29 int n2 = Integer.parseInt(
30 s.substring(spac+1).trim());
31 number = n1/n2;
32 noGood = false;
33
34 } catch(NumberFormatException e) {
35 JOptionPane.showMessageDialog(
36 null,"<html>This were not integers!" +
37 "<br />Try again!!!",
38 "ERROR",JOptionPane.ERROR_MESSAGE);
39 System.err.println("EXCEPTION " +
40 e.getMessage() + '\n' + "STACK TRACE:");
41 e.printStackTrace();
42
43 } catch(ArithmeticException e) {
44 JOptionPane.showMessageDialog(
45 null,"<html>Division by zero!" +
46 "<br />Try again!!!",
47 "ERROR",JOptionPane.ERROR_MESSAGE);
48 System.err.println("EXCEPTION " +
49 e.getMessage() + '\n' + "STACK TRACE:");
50 e.printStackTrace();
51 }
52 }
53
54 System.out.println(
55 noGood ?
56 "Program cancelled"
57 : "Result of division: " + number);
58 }
59 }
The last example illustrates custom (user defined) exceptions: we define our own
type of exception:
Listing 71 AYN-CheckedExc/MyUncheckedException.java
1 public class MyUncheckedException
2 extends IllegalArgumentException {
3 MyUncheckedException() {
4 super();
108
5 }
6 MyUncheckedException(String message) {
7 super(message);
8 }
9 }
Listing 72 AYN-CheckedExc/CheckedExc.java
1 public class CheckedExc {
2
5 try {
6 goSleep(3*1000);
7 } catch(InterruptedException ignored) {
8 System.err.println("Interrupted");
9 } finally {
10 System.err.println("AFTER SLEEP");
11 }
12
109
40 throw new MyUncheckedException("Negative time");
41
42 System.out.println(
43 "Going to sleep for " + time +"ms");
44 Thread.sleep(time);
45 System.out.println("Waking up");
46 }
47 }
As we can see, the string "QUITTING" was printed by the finally clause even though
there was an unhandled exception and the program crushed.
12.6 Assertions
Another way of dealing with possible errors in our programs is by using assertions
(line 34 of the listing 68 on page 105). After the keyword assert, we specify a condition
(something with boolean value) and, after a colon, a message (which is in fact optional)
assert boolExp : message;
At runtime, the value of boolExp is evaluated and if found false, the program will print
the message and terminate by throwing an exception of type AssertionError. In our
example, this is what will happen when the file is missing and the variable scfile is null.
By default, assertions are disabled at runtime, but can be enabled by -ea switch4
when running a program. Because assertions are sometimes on and sometimes off,
there should be no side effects of using them. Normally, assertions check conditions
that we are almost sure should always hold. If, however, the condition is not met,
and an unchecked exception of type AssertionError is thrown, we should never even
try to handle it, because it indicates a serious flaw in the program that just must be
corrected.
4
It is also possible to enable/disable assertions selectively for certain packages or individual classes.
110
Section 13
IO streams primer
An IO stream can be viewed as a sequence of data (ultimately, these are always just
bytes) ‘flowing’ from a ‘source’ to a ‘sink’ (destination). All IO streams fall into two
categories
• output streams: data from our programs (values of variables, objects, arrays
etc.) are the “source” while the destination is a file, a socket, the console or even
a region in memory;
• input streams: our programs (variables, arrays etc.) is now the destination, while
the source might be a file, a socket, the keyboard, etc.
Java represents characters by their two-byte Unicode codes (the so called code
points). Therefore, there is a problem with reading and writing texts: any text is
written using some encoding, so Java must translate a byte or a sequence of bytes
corresponding to a character into a two-byte code point and vice versa. Therefore,
we have to distinguish byte (binary) streams, where bytes are treated just as bytes,
without any modifications, and text (also called character ) streams where bytes are
subject to some transformations, dependent of the encoding in use. Hence, we end up
with four types of IO streams:
• output byte (binary) streams: they all correspond to subclasses of the class
OutputStream (e.g., ByteArrayOutputStream, FileOutputStream, Ob-
jectOutputStream);
• output text streams: they correspond to subclasses of Writer (e.g., Buffered-
Writer, CharArrayWriter, OutputStreamWriter, PrintWriter, String-
Writer);
• input byte streams: they correspond to subclasses of InputStream (e.g., ByteAr-
rayInputStream, FileInputStream, ObjectInputStream, StringBufferIn-
putStream);
• input character streams: they correspond to subclasses of Reader (e.g., Buffere-
dReader, CharArrayReader, InputStreamReader, StringReader);
The main four classes mentioned above are abstract, what means that one cannot create
objects of these types, only objects of their concrete subclasses.
Any Java process, as other processes, is given by the operating system three stan-
dard streams. They are represented by objects which are static fields of the class
System
Both System.out and System.err are of type PrintStream which inherits (indirectly)
from OutputStream (but is designed to handle text output) while System.in is of
111
a type inheriting from InputStream (and is a binary stream). The difference between
System.out and System.err is that the former is buffered, while the latter is not. When
we write to a buffered stream, like System.out, we are in fact writing to a buffer which
will be eventually flushed to its destination (by default, to the screen) in larger chunks.
Sometimes such behavior is not desired, especially when we expect some exception
handling or when we write to a socket. Printing to unbuffered streams (like System.err)
is immediate — data is not stored in any buffer, it goes directly to the destination of
the stream.
All I/O operations5 may throw exceptions of types extending IOException and
are checked ; therefore, when using them, we have to handle possible failures somehow.
Let us consider the following example. In the simple program below, we first write,
in a loop, individual bytes of a long, starting with the most significant one, to the file.
Then we read these bytes back and reconstruct a long with the same value. Note the
special form of the try clause, the so called try-with-resources. In the round parentheses,
we create I/O streams and references to these streams. Note that they will be in scope
of the try clause. Normally, we have to remember to close all streams that have been
opened. The try-with-resources construct, however, takes care of closing streams no
matter what happened, even if an exception has been thrown. Therefore, no finally
clause is needed here.
Listing 73 KEP-WriteBin/WriteBin.java
1 import java.io.FileInputStream;
2 import java.io.FileOutputStream;
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6
22 long after = 0;
23 try (
24 InputStream is =
25 new FileInputStream("WriteBin.bin");
26 ) {
5
except those on PrintStream objects, which ‘consumes’ possible exceptions in its methods
112
27 for (int i = 0; i < 8; ++i)
28 after = (after << 8) | is.read();
29 } catch(IOException e) {
30 e.printStackTrace();
31 System.exit(1);
32 }
33 System.out.println("after = " + after);
34 }
35 }
Listing 74 BHJ-BytesChars/BytesChars.java
1 import java.io.InputStream;
2 import java.io.InputStreamReader;
3 import java.io.IOException;
4 import java.io.Reader;
5 import static java.nio.charset.StandardCharsets.UTF_8;
6
113
13 char c = ' ';
14 while (true) {
15 int i = is.read();
16 c = (char)i;
17 if (c == '\r' || c == '\n') break;
18 System.out.printf("%3d ('", i);
19 System.out.println(c + "') =>" +
20 " digit:" + Character.isDigit(c) +
21 " letter:" + Character.isLetter(c) +
22 " white:" + Character.isWhitespace(c));
23 }
24 } catch(IOException e) {
25 e.printStackTrace();
26 return;
27 }
28 // we don't close 'is' here, as this is the
29 // standard input and we will use it later
30 System.out.print("Type something again ==> ");
31 try (
32 Reader rd =
33 new InputStreamReader(System.in, UTF_8)
34 ) {
35 char c = ' ';
36 while (true) {
37 int i = rd.read();
38 c = (char)i;
39 if (c == '\r' || c == '\n') break;
40 System.out.printf("%#5x ('", i);
41 System.out.println(c + "') =>" +
42 " digit:" + Character.isDigit(c) +
43 " letter:" + Character.isLetter(c) +
44 " white:" + Character.isWhitespace(c));
45 }
46 } catch(IOException e) {
47 e.printStackTrace();
48 }
49 }
50 }
Running the program, we can see that in the first case multi-byte characters (as,
e.g., in Żółć) are not read correctly. This is because (at least under Linux) the text
from the console is in UTF-8 encoding, in which a single character can occupy 1, 2, 3,
or even four bytes. The stream System.in is, however, a byte (binary) stream, so each
read consumes one byte, which not necessarily corresponds to any character, as it may
be only a part of a multi-byte character.
The situation is different in the second case — here we ‘wrap’ System.in in Input-
StreamReader (which behaves as a text stream), passing also the correct encoding.
Object of this wrapper (decorator ) class behaves as a text stream, so each read con-
sumes one character, no matter how many bytes it takes.
114
Let us show now how one can read and write text files, what is a very common
task. This can be done in various ways, so consider the program below as an example,
not necessarily the best in all situations. Note that when dealing with text files, one
should always specify the encoding of all input/output files.
Listing 75 KFE-GrepNew/GrepNew.java
1 import java.io.BufferedReader;
2 import java.io.BufferedWriter;
3 import java.io.IOException;
4 import java.nio.file.Files;
5 import java.nio.file.Path;
6 import java.nio.file.Paths;
7 import static java.nio.charset.StandardCharsets.UTF_8;
8
23 try (
24 // UTF8 is the default in nio classes,
25 // but not in older io classes
26 BufferedReader br =
27 Files.newBufferedReader(filein, UTF_8);
28 BufferedWriter bw =
29 Files.newBufferedWriter(
30 Paths.get(oFileName), UTF_8))
31 {
32 String line;
33 int lineNo = 0;
34 while ( (line = br.readLine()) != null) {
35 ++lineNo;
36 if (line.indexOf(wordSearchedFor) >=0)
37 bw.write(String.format("Line %3d: %s%n",
38 lineNo,line));
39 }
40 System.out.println("Results written to " +
41 oFileName);
42 } catch(IOException e) {
43 System.out.println("Something wrong");
115
44 System.exit(1);
45 }
46 }
47 }
In the program above, we used classes from the java.nio package, which is newer than
java.io and usually recommended. Objects of class Path represent the names of files
in the current file system (not necessarily existing ones). We can create such objects
by calling the static factory method get and passing (absolute or relative) name of
a file. Then one can check if such a file exists, whether it is readable, executable, what
its size or last modification time is, etc.
Note also the form of the try clause. Here, we used again the try-with-resources
construct. Before the opening brace, in round parentheses, we create objects represent-
ing “resources” — in this case streams. These could be also other types or resources,
like data base connections; what is important is that they have to be closeable (in other
words, they have to implement the Closeable interface). If there are more than one,
as here, we separate them by a semicolon. As we remember, the benefit of this form
of the try clause is that we don’t have to bother with closing the resources — they
will be automatically closed whether an exception has occurred or not. Moreover, this
form inserts variables declared in parentheses to the scope of the try clause, but not to
the outer scope, keeping the outer scope unpolluted by variables that are not needed
there.
In the loop reading the file, we call readLine on the BufferedReader object. In this
way, we can read the file line by line not bothering about detecting the LF character
ourselves. When the end of file is reached, readLine returns null, otherwise it returns
the next line as a string (with the LF character chopped off ).
For completeness, another version of essentially the same program is shown below.
Here, we don’t use classes from the java.nio package, but from an older (but still used
and still necessary) java.io package.
Listing 76 KFD-Grep/Grep.java
1 import java.io.BufferedReader;
2 import java.io.BufferedWriter;
3 import java.io.File;
4 import java.io.FileReader;
5 import java.io.FileWriter;
6 import java.io.IOException;
7
13 if ( !filein.exists() ||
14 !filein.canRead() ||
15 filein.isDirectory() ) {
16 System.out.println("Invalid input file !!!");
116
17 System.exit(1);
18 }
19 File fileou = new File("grep_" + filein.getName());
20 BufferedReader br = null;
21 BufferedWriter bw = null;
22 String LF=System.getProperty("line.separator");
23
24 try {
25 br = new BufferedReader(
26 new FileReader(filein));
27 bw = new BufferedWriter(
28 new FileWriter(fileou));
29 String line;
30 int lineNo = 0;
31 while ( (line = br.readLine()) != null) {
32 ++lineNo;
33 if (line.indexOf(wordSearchedFor) >=0)
34 bw.write(String.format("Line %2d: %s%s",
35 lineNo,line,LF));
36 }
37
38 } catch (IOException e) {
39 System.out.println("Problems with reading");
40 } finally {
41 try { if (br != null) br.close(); }
42 catch(IOException ignore) { }
43 try { if (bw != null) bw.close(); }
44 catch(IOException ignore) { }
45 System.out.println("Results written to " +
46 fileou.getAbsolutePath());
47 }
48 }
49 }
There are some differences to be noted: class File which, to some extend, plays
the rôle of the class Path from the java.nio.file package. Notice also, that Buffere-
dReader must be created in two steps: first we create ‘raw’ byte stream of type, e.g.,
FileInputStream, then we pass it to the constructor of InputStreamReader with, as
the second argument, the required encoding, and then this to the constructor of the
BufferedReader. In the example above there are only two steps: to the constructor
of BufferedReader we pass directly an object of type FileReader, but then there is
no way to specify the encoding — default system encoding will be assumed. Note how
try-with-resources simplifies operations on IO streams; in the program above, we don’t
use it and therefore the somewhat complicated finally clause is needed (as close may
itself also throw an exception).
It is sometimes useful to a read text file into a single string (for example, to process
it with regular exceptions). This is a very common task, and it can be accomplished,
for example, as shown below
117
Listing 77 KFI-File2Str/File2Str.java
1 import java.io.IOException;
2 import java.nio.file.Files;
3 import java.nio.file.Paths;
4 import static java.nio.charset.StandardCharsets.UTF_8;
5
10 try {
11 byte[] bytes =
12 Files.readAllBytes(Paths.get("pangram.txt"));
13 text = new String(bytes, UTF_8);
14 } catch(IOException e) {
15 System.out.println(e.getMessage());
16 System.exit(1);
17 }
18 System.out.println("***** File read (1):\n" + text);
19
The last example illustrates the StreamTokenizer utility class. It reads from a text
file and splits the input into ‘tokens’ – single pieces of information (treating spaces
as separators, but this can be changed). After reading a token with the nextToken
method, the field ttype contains information about the type of this token in the form of
a predefined integer constant: TT_NUMBER if the token can be interpreted as a num-
ber, TT_WORD if it’s a string, TT_EOL if it’s the end-of-line character, TT_EOF if
the end of file has been reached. When the token is a number or a string, one can get
their values from the fields nval (double) or sval (String), respectively.
For example, for a data file like this
Tokyo 38 Delhi
25.7 Shanghai 23.7 SaoPaulo 21 Mumbai 21
the following program
118
Listing 78 HUS-Tokens/Tokenizer.java
1 import java.io.BufferedReader;
2 import java.io.IOException;
3 import java.io.FileNotFoundException;
4 import java.io.StreamTokenizer;
5 import java.nio.file.Files;
6 import java.nio.file.Paths;
7 import static java.io.StreamTokenizer.TT_EOF;
8 import static java.io.StreamTokenizer.TT_NUMBER;
9 import static java.io.StreamTokenizer.TT_WORD;
10
will print
119
Total population: 129.4
Cities: Tokyo Delhi Shanghai SaoPaulo Mumbai
// ...
try (
InputStream fis = new FileInputStream("fi.bin");
OutputStream fos = new FileOutputStream("fo.bin")
){
int n;
while ( (n = fis.read()) != -1) {
char c = (char)n;
// ...
System.out.print(c);
fos.write(n);
}
} catch(IOException e) { /* ... */ }
To make reading/writing more efficient, one may also use buffered streams:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
// ...
InputStream fis =
new BufferedInputStream(
new FileInputStream("fi.bin"));
OutputStream fos =
new BufferedOutputStream(
new FileOutputStream("fo.bin"))
// ...
120
byte[] ba = null;
try {
ba = Files.readAllBytes(Paths.get("fi.bin"));
} catch(IOException e) { /* ... */ }
for (byte b : ba) System.out.print((char)b);
// ...
try (
BufferedReader br =
Files.newBufferedReader(
Paths.get("fi.txt"), UTF_8);
BufferedWriter bw =
Files.newBufferedWriter(
Paths.get("fo.txt"), UTF_8)
){
String line;
while ( (line = br.readLine()) != null) {
System.out.println(line);
bw.write(line);
bw.newLine();
}
} catch(IOException e) { /* ... */ }
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.IOException;
import static java.nio.charset.StandardCharsets.UTF_8;
try (
Reader rd =
121
new InputStreamReader(System.in, UTF_8)
) {
while (true) {
int i = rd.read();
// ... a condition to stop the loop
c = (char)i;
System.out.print(c);
}
} catch(IOException e) {
e.printStackTrace();
}
122
Listings
List of listings
1 AAC-HelloWorld/Hello.java . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2 AAG-Literals/Literals.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3 AAL-Variables/Variables.java . . . . . . . . . . . . . . . . . . . . . . . . . 13
4 AAI-ReadKbd/ReadKbd.java . . . . . . . . . . . . . . . . . . . . . . . . . 16
5 AAJ-ReadGraph/ReadGraph.java . . . . . . . . . . . . . . . . . . . . . . . 17
6 ABY-RelOps/RelOps.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
7 ACB-CondOp/Largest.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
8 BAA-Bits/Bits.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
9 AAP-BasicOps/BasicOps.java . . . . . . . . . . . . . . . . . . . . . . . . . 27
10 ABX-Ifs/LeapYear.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
11 ACC-SimpleSwitch/SimpleSwitch.java . . . . . . . . . . . . . . . . . . . . 31
12 ACD-Switch/Switch.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
13 ACE-SwitchArrow/SwitchArrow.java . . . . . . . . . . . . . . . . . . . . . 33
14 ACF-SwitchMult/SwitchMult.java . . . . . . . . . . . . . . . . . . . . . . 34
15 ACG-SwitchExpr/SwitchExpr.java . . . . . . . . . . . . . . . . . . . . . . 34
16 ACH-SwitchEnum/SwitchEnum.java . . . . . . . . . . . . . . . . . . . . . 35
17 AFA-While/Prime.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
18 AFB-WhileBis/Prime.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
19 AFE-DoWhileDice/Dice.java . . . . . . . . . . . . . . . . . . . . . . . . . 39
20 AFH-ForPyram/Stars.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
21 AFJ-ForWhileEuler/ForWhileEuler.java . . . . . . . . . . . . . . . . . . . 41
22 CYD-BasicArray/BasicArr.java . . . . . . . . . . . . . . . . . . . . . . . . 45
23 CYB-SimpleArrays/SimpleArrays.java . . . . . . . . . . . . . . . . . . . . 47
24 CYJ-Arr3D/Arr3D.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
25 CYS-PassArr/PassArr.java . . . . . . . . . . . . . . . . . . . . . . . . . . 53
26 BHK-StatFun/StatFun.java . . . . . . . . . . . . . . . . . . . . . . . . . . 54
27 BHL-RecFun/RecFun.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
28 AFR-SelSort/SelSort.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
29 AFM-Overload/Overload.java . . . . . . . . . . . . . . . . . . . . . . . . . 57
30 AFN-Resol/Resol.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
31 BGO-TrivPoint/TrivPoint.java . . . . . . . . . . . . . . . . . . . . . . . . 60
32 BGO-TrivPoint/Main.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
33 BGI-VerySimple/VerySimple.java . . . . . . . . . . . . . . . . . . . . . . . 63
34 BGI-VerySimple/Main.java . . . . . . . . . . . . . . . . . . . . . . . . . . 63
35 BGP-Point/Point.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
36 BGR-BasicClass/Person.java . . . . . . . . . . . . . . . . . . . . . . . . . 66
37 BGR-BasicClass/Main.java . . . . . . . . . . . . . . . . . . . . . . . . . . 67
38 BHE-StatEx/StatExample.java . . . . . . . . . . . . . . . . . . . . . . . . 68
39 AFL-FunRecur/SimpleRec.java . . . . . . . . . . . . . . . . . . . . . . . . 69
40 BHG-StatOrd/Stats.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
41 BHG-StatOrd/Main.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
42 BHF-StatBlocks/StatBlocks.java . . . . . . . . . . . . . . . . . . . . . . . 73
43 BGX-Singlet/Connect.java . . . . . . . . . . . . . . . . . . . . . . . . . . 75
44 BGX-Singlet/Config.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
45 BGX-Singlet/Main.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
46 BGU-SingList/Node.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
47 BGU-SingList/MyList.java . . . . . . . . . . . . . . . . . . . . . . . . . . 78
123
48 BGU-SingList/Main.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
49 BGW-Stack/Node.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
50 BGW-Stack/MyStack.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
51 BGW-Stack/Main.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
52 BGZ-ArrStack/ArrStack.java . . . . . . . . . . . . . . . . . . . . . . . . . 81
53 BGZ-ArrStack/Main.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
54 BGY-SimpQueue/MyQueue.java . . . . . . . . . . . . . . . . . . . . . . . 83
55 BGY-SimpQueue/Main.java . . . . . . . . . . . . . . . . . . . . . . . . . . 83
56 BHH-Strings/Strings.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
57 BHI-StrBuilder/StrBuilder.java . . . . . . . . . . . . . . . . . . . . . . . . 88
58 DJV-Inherit/Point.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
59 DJV-Inherit/Pixel.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
60 DJV-Inherit/Main.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
61 DJW-InherAnimal/Animal.java . . . . . . . . . . . . . . . . . . . . . . . . 94
62 DJW-InherAnimal/Dog.java . . . . . . . . . . . . . . . . . . . . . . . . . . 94
63 DJW-InherAnimal/Cat.java . . . . . . . . . . . . . . . . . . . . . . . . . . 95
64 DJW-InherAnimal/Main.java . . . . . . . . . . . . . . . . . . . . . . . . . 95
65 AAR-EquToString/EquToString.java . . . . . . . . . . . . . . . . . . . . . 96
66 HUM-HashEquals/Person.java . . . . . . . . . . . . . . . . . . . . . . . . 98
67 HUM-HashEquals/AHash.java . . . . . . . . . . . . . . . . . . . . . . . . 99
68 AXW-ScanExcept/ScanExcept.java . . . . . . . . . . . . . . . . . . . . . . 105
69 AXX-TryCatch/TryCatch.java . . . . . . . . . . . . . . . . . . . . . . . . 106
70 AYC-Excpts/Excpts.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
71 AYN-CheckedExc/MyUncheckedException.java . . . . . . . . . . . . . . . 108
72 AYN-CheckedExc/CheckedExc.java . . . . . . . . . . . . . . . . . . . . . 109
73 KEP-WriteBin/WriteBin.java . . . . . . . . . . . . . . . . . . . . . . . . . 112
74 BHJ-BytesChars/BytesChars.java . . . . . . . . . . . . . . . . . . . . . . 113
75 KFE-GrepNew/GrepNew.java . . . . . . . . . . . . . . . . . . . . . . . . . 115
76 KFD-Grep/Grep.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
77 KFI-File2Str/File2Str.java . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
78 HUS-Tokens/Tokenizer.java . . . . . . . . . . . . . . . . . . . . . . . . . . 119
124
Index
125
StringBufferInputStream, 111 FIFO, 82
StringBuilder, 88 File (class), 117
StringReader, 111 FileInputStream (class), 111
StringWriter, 111 FileNotFoundException, 106
Throwable, 100 FileOutputStream (class), 111
Writer, 111 FileReader (class), 117
Class (class), 90 final method, 90
class file, 5 finally, 102
Closeable (interface), 116 first in, first out, 82
column of array, 47 floating point type, 6
comment, 4 for loop, 40
compiler, 2 for-each loop, 43
compound assignment operator, 18 free memory, 59
compound instruction, 29 function
concurrency, 3 argument, 51
conditional operator, 23 main, 4
conditional statement, 29 overloading, 57
constructor, 59, 63 parameter, 51
default, 60, 64 recursive, 55
delegating, 64 static, 51, 59
overloading, 64
conversion, 14, 26 garbage collector, 10
Gosling J., 2
decorator, 114 graphical user interface, 3
default constructor, 60 GUI, 3
derived class, 90
diagonal, 47 hashCode (method), 96
do-while loop, 38 heap, 15, 59
hermetization, 62
eager evaluation, 76 hexadecimal notation, 1
encapsulation, 62
equals (method), 96 implicit conversion, 26
Error (class), 100 index, 43
Euclid, 2 inheritance, 90
evaluation initialization blocks, 72
lazy, 75, 76 inner class, 59
exception, 100 InputStream (class), 111
checked, 100 InputStreamReader (class), 111, 113
FileNotFoundException, 106 instance, 59
IOException, 101, 102, 104 instanceof, 90
NumberFormatException, 106 instruction, 1, 29
propagating, 103 compound, 29
rethrowing, 102 instruction set, 1
throw, 100 integral type, 6
throwing, 103 interface
unchecked, 100 Closeable, 116
Exception (class), 100 interpreter, 2
executable, 1 IOException, 101, 102, 104
expression, 29, 40 IOException (class), 112
126
JIT, 5 operator, 18
just-in-time compilation, 5 <<, 24
JVM, 3, 5 =, 18
>>, 24
labeled loop, 37 >>>, 24
last in, first out, 80 &, 24
late binding, 90 &&, 21
lazy evaluation, 75 |, 25
left shift, 8 ||, 21
LIFO, 80 !, 21
list, 77 ^, 22, 25
local variable, 51 AND, 21
logical type, 6 arithmetic, 19
loop assignment, 18
do-while, 38 associativity, 27
for, 40 binary, 18
for-each, 43 bit-wise, 24
labeled, 37 compound assignment, 18
named, 37 conditional, 23
while, 35 instanceof, 90
machine code, 1 modulus, 20
main, 4 NOT, 21
main diagonal, 47 OR, 21
member, 59 precedence, 26
non-static, 59 remainder, 20
method, 59 shift, 24
equals, 96 short-circuited, 21
final, 90 ternary, 18
hashCode, 96 unary, 18
overriding, 96 XOR, 22
static, 51 OR, 7
virtual, 90 OR operator, 21
modulus operator, 20 OutputStream (class), 111
multi-dimensional array, 46 OutputStreamWriter (class), 111
multithreading, 3 overloading, 57, 64
overriding, 90
named loop, 37 overriding a method, 96
nested class, 83
network programming, 3 package private, 62
non-static member, 59 parameter, 51
NOT, 8 pointer, 6, 43, 52
NOT operator, 21 polymorphism, 90
NumberFormatException, 106 precedence of operators, 26
primitive type, 6
object, 59 PrintStream (class), 111
Object (class), 90 PrintWriter (class), 111
object type, 10 private, 62
ObjectInputStream (class), 111 processor, 1
ObjectOutputStream (class), 111 program, 1
operand, 18 propagating exception, 103
operating system, 1 protected, 62
127
public, 62 String (class), 85
StringBufferInputStream (class), 111
queue, 82 StringBuilder (class), 88
StringReader (class), 111
random numbers, 39
StringWriter (class), 111
Reader (class), 111
subnormal number, 9
rectangular array, 46
superclass, 90
recursion, 55
switch expression, 34
reference, 43, 59, 61
switch statement, 30
reference type, 6
System.err, 111
regex, 86
System.in, 111
register, 1
System.out, 111
remainder operator, 20
rethrowing exception, 102 ternary operator, 18
return type, 51 this, 62
right shift, 8 throw exception, 100
row of array, 47 Throwable (class), 100
RuntimeException (class), 100 throwing exception, 103
toString, 66
Scanner, 104 try, 100, 101
Scanner (class), 16 try-with-resources, 112, 116
scientific notation, 11 type, 6
selection sort, 56 boolean, 6
shift casting, 14
left, 8 conversion, 14
right, 8 floating point, 6
shift operator, 24 integral, 6
short-circuited operator, 21 logical, 6
singleton, 75 object, 10
singly linked list, 77 primitive, 6
square array, 47 reference, 6
stack, 15, 51, 80
stack frame, 51 unary operator, 18
state, 59 unchecked exception, 100
statement, 29
conditional, 29 variable, 6, 10
switch, 30 local, 51
static, 59, 68 virtual method, 90
static field, 59 void, 51
static function, 51 while loop, 35
static method, 51 Writer (class), 111
stream (IO), 111
StreamTokenizer (class), 118 XOR, 7
String, 85 XOR operator, 22
128