Module 6, Part 2: More About Arrays and Objects#
In this second part of Module 6 we reprise the topics addressed in Module 5, Part 1: Arrays and Module 6, Part 1: Simple Classes and Objects, and explore them in more depth.
How to access the command-line arguments of a Java program
How to define and use arrays of objects and arrays of arrays
The Array of Command-Line Arguments#
We have seen that, when writing Java programs, the execution begins with a
static method called main
(defined inside some class) that looks like this:
class ClassName {
public static void main(String[] args) {
// ...
}
}
Notice the type of the argument args
, i.e. String[]
: it means that args
is
an array of String
objects. That array lets our program access the
command-line arguments used to start the program itself.
More in detail: when a Java program is executed from a terminal, the method
main(args)
is automatically called by Java, with the array args
containing
the arguments passed to the Java program via the command-line. Therefore,
inside the body of the static method main(args)
we can access those
command-line arguments by accessing the contents of the array args
: this is
shown in Example 41 below.
(A program that examines its command-line arguments)
Consider the following program:
class CommandLineExample {
public static void main(String[] args) {
System.out.println("Number of command-line arguments: " + args.length);
for (var i = 0; i < args.length; i++) {
System.out.println("args[" + i + "] is: " + args[i]);
}
}
}
Save this program in a file called CommandLineExample.java
, and then execute
in a terminal (in the same folder where the program is saved):
java CommandLineExample.java
The output will be:
Number of command-line arguments: 0
Now, try again by executing in a terminal (in the same folder where
CommandLineExample.java
is saved):
java CommandLineExample.java foo --bar -b -a -z
The output will be:
Number of command-line arguments: 5
args[0] = foo
args[1] = --bar
args[2] = -b
args[3] = -a
args[4] = -z
Notice that each argument written on the command line (after java
and the name
of the program itself) is available to the running program as an element of the
array args
.
Comparing Arrays and Objects#
We have seen that if str1
and str2
are two objects of type String
,
the correct way to compare them
is to call the method str1.equals(str2)
, which returns true
if
str1
and str2
are equal character-by-character.
Instead, the expression str1 == str2
will often produce false
even if str1
and str2
are equal character-by-character. The same applies to Java arrays,
and to Java objects in general, as illustrated below.
If
arr1
andarr2
are arrays of the same type (e.g.int[]
), the expressionarr1 == arr2
will often returnfalse
even whenarr1
andarr2
contain exactly the same values at the same positions. To check whether the two arrays are equal, your Java programs will need to:check whether
arr1
andarr2
have the same length, andexecute a loop that checks whether each element of
arr1
is equal to the element ofarr2
with the same index.
If
o1
ando2
are objects of the same class, the expressiono1 == o2
will often returnfalse
even when the fields ofo1
ando2
contain exactly the same values. To compare two objectso1
ando2
, you should typically callo1.equals(o2)
instead. More in detail:If
o1
ando2
are objects of some class that is not defined by you (e.g., it is defined in the Java API), then the methodequals(...)
usually performs the object comparison in the expected way, so you can just callo1.equals(o2)
. (To be sure, you should check the documentation of the classC
.)Otherwise, if
o1
ando2
are objects of a class that you have defined, then you may want to define some method that performs the comparison and returns aboolean
. Following the Java conventions, you may define and implement a method calledequals(...)
allowing you to callo1.equals(o2)
(you should have already done this: see Exercise 26 and the assessment 04 - Cars 2).Important
Java provides a default implementation of the method
equals(...)
to all classes — including the classes defined by you. If you do not define the methodequals(...)
in your own classes, then the default method is used, which behaves like the operator==
: therefore, its result may not be the one you want! For example, try defining a simple class and two objects on the Java shell:class Shoe { int size; } var s1 = new Shoe(); s1.size = 43; var s2 = new Shoe(); s2.size = 43;
Now, if you call
s1.equals(s2)
, you will get the resultfalse
! This is because you are calling the default methodequals(...)
of the classShoe
, which returns the same result as the expressions1 == s2
.
Arrays of Objects and Arrays of Arrays#
We can define arrays that contain any type of data, depending on the requirements of the programs we write. In this section we explore two common scenarios:
Define and use an array that contains objects
Define and use an array that contains arrays
Arrays of Objects#
We have seen in previous modules
that, in Java, String
is a class, and a string instance (like "Hello"
) is an
object with methods. Therefore, to see a simple example of array of objects, we
can create an array of String
s on the Java shell:
jshell> var words = new String[] {"Hello", "DTU", "this", "is", "a", "test"}
words ==> String[5] { "Hello", "DTU", "this", "is", "a", "test" }
| created variable words : String[]
Now, if we want to print the length of the 3rd element of the array
words
, we can try:
jshell> System.out.println("Length of '" + words[5] + "': " + words[5].length())
Length of 'test': 4
where words[5].length()
means: “access the object stored at index 5 of the
array words
(i.e. words[5]
) and call its method length()
”.
We can also loop over all objects contained in the array and call their methods, e.g. by using a for-each loop: (try this on the Java shell!)
for (var w: words) {
System.out.println("Uppercase '" + w + "': " + w.toUpperCase());
}
Alternatively, we can produce the same output with a for
-loop:
for (var i = 0; i < words.length; i++) {
System.out.println("Uppercase '" + words[i] + "': " + words[i].toUpperCase());
}
Example 42 below shows a more complex use of arrays of objects.
(Arrays of objects)
Suppose we need to write a program that manages the inventory of a shop. To
represent the inventory, we could use an array, where each element is an object
of the class ShopItem
from Example 40.
So, let us consider again the class ShopItem
introduced in
Example 40:
1class ShopItem {
2 String name;
3 double netPrice;
4 double vat;
5 int availability;
6
7 ShopItem(String name, double netPrice, double vat, int avail) {
8 this.name = name;
9 this.netPrice = netPrice;
10 this.vat = vat;
11 this.availability = avail;
12 }
13
14 String toString() {
15 var str = this.name + " (price: " + this.netPrice + " DKK + "
16 + (this.vat * 100) + "% VAT; "
17 + "availability: " + this.availability + ")";
18 return str;
19 }
20
21 void inflateNetPrice(double percentage) {
22 this.netPrice = this.netPrice + (this.netPrice * (percentage / 100.0));
23 }
24}
The program below creates an array (called inventory
) of ShopItem
objects,
and then increases the price of each object in the array.
1class ShopInventory {
2 public static void main(String[] args) {
3 var inventory = new ShopItem[] {
4 new ShopItem("Freesbee", 115, 25/100.0, 42),
5 new ShopItem("Plant pot", 61.5, 25/100.0, 23),
6 new ShopItem("Newspaper Politiken", 61.5, 0/100.0, 23),
7 new ShopItem("Cellphone", 2500, 25/100.0, 8),
8 new ShopItem("Chocolate bar", 25, 25/100.0, 37)
9 };
10
11 System.out.println("Shop items before inflation:");
12 for (var i = 0; i < inventory.length; i++) {
13 System.out.println(" - " + inventory[i].toString());
14 }
15
16 System.out.println("Applying inflation...");
17 for (var item: inventory) {
18 item.inflateNetPrice(10.0);
19 }
20
21 System.out.println("Shop items after inflation:");
22 for (var i = 0; i < inventory.length; i++) {
23 System.out.println(" - " + inventory[i].toString());
24 }
25 }
26}
When executed, the program above prints:
Shop items before inflation:
- Freesbee (price: 115.0 DKK + 25.0% VAT; availability: 42)
- Plant pot (price: 61.5 DKK + 25.0% VAT; availability: 23)
- Newspaper Politiken (price: 61.5 DKK + 0.0% VAT; availability: 23)
- Cellphone (price: 2500.0 DKK + 25.0% VAT; availability: 8)
- Chocolate bar (price: 25.0 DKK + 25.0% VAT; availability: 37)
Applying inflation...
Shop items after inflation:
- Freesbee (price: 126.5 DKK + 25.0% VAT; availability: 42)
- Plant pot (price: 67.65 DKK + 25.0% VAT; availability: 23)
- Newspaper Politiken (price: 67.65 DKK + 0.0% VAT; availability: 23)
- Cellphone (price: 2750.0 DKK + 25.0% VAT; availability: 8)
- Chocolate bar (price: 27.5 DKK + 25.0% VAT; availability: 37)
Arrays of Arrays (a.k.a. Bidimensional Arrays)#
We can also define arrays that contain other arrays as elements. The resulting arrays are often called bidimensional arrays. This is a very common scenario, that deserves some discussion and examples.
Recall that the type int[]
describes an array that contains elements of type
int
; correspondingly, the type int[][]
describes an array that contains
elements of type int[]
— i.e. each element of the array is itself an array
which, in turn, contains int
values.
For example, you can try the following on the Java shell: create two arrays
arr1
and arr2
, and then use them as elements of an array of arrays arrArr
.
jshell> var arr1 = new int[] {1, 2}
arr1 ==> int[2] { 1, 2 }
| created variable arr1 : int[]
jshell> var arr2 = new int[] {3, 4}
arr2 ==> int[2] { 3, 4 }
| created variable arr2 : int[]
jshell> var arrArr = new int[][] { arr1, arr2 }
arrArr ==> int[2][] { int[2] { 1, 2 }, int[2] { 3, 4 } }
| created variable arrArr : int[][]
This way, we have declared the variable arrArr
as a new array that contains
the two elements arr1
and arr2
of type int[]
— and consequently, the
type of the variable arrArr
is int[][]
.
Note
When defining arrays of arrays, we can save some typing by creating the internal arrays “on the fly.” E.g. we can write:
var arrArr = new int[][] { new int[] {1, 2}, new int[] {3, 4} };
We can save even more typing by omitting new int[]
inside the array
definition. Therefore, the definition of arrArr
above can be shortened as:
var arrArr = new int[][] { {1, 2}, {3, 4} };
To access the elements of arrArr
, we simply write e.g.
jshell> System.out.println("arrArr[1][0] contains the value: " + arrArr[1][0])
arrArr[1][0] contains the value: 3
Here, arrArr[1][0]
means:
first, compute
arrArr[1]
, which produces the element at index1
ofarrArr
. Therefore,arrArr[1]
produces the array containing the values{3, 4}
;then, take the element at index
0
of the array produced byarrArr[1]
— which is the value 3.
We can modify an element of an array of arrays in a similar way. For example,
to change the value stored at index 0 of the array stored at index 1 of
arrArr
:
jshell> arrArr[1][0] = 999
...
Then, if we display the contents of arrArr
, we can observe that the value 3
has been changed to 999
:
jshell> arrArr
arrArr ==> int[2][] { int[2] { 1, 2 }, int[2] { 999, 4 } }
| value of arrArr : int[][]
(Representing a matrix as a bidimensional array)
The examples above suggests that arrays of arrays can be used to represent and
store tabular data, like matrices. For example, we could represent a matrix
with 3 rows and 3 columns as an array containing 3 arrays, which in turn contain
3 values of type double
: (try the following code snippet on the Java shell)
var matrix = new double[][] { {1.0, 2.0, 3.0},
{4.0, 5.0, 6.0},
{7.0, 8.0, 9.0} }
Then, we can print the element at the 2nd row and 3rd column of matrix
by
writing: (remember that array elements are counted from 0, so the array indexes
are row 1 and column 2)
jshell> System.out.println(matrix[1][2])
6.0
We can loop through all elements of matrix
, with two nested loops:
one loop ranges over each row, i.e. over each array stored in
matrix
then, a nested loop ranges over each element of the current row.
for (var i = 0; i < matrix.length; i++) {
// Here, matrix[i] is the array stored at index 'i' of 'matrix': it
// represents a row of values of the matrix. Therefore, 'matrix[i].length'
// is the number of values contained in row 'i' of 'matrix'
for (var j = 0; j < matrix[i].length; j++) {
System.out.println("matrix[" + i + "][" + j + "] is: " + matrix[i][j]);
}
}
(Bidimensional array with default values)
As with regular arrays, we can define a bidimensional array by specifying the
number of elements along each dimension, and without providing the value of each
element. For example, if n
is a variable of type int
with a value read from
the terminal, we can define a matrix of size n
\(\times\) n
(containing
double
s) by writing: (try it on the Java shell!)
var matrix = new double[n][n];
As a consequence, the array of arrays will be filled with the default value 0.0 — and we can modify those values afterwards.
(Adding two matrices)
Remember that the addition between a matrix \(A\) (with \(m\) rows and \(n\) columns) and a matrix \(B\) (with \(m\) rows and \(n\) columns) is a matrix \(C\) with \(m\) rows and \(n\) columns, such that:
Based on this formula, we can add two matrices a
and b
by defining a target
matrix c
with the correct number of rows and columns (and filled with default
values), and then updating each element of c
with the result of the addition.
To update each element of matrix c
, we use a nested loop (as in the
previous example). For instance:
var a = new double [][] { {1.0, 2.0},
{3.0, 4.0} };
var b = new double [][] { {10.0, 20.0},
{30.0, 40.0} };
var c = new double [a.length][a[0].length]; // Filled with 0.0
for (var i = 0; i < c.length; i++) {
for (var j = 0; j < c[i].length; j++) {
c[i][j] = a[i][j] + b[i][j];
}
}
(Jagged arrays (a.k.a. ragged arrays))
So far we have discussed “table-like” bidimensional arrays whose elements are arrays that all have the same size (like a matrix). However, bidimensional arrays may not always be “table-like”: in Java, we can easily define a jagged array (a.k.a. ragged array), i.e. an array whose elements are arrays, whose sizes may be different from each other. For example:
var jagged = new double[][] { {1.0}, {2.0, 3.0}, {4.0, 5.0, 6.0, 7.0}, {} };
Consequently, jagged
is an array that contains four arrays of double
s:
jagged[0]
has length 1;jagged[1]
has length 2;jagged[2]
has length 4;jagged[3]
has length 0.
Concluding Remarks#
You should now have an understanding of these topics:
Exercises#
Note
These exercises are not part of the course assessment. They are provided to help you self-test your understanding.
(For-each loop with bidimensional arrays)
Consider again Example 43: print the elements of matrix
by using nested for-each loops
(instead of the nested for
-loops).
(Experimenting with jagged arrays)
Consider again Example 46:
write a loop that prints the length of each array in
jagged
;write a loop that prints each element contained in each array in
jagged
.
Experiment with different kinds of loops (for
, while
, for-each
).
As a starting point, you can use the nested loop shown in Example 43.
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.
05 - Unique Command-Line Arguments#
Edit the file UniqueArgs.java
provided in the handout, and write a program
that prints all the unique command-line arguments passed to the program
itself. The program must print one unique command-line argument per line,
respecting the order in which the arguments are provided.
For example, if the program is executed from a terminal as:
java UniqueArgs.java foo bar
then the program must print:
foo
bar
Instead, if the program is executed from a terminal as:
java UniqueArgs.java foo foo
then the program must not print anything (because the argument foo
is not
unique).
When you are done, submit the modified file UniqueArgs.java
on DTU Autolab.
Hint
Example 41 shows how a Java program can read its command-line arguments.
06 - Array Formatting#
Edit the file ArrayUtils.java
provided in the handout, and implement a class
called ArrayUtils
with the following static methods:
static String format(int[] arr)
This static method takes as argument an array of integers
arr
, and computes and returns aString
containing each array element, with consecutive elements separated by one space. NOTE: there must be no space after the last array element. So, for example, if a program calls:ArrayUtils.format(new int[]{1, 2, 3})
the return value of the call should be the string
"1 2 3"
.static String format(int[][] arr)
This static method takes as argument an array of arrays of integers
arr
, and computes and returns aString
containing a table-like representation ofarr
, where:each array of integers is on a separate line, and
consecutive integers on a same line are separated by one space. So, for example, if a program calls:
ArrayUtils.format(new int[][]{{1, 2, 3}, {4, 5 6}})
the return value of the call should be a string that, when printed on screen, should look like:
1 2 3 4 5 6
NOTE: the returned string must not contain a line separator after the last line.
The handout includes some Java files called Test01.java
, Test02.java
, etc.:
they are test programs that use the class ArrayUtils
, and they might not
compile or work correctly until you complete the implementation of class
ArrayUtils
in the file ArrayUtils.java
. You should read those test programs
and try to run them, but you must not modify them.
When you are done, submit the modified file ArrayUtils.java
on DTU Autolab.
Hint
When implementing
format(int[][] arr)
, you may save some work by callingformat(int[] arr)
.When implementing
format(int[][] arr)
, you will need to include line separators in the returned string. To achieve this, you should useSystem.lineSeparator()
, as explained in the hints of 06 - Square 2.
07 - Matrix Multiplication#
Edit the file Matrix.java
provided in the handout, and implement a class
called Matrix
with the following static method:
static double[][] mult(double[][] a, double[][] b)
The static method takes two arrays of arrays of double
s (a
and b
) that
represent two matrices, and computes and returns the result of their
multiplication.
Remember that the multiplication between a matrix \(A\) (with \(m\) rows and \(p\) columns) and a matrix \(B\) (with \(p\) rows and \(n\) columns) is a matrix \(C\) with \(m\) rows and \(n\) columns, such that:
where \(C_{i,j}\) is the element of matrix \(C\) located at row \(i\) and column \(j\).
You can assume that the static method arguments a
and b
have at least one
row and one column each.
If the method mult
is called with arrays of arrays of incompatible dimensions
(i.e. the number of columns of a
is not equal to the number of rows of b
),
then the method must print on screen ERROR: the matrices cannot be multiplied
,
and it must return an array of arrays with zero rows and zero columns (that
can be created with new double[][]{}
).
The handout includes some Java files called MatrixUtils.java
, Test01.java
,
Test02.java
, etc.: they contain utility methods and test programs that use the
class Matrix
, and they might not compile or work correctly until you complete
the implementation of class Matrix
in the file Matrix.java
. You should read
those .java
files, and try to run the test programs — but you must not
modify any file besides Matrix.java
.
When you are done, submit the modified file Matrix.java
on DTU Autolab.
Hint
It may be useful to revise the examples on how to represent matrices and how to add matrices.
To implement the static method
mult
, you might follow an approach similar to this example:first, create an array of arrays of
double
s \(C\) with the desired dimensions, and with its elements having the default value for the typedouble
(which is 0.0);then, you might change the value of each element of \(C\) by using the formula for computing \(C_{i,j}\) shown above.
After that, the elements of \(C\) are the result of the matrix multiplication, and you can return it.
08 - Barf Bags Collection#
You are helping your friend Frogertha organise her collection of barf bags: the objective is to write a Java program that can read barf bags information from the terminal, and represent and memorise barf bags using classes and objects.
Your task is to edit the file BarfBagsCollection.java
included in the handout,
which defines two classes: BarfBag
(to represent a barf bag) and
BarfBagsCollection
(containing the main
static method). You have two goals
(described in detail below):
When you are done, submit the modified file BarfBagsCollection.java
on DTU
Autolab.
1. Implementing the Class BarfBag
#
Edit the class BarfBag
(at the bottom of the file BarfBagsCollection.java
)
to satisfy the following requirements.
Objects of the class
BarfBag
must have four fields called respectively:id
(e.g.42
)airline
(e.g."Air France"
)year
(e.g.2010
)value
(e.g.450.0
)
The class must have a constructor that takes the barf bag’s id, airline, year, and value, and can be called e.g. as:
var bag = new BarfBag(42, "Air France", 2010, 450.0);
BarfBag
objects must have the following method:String description()
When called, the method returns a string summarising all information about a barf bag. For instance, calling
bag.description()
(usingbag
from the previous point) must return a string of the following form:Bag 42: Air France (2010). Value: 450.0 DKK
2. Reading Barf Bag Information from the Terminal#
Edit the static method main
in the class BarfBagsCollection
and implement a
program that reads a number \(n\) from the terminal, and then reads \(n\) lines of
input text, each one containing information about a barf bag in Frogertha’s
collection.
Each line of input text read by the program contains the id, airline, year, and
value of a barf bag, separated by “;
” (i.e., a semicolon and a space).
After reading the \(n\) input lines, the program must print on the terminal the
information about the \(n\) barf bags, in the same order, formatted using the
method description()
of the class BarfBag
(see above).
For instance, here is a possible execution of the program: (the highlighted lines are the user’s input)
3
1; SAS; 2023; 25
13; Alitalia; 1991; 2300.10
42; Air France; 2010; 450.0
Bag 1: SAS (2023). Value: 25.0 DKK
Bag 13: Alitalia (1991). Value: 2300.1 DKK
Bag 42: Air France (2010). Value: 450.0 DKK
Important
If you create a scanner s
and then call s.nextInt()
followed by
s.nextLine()
, your program may read a number followed by an empty string:
the reason is explained e.g. in this article.
To avoid the issue, you may read the number of barf bags \(n\) by using (instead
of s.nextInt()
):
var n = Integer.parseInt(s.nextLine());
Hint
You can split each input line into an array of
String
s as shown in Example 35.To obtain an
int
eger from aString
, you can useInteger.parseInt(...)
. For instance,Integer.parseInt("42")
produces the value 42 (of typeint
).To obtain an
double
from aString
, you can useDouble.parseDouble(...)
. For instance,Double.parseDouble("3.14")
produces the value 3.14 (of typedouble
).
Warning
The automatic grading on DTU Autolab includes some additional secret checks that test your submission with more tests and more inputs. After you submit, double-check your grading result on DTU Autolab: if the secret checks fail, then your solution is not correct, and you should fix it and resubmit.