Module 2, Part 1: a First Taste of Java#
This Module is split in two parts. In this first part, you will get a first taste of the Java programming language. We first discuss what is Java some experiments with the Java shell (JShell), and then write and execute our first Java program.
In the second part of this Module we will explore some more Java programming constructs allowing us to write some basic programs.
What is Java?#
Java is a high-level programming language, originally launched in 1995 by Sun Microsystems (which was later acquired by Oracle). Since then, the language and its tools have been expanded and improved significantly.
The term “high-level” means that the language offers programming concepts and tools that are quite detached from the “low-level” details of the underlying computer. This is very different from assembly, where each instruction has a direct correspondence with the computer’s machine instruction; instead, when we write and execute a Java program, the Java code is automatically translated into machine instructions, which are then executed. (This is not 100% accurate, but we will discuss the precise details of how Java works later in the course.)
The main benefits of Java (and most high-level programming languages in general) when compared to assembly are:
The code is more readable by humans, since its notation is based on Eglish and mathematics;
Programmers can plan their programs by thinking about high-level tasks (e.g., “add \(x\) and \(y\)”) without worrying about low-level details (e.g., “which exact registers are being used to hold \(x\), \(y\), and the result of the addition?)”;
The language provides different types of data to avoid confusion and mistakes. We have seen that, under the hood, a computer only deals with numerical values that may have different interpretations; correspondingly, we have seen that a BlinkenBits assembly program only deals with numerical values, and the programmer must keep track of what those values are for — e.g., if the value 10 is in a register, that value might be either a number to be used for mathematical computations, or a memory address, or the symbol
a
to be shown on the BlinkenBits display. Java programs assign a type to each value, to distinguish how the value can be used: e.g., Java distinguishes a value of typeint
(integer, usable for mathematical computations) from a value of typechar
(which represent a symbol like the lettera
). This makes programs more readable and easier to maintain.Since the code is not tied to specific machine instructions, the code is also more portable across different computer architectures. For instance, a Java program can run in the same way on an Intel or AMD computer with an x86 processor, and on an Apple computer with an M3 processor, even if their machine instructions are incompatible with each other.
Experimenting with the Java Shell (JShell)#
The Java Shell (JShell) is an interactive tool for learning the Java programming language and prototyping Java code.
Important
The following instructions will only work if you have already installed the software required for the course.
First, you need to open a terminal (a.k.a. console) with the command-line interface.
Search for Git Bash in the start menu, and press ⏎
(return key).
Alternatively, please follow these instructions (look for the section “Open Git Bash directly in the folder”).
Please follow these instructions.
Press Ctrl
+Alt
+T
.
When the terminal starts, write:
jshell -v
and press ⏎
(return key).
Important
The option -v
above stands for “verbose” and makes the Java shell display more
informative messages while we use it. This option is needed to see information
about types, that we will discuss shortly.
You should now see the following message printed on the terminal (although the reported version may be slightly different):
| Welcome to JShell -- Version 21.0.4
| For an introduction type: /help intro
jshell>
Here, jshell>
is the Java shell prompt: we can write a snippet of Java
code there, which will be executed immediately (unless it contains errors);
then we will see the result of the execution, and we will get a new prompt to
keep using the shell.
Note
In the JShell examples below, some lines are highlighted, which means:
if you write what is on the right of the jshell>
prompt on the highlighted
line and and press ⏎
(return), then the Java shell will produce the output
that follows.
Some Simple Integer Expressions#
In Java, every value and expression has a type. For example, let us try to perform some calculations with integer values:
jshell> 2 + 3
$1 ==> 5
| created scratch variable $1 : int
The message above is telling us that the expression 2 + 3
has been executed
and it has produced the result 5 — which in turn has been used to create a
“scratch variable” called $1
; moreover, the variable $1
has a type, which is
int
(shorthand for “integer”). In other words, the integer value 5 is now
stored in a memory location that we can access later (if we need) through the
variable name $1
.
Note
You can imagine that, in order to execute the example above, the Java shell has
translated the expression 2 + 3
into a sequence of machine instructions that
perform the desired computation, producing the result 5. If those machine
instructions were written in
BlinkenBits assembly, they might look
as follows:
00: r0 <- 2 // Put the value 2 in register r0
01: r1 <- 3 // Put the value 3 in register r1
02: r2 <- r0 + r1 // Add the values in r0 and r1, put the result in r2
03: r3 <- 10 // Put the value 10 (a memory address) in register r3
04: memory@r3 <- r2 // Store the result of the addition in memory
When writing Java code, we do not need to worry about which exact registers are used for the computation, or which exact memory locations are used to store results: the translation from Java to machine instructions takes care of these aspects.
Continuing the example above, we can now try:
jshell> $1 * 2
$2 ==> 10
| created scratch variable $2 : int
I.e., we have given the expression $1 * 2
to the Java shell, which proceeds as
follows:
first, it looks up the value of the variable
$1
, which is 5;conceptually, Java rewrites the expression
$1 * 2
by substituting the occurrence of variable$1
with its value 5 — and this substitution gives the expression5 * 2
;then, it computes the result of the expression
5 * 2
, that produces the result 10;finally, it stores the result 10 in a “scratch variable” called
$2
of typeint
.
The “scratch” variable names $1
, $2
, etc. are automatically selected by the
Java shell to let us reuse the results of previous expressions — but we can
create variables with (almost) any name we like. Recalling the
opening example about computing an area, let
us now use the Java shell to compute the area of a rectangle having width 3 and
height 7. We can start by declaring some variables with descriptive names:
jshell> var width = 3
width ==> 3
| created variable width : int
jshell> var height = 7
height ==> 7
| created variable height : int
jshell> var area = width * height
area ==> 21
| created variable area : int
In the first highlighted line above, var width = 2
declares a variable called
width
and having the value 3; in the two lines that follow, JShell tells us
that width
has value 3 and type int
. Then, we declare the variables
height
and area
in a similar way. Notice that we declare the variable
area
with the result of the expression width * height
i.e. by referring to
the variables that we have introduced earlier. The value of area
is computed
by substituting the variables width
and height
with their respective values:
therefore, the expression width * height
becomes 3 * 7
, which produces the
value 21.
Important
If you try to write an expression that references an undefined variable, you
will get an error. For example, if you misspell area
as aera
, you will get:
jshell> aera + 42
| Error:
| cannot find symbol
| symbol: variable aera
| aera + 42
| ^--^
Tip
To see which variables have been declared so far, their type, and their values,
you can use the JShell command /vars
. If you try it now, you should see an
output like:
jshell> /vars
| int $1 = 5
| int $2 = 10
| int width = 3
| int height = 7
| int area = 21
This tells us e.g. that area
is a variable of type int
, and has value 21.
Note that in Java expressions can be nested as one may expect from mathematics. In this case, after a sub-expression is computed, its result is used to compute the surrounding expression. Expression nesting is a common feature of most programming languages (except assembly). For example, consider:
jshell> var twiceArea = (width * height) * 2
twiceArea ==> 42
| created variable twiceArea : int
When executing the code above, Java proceeds as follows:
First, Java evaluates the innermost sub-expression, i.e.,
witdth * height
:Java substitutes he variables
width
andheight
with their values, which are respectively3
and7
. Hence, the sub-expressionwitdth * height
becomes3 * 7
;Java now computes the result of the sub-expression
3 * 7
, which is21
.
Then, Java uses the result of the sub-expression
witdth * height
(i.e.,21
) to compute the result of the initial expression(width * height) * 2
. Therefore, Java computes21 * 2
, which gives the result42
.
The result 42
is finally used to initialise the variable twiceArea
.
(We will discuss nested expressions again later, in Remark 7.)
Some Expressions with Numbers Having Fractional Parts#
Continuing the example above, let us now try to compute half of the area of the rectangle:
jshell> area / 2
$6 ==> 10
| created scratch variable $6 : int
Since the value of area
is 21, the result of area / 2
should have been 10.5
— but it has been truncated to 10!
This happens because the arithmetic expression area / 2
only involves values
and variables of type int
— and consequently, Java computes a result of type
int
, without the fractional part .5
. Correspondingly, the scratch variable
$6
gets type int
, because that is the type of the result of the expression
area / 2
.
Let us try instead to divide the area by 2.0
(i.e. we explicitly write the
radix point and the fractional part, which in this case is 0):
jshell> var halfArea = area / 2.0
halfArea ==> 10.5
| created variable halfArea : double
Observe that the result of area / 2.0
is now 10.5, which becomes the value of
the variable halfArea
; moreover, halfArea
has type double
, which denotes
numbers which may include a fractional part.
The reason is that, in the expression area / 2.0
, the value 2.0
has type
double
— and when a numerical operation contains at least one operand of
type double
, then the result of the whole operation has also type double
,
and is computed with the fractional part if needed. Consequently, the result of
area / 2.0
has type double
and is the value 10.5. Correspondingly, the
variable halfArea
gets type double
, because that is the type of the result
of the expression area / 2.0
.
(Type promotion)
More technically, what happened to the expression area / 2.0
is an example of
automatic type promotion, and the principle is: if a numerical operation
involves operands having different types, then the operand with the “less
precise” type is promoted (i.e. converted) to the “most precise” type before
computing the result. In the example above, the type of the operand 2.0
(which is double
) is more precise than the type of area
(which is int
).
Therefore, the value of area
(21, of type int
) is automatically promoted
(to 21.0, of type double
) before computing the operation (21.0 / 2.0).
Beyond Numbers: Booleans and Strings#
Java supports values and expressions with a variety of types (and later in the course we will see that we can also define new types).
For instance, Java supports values of type boolean
, which can be only true
or false
. It also provides the operators <
(“less than”), >
(“greater
than”) or ==
(“equal”) to compare two numbers — and the result of the
comparison is a boolean value.
jshell> width < height
$9 ==> true
| created scratch variable $9 : boolean
jshell> width > height
$10 ==> false
| created scratch variable $10 : boolean
jshell> (width + 4) == height
$11 ==> true
| created scratch variable $11 : boolean
Notice that, since true
and false
are values, we can store them into
variables: in fact, the booleans computed in the examples above are stored in
(scratch) variables — and of course, we can create our own variables of type
boolean
:
jshell> var isWidthGreaterThanHeight = width > height
isWidthGreaterThanHeight ==> true
| created variable isWidthGreaterThanHeight : boolean
Note
The idea that true
and false
are values that are produced by a computation
can also be found in the BlinkenBits
comparison instructions: for instance, r1 <- r2 < r3 produces the value
1 (and writes it in r1
) when the value in r2
is smaller than the value in
r3
; otherwise, the comparison produces the value 0 (and writes it in r1
).
The difference is that Java provides the dedicated keywords true
and false
to represent boolean values, instead of letting us deal with the numerical
values 1 and 0.
Important
When writing Java code, we must be mindful about the types expected by operators
and other language constructs. For example, the multiplication operator *
and
the comparison <
only accept operands that are numbers (e.g. of type int
or
double
). Therefore, if we try to use *
or <
with some operand of type
boolean
, we get an error. You can see the errors by trying the following
examples:
jshell> width * true
| Error:
| bad operand types for binary operator '*'
| first type: int
| second type: boolean
| width * true
| ^----------^
jshell> isWidthGreaterThanHeight > 42.0
| Error:
| bad operand types for binary operator '>'
| first type: boolean
| second type: double
| isWidthGreaterThanHeight > 42.0
| ^-----------------------------^
jshell> width < (4 == height)
| Error:
| bad operand types for binary operator '<'
| first type: int
| second type: boolean
| width < (4 == height)
| ^-------------------^
This type distinction does not exist in BlinkenBits,
where every value is a numerical value — so the value 0
is used both
as the result of a (false) numerical comparison, and as an integer that can be
operand for additions or subtractions. This lack of type distinctions between
values is often the source of confusion and mistakes in assembly programs.
Java also supports values of type String
, that are sequences of characters.
To create a value of type String
, we write the desired sequence of characters
enclosed between double quotes "
. For example:
jshell> var hello = "Hello, I am a String!"
hello ==> "Hello, I am a String!"
| created variable hello : String
The operator +
also works on strings: it can take two strings as operands, and
produces a new string obtained by concatenating the first and second operand.
jshell> var hello2 = hello + " And here is more text"
hello2 ==> "Hello, I am a string! And here is more text"
| created variable hello2 : String
The operator +
can also concatenate strings to int
s, double
s, or
boolean
s, and produce a new string as a result. For example:
jshell> var message = "The area of the rectangle is " + area
message ==> "The area of the rectangle is 21"
| created variable message : String
Java’s Primitive Data Types and Numerical Operators#
Table 5 and Table 6 below provide a brief summary of all the primitive data types and numerical operators in Java (some of which we have already seen above). Table 5 also includes examples on how to write literal values of each primitive data type (try writing them on the Java shell!).
These tables are just for reference, and you do not need to know their content by heart. We will use these primitive types and numerical operators throughout the course.
Type |
Size (bits) |
Example |
Range of allowed values |
---|---|---|---|
|
8 |
|
-128 to 127 |
|
16 |
|
-32,768 to 32,767 |
|
32 |
|
-2,147,483,648 to 2,147,483,647 |
|
64 |
|
-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
|
32 |
|
+/-3.4E+38F (6-7 significant digits) |
|
64 |
|
+/-1.8E+308 (15 significant digits) |
|
16 |
|
|
|
8 |
|
Either |
Important
As shown in Table 5, all primitive Java types
have limits — for example, a value of type int
cannot be smaller than
-2147483648, nor larger than 2147483647. This is conceptually similar to the
limits in BlinkenBits, where a
value held in a register or in a memory location must be between 0 and 4095.
When a computation exceeds the maximum value allowed by its result type, it will overflow and “wrap around” to the minimum allowed value. Vice versa, when a computation yields a result that is smaller than the minimum value allowed by its result type, it will underflow and “wrap around” to the maximum allowed value. For instance, you can try on the Java shell:
jshell> var overflow = 2147483647 + 1
overflow ==> -2147483648
| created variable overflow : int
jshell> var underflow = -2147483648 - 1
underflow ==> 2147483647
| created variable underflow : int
In this example, adding 1 to the maximum int
eger value yields the minimum
int
eger value; vice versa, subtracting 1 from the minimum int
eger value
yields the maximum int
eger value.
You can observe overflows and underflows in BlinkenBits, too. For example, you can:
Overflow the value contained in a register:
00: r0 <- 05 // Memory addr. containing the max allowed value (see below) 01: r0 <- memory@r0 // Load maximum allowed value 4095 into r0 02: r1 <- 1 // Put value 1 into r1 03: r0 <- r0 + r1 // The result of this addition will be 0 (overflow) 04: halt 05: data 4095
Underflow the value contained in a register:
00: r0 <- 0 // Put the minimum allowed value 0 into r0 01: r1 <- 1 // Put value 1 into r1 02: r0 <- r0 - r1 // The result of this subtraction will be 4095 (underflow) 03: halt
Important
If you try to write a literal numerical value like 1234567890123456789
, Java
will interpret it as a literal value of type int
— and you will get an
error, because that value is too big to fit in 32 bits (the size of int
values, as shown in Table 5). Try it on the Java
shell!
To represent such a big number, Java provides the long
type — and to write a
literal value of long
type, you need to add an L
at the end of the number,
as shown in the example in Table 5.
Still, the long
type also has its limits — so if you add a 0
at the end of
the number above and write 12345678901234567890L
, you will get an error,
because that number does not fit in 64 bits (the size of long
values). (Try
it on the Java shell!)
Note
Table 5 does not include the type String
.
This is not an oversight: we will address the topic in
Module 2, Part 2: Console I/O, Conditionals, Strings.
Operator |
Example |
Description |
---|---|---|
|
|
Addition |
|
|
Subtraction |
|
|
Division |
|
|
Multiplication |
|
|
Modulo (a.k.a. remainder of the division) |
Our First Java Program#
We have used the Java shell to
experiment with the Java language — but to write an actual Java program, we
need to write Java statements in a text file with extension .java
. Let us now
create a simple Java program by assembling some of the statements we tried on
the Java shell.
1// Our first Java program!
2
3class Area {
4 public static void main(String[] args) {
5 var width = 3;
6 var height = 7;
7 var area = width * height;
8 var message = "The area of the rectangle is " + area;
9
10 System.out.println(message);
11 }
12}
Let us examine the program above in more detail:
Line 1 is just a comment: when we write
//
in Java, everything that follows the slashes until the end of the line is ignored by the language. This is handy e.g. for leaving notes and documentation for the humans who read the program.Lines 5–8 are the same Java statements that we have tried on the Java shell — except that each statement is followed by a semicolon (the semicolon is mandatory, and is used to indicate the end of a statement).
On line 10, there is an additional statement: it invokes
System.out.println
passing the variablemessage
as argument.System.out.println
is a facility included in the Java environment: when it is invoked, it displays the value of its argument on the terminal where the program is running. (You can try that on the Java shell, if you wish. We will discuss this topic in more detail later in the course.)All these statements are contained inside the curly brackets
{
…}
(lines 4–11) of a method calledmain
: when a Java program is launched, it runs by executing the statements insidemain
, one by one. This is conceptually similar to BlinkenBits that executes the instructions in memory, one by one. (We will talk more about Java methods later in the course.)The method
main
, in turn, belongs a class calledArea
(whose contents are inside the curly brackets{
…}
on lines 3–12). We can choose the class name however we like. (We will talk more about classes later in the course.)
Important
In the Java program above, you can notice that the code is indented:
the code belonging to
main
(lines 5-10) is indented to the right w.r.t. the lines that open and close the definition ofmain
(lines 4 and 11);similarly, the code belonging to
Area
is indented to the right w.r.t. the lines that open and close the definition ofArea
(lines 3 and 12).
This indentation is not required by Java, but makes the code much easier to read for us programmers. This will become very important as our programs will become more complex: indentation helps in grasping the code structure at a glance, and navigating the program while working on it.
To write and run this program, you can proceed as follows:
Open a new terminal — or exit from the Java shell by typing
/exit
.At the terminal prompt, launch Visual Studio Code by executing:
code Area.java
You should now see the editing window of VS Code: copy&paste the text of the Java program above, and save the file (by pressing
Ctrl
+S
or⌘
+S
).Note
If VS Code suggests you to install some “Java extensions,” please ignore it.
With the steps above, you have created a new text file called
Area.java
containing the simple Java program above. To run the program, go back to the terminal from which you launched VS Code, and execute:java Area.java
If everything goes well, you should see the following output on the terminal:
The area of the rectangle is 21
and then you should see the terminal prompt again: this means that the Java program has completed its execution.
Behind the scenes, the java
command has translated the program written in the
file Area.java
into machine instructions, and those instructions have been
executed by the CPU of your computer. (This is not 100% accurate and there is
more going on behind the scenes, but we will discuss the precise details of how
java
works later in the course.)
Concluding Remarks#
You should now have an understanding of the following topics:
how to experiment with basic Java statements on the Java shell (JShell);
in Java, every value and expression has a type (we have experimented with
int
,double
,boolean
andString
);how to save a simple Java program in a file, and how to run it.
You should be able to use the Java shell for further experiments, and use the simple Java program above as a blueprint and starting point for writing other simple programs.
Important
If you read other materials about Java programming (including the
reference book), you will notice that there is another popular
style for declaring variables: instead of writing var x = ...
, we can manually
specify the type of the variable, by writing the type itself instead of var
.
Therefore,
the simple Java program above
can be also written as follows:
1class Area {
2 public static void main(String[] args) {
3 int width = 3;
4 int height = 7;
5 int area = width * height;
6 String message = "The area of the rectangle is " + area;
7
8 System.out.println(message);
9 }
10}
Feel free to adopt the style you like better, or mix the two styles (e.g.
declare some variables with var x = ...
and others with String y = ...
). No
matter which style you adopt, keep in mind that all variables and values have a
type, as seen during our
experiments with the Java shell.
References and Further Readings#
You are encouraged to read the following sections of the reference book: (Note: they sometimes mention Java features that we will address later in the course)
Section 1.4 - “The Java Programming Language”
Section 2.3 - “Primitive Data Types”
Section 2.4 - “Expressions”
The following appendix of the reference book may be also useful:
Appendix C - “The Unicode Character Set”
The Java documentation includes a Java Shell User’s Guide (Note: it mentions Java features that we will address later in the course).
Exercises#
Note
These exercises are not part of the course assessment. They are provided to help you self-test your understanding and practice with Java.
(What is the type?)
Read the following Java expressions, and figure out what is the type of each one. To verify whether your answers are correct, you can execute each expression on the Java shell.
Important
Don’t forget to use the option -v
when you launch jshell -v
, otherwise the
Java shell will not display the type information.
58
true
-23
"afd "
42.0
"42.0"
"true"
1.5 * 60.0
1.5 * 60
24 * 60
1.1 + 60 - 1
150.0 / 60
150 / 60
2 + 2
"2" + 3
2 + "3"
"Apollo " + (5 * 3 + 2) +" was the last crewed mission to the Moon"
(Causing errors)
Edit
the simple Java program above
by applying one or more of the changes below: each change introduces an error in
the program. After each change, save the modified file Area.java
, and try to
run it, by executing java Area.java
on the terminal: observe how each change
causes a different error.
Remove one of the semicolons
Remove one of the curly brackets
On line 4, change
main
intomaiiin
On line 3, change the name of the variable
width
intow
On line 3, change the initialisation value from
3
(which has typeint
) into"3"
(which has typeString
)On line 8, remove the first or last double-quote
"
On line 10, change
println
intobogus
Lab and Weekly Assessments#
During the lab you can try the exercises above or begin your work on the weekly assessments below.
Important
For each assessment, you can download the corresponding handout and submit your solution on DTU Autolab: https://autolab.compute.dtu.dk/courses/02312-E24.
For details on how to use Autolab and the assessment handouts, and how to submit your solutions, please read these instructions.
If you have troubles, you can get help from the teacher and TAs.
01 - Hello, World!#
Edit the file Hello.java
provided in the handout to make it display the
sentence Hello, World!
on the terminal. Submit the modified file Hello.java
on DTU Autolab.
Hint
In the file
Hello.java
(provided in the handout) there is a comment where you should write your Java code.You should use
System.out.println(...)
, as in the simple Java program above.Make sure that the program still runs without errors after your changes, by executing in a terminal (in the handout directory):
java Hello.java
Before submitting your solution on DTU Autolab, you can grade it on your own computer, by executing in a terminal (in the handout directory):
./grade
When the resulting messages say “For details, see: file:/…”, then you can open the URL beginning with
file:/...
in a browser to see more details.
02 - Taxes#
The file Taxes.java
provided in the handout computes and displays the labour
market contribution and the special pension savings of a person with an
income of DKK 120000.
Edit the file Taxes.java
to make it also compute and display the basic
tax, as described below. Then, submit the modified file Taxes.java
on DTU
Autolab.
The basic tax is computed with the following formula:
where:
\(\mathit{contrib}\) is the labour market contribution (already computed in
Taxes.java
);\(\mathit{pension}\) is the special pension savings (already computed in
Taxes.java
);\(\mathit{allowance}\) is a personal allowance that (for this exercise) must be equal to DKK 33400.
Hint
See the hints provided for the assessment 01 - Hello, World!
To see what the modified
Taxes.java
is expected to output on the terminal, you can execute, in the handout directory:./grade
You will see a message that says “For details, see: file:/…”: you can open the URL beginning with
file:/...
in a browser to see more details.