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.

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.

Example 41 (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 and arr2 are arrays of the same type (e.g. int[]), the expression arr1 == arr2 will often return false even when arr1 and arr2 contain exactly the same values at the same positions. To check whether the two arrays are equal, your Java programs will need to:

    1. check whether arr1 and arr2 have the same length, and

    2. execute a loop that checks whether each element of arr1 is equal to the element of arr2 with the same index.

  • If o1 and o2 are objects of the same class, the expression o1 == o2 will often return false even when the fields of o1 and o2 contain exactly the same values. To compare two objects o1 and o2, you should typically call o1.equals(o2) instead. More in detail:

    • If o1 and o2 are objects of some class that is not defined by you (e.g., it is defined in the Java API), then the method equals(...) usually performs the object comparison in the expected way, so you can just call o1.equals(o2). (To be sure, you should check the documentation of the class C.)

    • Otherwise, if o1 and o2 are objects of a class that you have defined, then you may want to define some method that performs the comparison and returns a boolean. Following the Java conventions, you may define and implement a method called equals(...) allowing you to call o1.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 method equals(...) 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 result false! This is because you are calling the default method equals(...) of the class Shoe, which returns the same result as the expression s1 == 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:

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 Strings 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.

Example 42 (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 index 1 of arrArr. Therefore, arrArr[1] produces the array containing the values {3, 4};

  • then, take the element at index 0 of the array produced by arrArr[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[][]

Example 43 (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]);
    }
}

Example 44 (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 doubles) 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.

Example 45 (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:

\[ C_{i,j} \;=\; A_{i,j} + B_{i,j} \]

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];
    }
}

Example 46 (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 doubles:

  • 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.

Exercise 27 (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).

Exercise 28 (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

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:

  1. static String format(int[] arr)
    

    This static method takes as argument an array of integers arr, and computes and returns a String 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".

  2. static String format(int[][] arr)
    

    This static method takes as argument an array of arrays of integers arr, and computes and returns a String containing a table-like representation of arr, 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 calling format(int[] arr).

  • When implementing format(int[][] arr), you will need to include line separators in the returned string. To achieve this, you should use System.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 doubles (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:

\[ C_{i,j} \;=\; \sum_{k = 1}^{p} A_{i,k}\, B_{k,j} \]

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 doubles \(C\) with the desired dimensions, and with its elements having the default value for the type double (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):

  1. implement the class BarfBag, and

  2. write a program to read barf bag information from the terminal.

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() (using bag 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 Strings as shown in Example 35.

  • To obtain an integer from a String, you can use Integer.parseInt(...). For instance, Integer.parseInt("42") produces the value 42 (of type int).

  • To obtain an double from a String, you can use Double.parseDouble(...). For instance, Double.parseDouble("3.14") produces the value 3.14 (of type double).

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.