Computer Language
Lecture 12-1

Multidimensional data, writing files
and object equality

Josue Obregon

Seoul National University of Science and Technology
Information Technology Management
Lecture slides index

May 26, 2025

Class contents

  1. Introduction to Computer Programming and Java
  2. Reading Input and Strings
  3. Variables and calculation with numbers
  4. Conditional statements
  5. Repeating functionality
  6. Methods
  7. Lists
  8. Midterm
  1. Arrays and Strings
  2. Introduction to Object Oriented Programming
  3. More of Object Oriented Programming
  4. Inheritance
  5. Interfaces
  6. Project Live Code Review and Evaluation
  7. Final examination

Agenda

  • Multidimensional data
  • Writing files
  • Throwing exceptions
  • Object equality

Learning Objectives

  • Know ways to represent multidimensional data
  • Can create and use multidimensional arrays
  • You will be able to write to a file.
  • Know that some exceptions have to be handled and that some exceptions do not have to be handled.
  • You can create the method equals, which can be used to check if two objects of the same type have the same contents or state.

Multidimensional data

  • Previously we have used one dimensional arrays, where the index tells us the location of a value in the only dimension.
  • We can also create multidimensional arrays.
  • Then we need the indexes of a value in each dimension to access the value.
  • This comes handy when our data is multidimensional, for example when dealing with coordinates.
  • A two dimensional array with two rows and three columns can be created like so:
int rows = 2;
int columns = 3;
int[][] twoDimensionalArray = new int[rows][columns];
  • In the array we created above, each row refers to an array with a certain number of columns.

Iterating over 2D arrays

  • We can iterate over a two dimensional array using two nested for loops like so.
  • We can see that the default value of variables type int is 0.
int rows = 2;
int columns = 3;
int[][] twoDimensionalArray = new int[rows][columns];

System.out.println("row, column, value");
for (int row = 0; row < twoDimensionalArray.length; row++) {
    for (int column = 0; column < twoDimensionalArray[row].length; column++) {
        int value = twoDimensionalArray[row][column];
        System.out.println("" + row + ", " + column + ", " + value);
    }
}

Sample Output

row, column, value
0, 0, 0
0, 1, 0
0, 2, 0
1, 0, 0
1, 1, 0
1, 2, 0

Change values on a 2D array

  • We can change the values in the array just like before.
  • Below we set new values to three elements of the array.
int rows = 2;
int columns = 3;
int[][] twoDimensionalArray = new int[rows][columns];

twoDimensionalArray[0][1] = 4;
twoDimensionalArray[1][1] = 1;
twoDimensionalArray[1][0] = 8;

System.out.println("row, column, value");
for (int row = 0; row < twoDimensionalArray.length; row++) {
    for (int column = 0; column < twoDimensionalArray[row].length; column++) {
        int value = twoDimensionalArray[row][column];
        System.out.println("" + row + ", " + column + ", " + value);
    }
}

Sample Output

row, column, value
0, 0, 0
1, 0, 4
2, 0, 0
0, 1, 8
1, 1, 1
2, 1, 0

ArrayList of ArrayLists

  • Java also allows us to create lists of lists, for example: ArrayList<ArrayList<Integer>>.
  • This works like a 2D array but is more flexible because:
    • Rows can have different lengths.
    • You can dynamically add or remove rows and columns.
  • You still use nested loops to access or modify values.
  • Here’s the equivalent structure using ArrayList<ArrayList<Integer>>:

Example of ArrayList of ArrayLists

ArrayList<ArrayList<Integer>> matrix = new ArrayList<>();

int rows = 2;
int columns = 3;

// initialize matrix with 0s
for (int i = 0; i < rows; i++) {
    ArrayList<Integer> row = new ArrayList<>();
    for (int j = 0; j < columns; j++) {
        row.add(0);
    }
    matrix.add(row);
}

// set some values
matrix.get(0).set(1, 4);
matrix.get(1).set(1, 1);
matrix.get(1).set(0, 8);

// print matrix
System.out.println("row, column, value");
for (int i = 0; i < matrix.size(); i++) {
    for (int j = 0; j < matrix.get(i).size(); j++) {
        System.out.println(i + ", " + j + ", " + matrix.get(i).get(j));
    }
}
row, column, value
0, 0, 0
0, 1, 4
0, 2, 0
1, 0, 8
1, 1, 1
1, 2, 0

Note

The set(index, value) method replaces the element at the given index with a new value.

Writing files in Java

  • Next, let’s take a look at writing data to files.
  • The PrintWriter class offers the functionality to write to files.
  • The constructor of the PrintWriter class receives as its parameter a string that represents the location of the target file.
PrintWriter writer = new PrintWriter("file.txt");
writer.println("Hello file!"); //writes the string "Hello file!" and line change to the file
writer.println("More text");
writer.print("And a little extra"); // writes the string "And a little extra" to the file without a line change
writer.close(); //closes the file and ensures that the written text is saved to the file
  • In the example above, we write to the file “file.txt” the string “Hello file!”, followed by a line change and some additional text.
  • Take notice that when writing to the file the method print does not add line changes, and you have to add them yourself.
  • In contrast, the method println adds a new line change at the end of the parameter string it receives.

Handling exceptions when writing files

  • The constructor of the PrintWriter class might throw an exception that must be either handled or thrown so that it is the responsibility of the calling method.
  • Here is what a method that receives as its parameters a file name and the text contents to write into it could look like.
public class Storer {

    public void writeToFile(String fileName, String text) throws Exception {
        PrintWriter writer = new PrintWriter(fileName);
        writer.println(text);
        writer.close();
    }
}
  • In the writeToFile method above we begin by creating a PrintWriter object.
  • It writes data to the the file that is located at the path that the string fileName indicates.
  • After this, we write the text to the file by calling the println method.
  • The possible exception that the constructor throws has to be handled with a try-catch block or the handling responsibility has to be transferred elsewhere.
  • In the writeToFile method, the responsibility to handle the exception is placed on the method that calls writeToFile.

Using writeToFile method

  • Let’s create a main method that calls the writeToFile of a Storer object.
  • There is nothing to force the main method to handle the exception – it, too, can state that it might throw an exception by adding throws Exception to the method definition.
public static void main(String[] args) throws Exception {
    Storer storer = new Storer();
    storer.writeToFile("diary.txt", "Dear diary, today was a good day.");
}
  • By calling the method above we create a file called “diary.txt” and write the text “Dear diary, today was a good day.” into it.
  • If the file already exists, the earlier contents are erased when we store the new text.
  • It is also possible to handle files in a way that adds the new text after the existing content.
  • In that case, you might want to use the FileWriter class.

Exceptions: Shifting the responsibility

  • Methods and constructors can throw exceptions.
  • There are roughly two categories of exceptions.
    • There are exceptions we have to handle, and exceptions we do not have to handle.
  • We can handle exceptions by wrapping the code into a try-catch block or throwing them out of the method.
  • The code in the next tab reads the file given to it as a parameter line by line.
  • Reading a file can throw an exception — for example, the file might not exist or the program does not have read rights to the file.
  • This kind of exception has to be handled.
  • We handle the exception by wrapping the code into a try-catch block.
  • In this example we do not really care about the exception, but we do print a message to the user about it.
public ArrayList<String> readLines(String fileName) {
    ArrayList<String> lines = new ArrayList<>();

    try (Scanner scanner = new Scanner(Paths.get(fileName))) {
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            lines.add(line);
        }

        scanner.close();
    } catch (Exception e) {
        System.out.println("Error: " + e.getMessage());
    }

    return lines;
}

Shifting the responsibility

  • A programmer can also leave the exception unhandled and shift the responsibility for handling it to whomever calls the method.
  • We can shift the responsibility of handling an exception forward by throwing the exception out of a method, and adding notice of this to the declaration of the method.
  • Notice on throwing an exception forward throws ExceptionType is added before the opening bracket of a method.
public ArrayList<String> readLines(String fileName) throws Exception {
    ArrayList<String> lines = new ArrayList<>();
    Scanner scanner = new Scanner(Paths.get(fileName));

    while (scanner.hasNextLine()) {
        String line = scanner.nextLine();
        lines.add(line);
    }

    scanner.close();
    return lines;
}

Shifting the responsibility again

  • Now the method calling the readLines method has to either handle the exception in a try-catch block or shift the responsibility yet forward.
  • Sometimes the responsibility of handling exceptions is avoided until the end, and even the main method can throw an exception to the caller:
public class MainProgram {
   public static void main(String[] args) throws Exception {
       // ...
   }
}
  • Now the exception is thrown to the program executor, or the Java virtual machine.
  • It stops the program execution when an error causing an exception to be thrown occurs.

Throwing exceptions

  • The throw command throws an exception.
  • For example a NumberFormatException can be done with command throw new NumberFormatException(). - The following code always throws an exception.
public class Program {

    public static void main(String[] args) throws Exception {
        throw new NumberFormatException(); // Program throws an exception
    }
}

Common Java Exceptions

  • NullPointerException
    Thrown when trying to use an object reference that is null.

  • ArrayIndexOutOfBoundsException
    Thrown when accessing an invalid index in an array.

  • ClassCastException
    Thrown when trying to cast an object to an incompatible type.

  • NumberFormatException
    Thrown when converting a badly formatted string to a number.

  • FileNotFoundException
    Thrown when trying to access a file that does not exist.

Comparing the equality of objects (equals)

  • While working with strings, we learned that strings must be compared using the equals method.
  • This is how it’s done.
Scanner scanner = new Scanner(System.in);

System.out.println("Enter two words, each on its own line.")
String first = scanner.nextLine();
String second = scanner.nextLine();

if (first.equals(second)) {
    System.out.println("The words were the same.");
} else {
    System.out.println("The words were not the same.");
}

Primitive vs reference comparison

  • With primitive variables such as int, comparing two variables can be done with two equality signs.

  • This is because the value of a primitive variable is stored directly in the “variable’s box”.

  • The value of reference variables, in contrast, is an address of the object that is referenced; so the “box” contains a reference to the memory location.

  • Using two equality signs compares the equality of the values stored in the “boxes of the variables” – with reference variables, such comparisons would examine the equality of the memory references.

  • The method equals is similar to the method toString in the respect that it is available for use even if it has not been defined in the class.

  • The default implementation of this method compares the equality of the references.

Using equals method

  • Let’s observe this with the help of the SimpleDate class.
SimpleDate first = new SimpleDate(1, 1, 2000);
SimpleDate second = new SimpleDate(1, 1, 2000);
SimpleDate third = new SimpleDate(12, 12, 2012);
SimpleDate fourth = first;

if (first.equals(first)) {
    System.out.println("Variables first and first are equal");
} else {
    System.out.println("Variables first and first are not equal");
}

if (first.equals(second)) {
    System.out.println("Variables first and second are equal");
} else {
    System.out.println("Variables first and second are not equal");
}

if (first.equals(third)) {
    System.out.println("Variables first and third are equal");
} else {
    System.out.println("Variables first and third are not equal");
}

if (first.equals(fourth)) {
    System.out.println("Variables first and fourth are equal");
} else {
    System.out.println("Variables first and fourth are not equal");
}
  • Sample Output
Variables first and first are equal
Variables first and second are not equal
Variables first and third are not equal
Variables first and fourth are equal
public class SimpleDate {
    private int day;
    private int month;
    private int year;

    public SimpleDate(int day, int month, int year) {
        this.day = day;
        this.month = month;
        this.year = year;
    }

    public int getDay() {
        return this.day;
    }

    public int getMonth() {
        return this.month;
    }

    public int getYear() {
        return this.year;
    }

    @Override
    public String toString() {
        return this.day + "." + this.month + "." + this.year;
    }
}

What happened?

  • There is a problem with the previous program.

  • Even though two dates (first and second) have exactly the same values for object variables, they are different from each other from the point of view of the default equals method.

  • If we want to be able to compare two objects of our own design with the equals method, that method must be defined in the class.

  • The method equals is defined as a method that returns a boolean type value – the return value indicates whether the objects are equal.

  • The equals method is implemented in such a way that it can be used to compare the current object with any other object.

Defining the equals method for a class

  • The method receives an Object-type object as its single parameter – all objects are Object-type, in addition to their own type.
  • The equals method first compares if the addresses are equal: if so, the objects are equal.
  • After this, we examine if the types of the objects are the same: if not, the objects are not equal.
  • Next, the Object-type object passed as the parameter is converted to the type of the object that is being examined by using a type cast, so that the values of the object variables can be compared.
  • In the next slide, the equality comparison has been implemented for the SimpleDate class.

SimpleDate equals method

public class SimpleDate {
    private int day;
    private int month;
    private int year;

    public SimpleDate(int day, int month, int year) {
        this.day = day;
        this.month = month;
        this.year = year;
    }

    public int getDay() {
        return this.day;
    }

    public int getMonth() {
        return this.month;
    }

    public int getYear() {
        return this.year;
    }

    public boolean equals(Object compared) {
        // if the variables are located in the same position, they are equal
        if (this == compared) {
            return true;
        }

        // if the type of the compared object is not SimpleDate, the objects are not equal
        if (!(compared instanceof SimpleDate)) {
            return false;
        }

        // convert the Object type compared object
        // into a SimpleDate type object called comparedSimpleDate
        SimpleDate comparedSimpleDate = (SimpleDate) compared;

        // if the values of the object variables are the same, the objects are equal
        if (this.day == comparedSimpleDate.day &&
            this.month == comparedSimpleDate.month &&
            this.year == comparedSimpleDate.year) {
            return true;
        }

        // otherwise the objects are not equal
        return false;
    }

    @Override
    public String toString() {
        return this.day + "." + this.month + "." + this.year;
    }
}

Another example

  • Building a similar comparison functionality is possible for Person objects too.
  • Below, the comparison has been implemented for Person objects that don’t have a separate SimpleDate object.
  • Notice that the names of people are strings (i.e. objects), so we use the equals method for comparing them.

Person equals method

public class Person {

    private String name;
    private int age;
    private int weight;
    private int height;

    // constructors and methods


    public boolean equals(Object compared) {
        // if the variables are located in the same position, they are equal
        if (this == compared) {
            return true;
        }

        // if the compared object is not of type Person, the objects are not equal
        if (!(compared instanceof Person)) {
            return false;
        }

        // convert the object into a Person object
        Person comparedPerson = (Person) compared;

        // if the values of the object variables are equal, the objects are equal
        if (this.name.equals(comparedPerson.name) &&
            this.age == comparedPerson.age &&
            this.weight == comparedPerson.weight &&
            this.height == comparedPerson.height) {
            return true;
        }

        // otherwise the objects are not equal
        return false;
    }

    // .. methods
}

Object equality and lists

  • Let’s examine how the equals method is used with lists.
  • Let’s assume we have the previously described class Bird without any equals method.
public class Bird {
    private String name;

    public Bird(String name) {
        this.name = name;
    }
}

Checking for birds

  • Let’s create a list and add a bird to it.
  • After this we’ll check if that bird is contained in it.
ArrayList<Bird> birds = new ArrayList<>()
Bird red = new Bird("Red");

if (birds.contains(red)) {
    System.out.println("Red is on the list.");
} else {
    System.out.println("Red is not on the list.");
}

birds.add(red);
if (birds.contains(red)) {
    System.out.println("Red is on the list.");
} else {
    System.out.println("Red is not on the list.");
}


System.out.println("However!");

red = new Bird("Red");
if (birds.contains(red)) {
    System.out.println("Red is on the list.");
} else {
    System.out.println("Red is not on the list.");
}
  • Sample Output
Red is not on the list.
Red is on the list.
However!
Red is not on the list.

Is it working?

  • We can notice in the previous example that we can search a list for our own objects.

  • First, when the bird had not been added to the list, it is not found – and after adding it is found.

  • When the program switches the red object into a new object, with exactly the same contents as before, it is no longer equal to the object on the list, and therefore cannot be found on the list.

  • The contains method of a list uses the equals method that is defined for the objects in its search for objects.

  • In the example above, the Bird class has no definition for that method, so a bird with exactly the same contents – but a different reference – cannot be found on the list.

The equals mehtod for the bird class

  • Let’s implement the equals method for the class Bird.
  • The method examines if the names of the objects are equal – if the names match, the birds are thought to be equal.
public class Bird {
    private String name;

    public Bird(String name) {
        this.name = name;
    }

    public boolean equals(Object compared) {
        // if the variables are located in the same position, they are equal
        if (this == compared) {
            return true;
        }

        // if the compared object is not of type Bird, the objects are not equal
        if (!(compared instanceof Bird)) {
            return false;
        }

        // convert the object to a Bird object
        Bird comparedBird = (Bird) compared;

        // if the values of the object variables are equal, the objects are, too
        return this.name.equals(comparedBird.name);

        /*
        // the comparison of names above is equal to
        // the following code

        if (this.name.equals(comparedBird.name)) {
            return true;
        }

        // otherwise the objects are not equal
        return false;
        */
    }
}

Now Bird objects can be compared

  • Now the contains list method recognizes birds with identical contents.
ArrayList<Bird> birds = new ArrayList<>()
Bird red = new Bird("Red");

if (birds.contains(red)) {
    System.out.println("Red is on the list.");
} else {
    System.out.println("Red is not on the list.");
}

birds.add(red);
if (birds.contains(red)) {
    System.out.println("Red is on the list.");
} else {
    System.out.println("Red is not on the list.");
}


System.out.println("However!");

red = new Bird("Red");
if (birds.contains(red)) {
    System.out.println("Red is on the list.");
} else {
    System.out.println("Red is not on the list.");
}
  • Sample Output
Red is not on the list.
Red is on the list.
However!
Red is on the list.

Checking our learning objectives

  • Know ways to represent multidimensional data
  • Can create and use multidimensional arrays
  • You will be able to write to a file.
  • Know that some exceptions have to be handled and that some exceptions do not have to be handled.
  • You can create the method equals, which can be used to check if two objects of the same type have the same contents or state.

Acknowledgements


Back to title slide Back to lecture slides index