week11
week11
EXCEPTIONS
Exceptions are runtime errors that interrupt the regular operation of a program while it’s running.
Some examples of exceptions are division by zero, invalid user input, file not found. When an
exception occurs in a program, the way we handle it can significantly impact the program’s
reliability, maintainability, and usability. If we merely return or display strings to indicate an error,
it can lead to several potential issues such as debugging difficulty, handling exceptions correctly.
Exception handling involves using structured approaches to address and manage exceptions
when they occur. It simplifies error handling, reduces the risk of unhandled errors and improves
program robustness and user experience.
Consider the following case. What happens if the variable b has a value of 0? A runtime error
will be triggered because integer division by 0 is not allowed.
public int aMethod(int a, int b){
return a / b;
}
1/18
The try block contains the code that might cause an exception while the catch block includes
the code that will handle the exception. The code within the try block is the only code that can
be monitored for exceptions. If an exception occurs outside of the try block, it will not be
caught and handled by the associated catch block. It is important to place the code that might
throw exceptions within the try block to ensure proper exception handling. Below code is a
demonstration including try and catch blocks for the above method throwing an exception:
Exception class extends Throwable class and here, spends some time to explore different types
of exceptions in here (I verbally discussed some of them in the class):
https://docs.oracle.com/javase/8/docs/api/java/lang/Exception.html
2/18
You should use the try-catch block to catch exceptions. The prototype is as follows. As you can
see you define many catch blocks for a single try block because there might be more than one
exception occurring in try and each or some of them could be treated differently.
try{
}
catch(Exceptiontype ex1){
}
catch(Exceptiontype2 ex2){
}
catch(Exceptiontype3 ex3){
}
… // some more catch blocks
In Java, the Throwable class is the superclass of all errors and exceptions. It provides several
fields and methods that can be used in case of an exception.
Fields:
Methods:
● printStackTrace(): Prints the throwable and its backtrace to the standard error stream.
● fillInStackTrace(): Fills the stack trace of this throwable with the current stack trace, as
determined by the current thread.
● initCause(Throwable): Initializes the cause of this throwable to the specified value.
● toString(): Returns a string representation of this throwable.
These fields and methods can be used to obtain information about an exception, such as its
message, stack trace, and cause. They can also be used to handle exceptions in a more
informative and robust manner.
The code outputs a detailed message to the user by retrieving the message from the
exception’s getMessage method.
try {
File file = new File("data.txt");
Scanner scanner = new Scanner(file);
while (scanner.hasNextLine()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
3/18
System.out.println("File not found: " + e.getMessage());
}
The finally block is used to specify code that will always execute after a try block, regardless of
whether an exception was thrown or caught. This guarantees the proper management and
cleanup of resources, even when errors occur. You should ideally clean up resources here. Its
execution is guaranteed.
try {
// Code that may throw an exception
} catch (ExceptionType e) {
// Code to handle the exception
} finally {
// Code that will always execute
}
An example of a finally block to close the opened file. We should close the file no matter what.
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
System.out.println(reader.readLine());
} catch (IOException e) {
System.out.println("An error occurred: " + e.getMessage());
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
System.out.println("Failed to close reader: " + e.getMessage());
}
}
System.out.println("Cleanup complete.");
}
Extending Exceptions
You can even create your own exceptions by extending an Exception. For example,
IllegalArgumentException is used to indicate that a method has been passed an argument that
is not valid. We can create a custom exception named AgeNotInRangeException by extending
IllegalArgumentException. The new exception provides a more descriptive, specific error for age
range violations.
public class AgeNotInRangeException extends IllegalArgumentException {
private int age;
4/18
public AgeNotInRangeException(String message) {
super(message);
}
In a method where we need to set an age, we can throw this exception as shown below:
public void setAge(int age) {
if (age < 0 || age > 100) {
throw new AgeNotInRangeException("Age must be between 0 and 100.", age);
}
System.out.println("Age set to: " + age);
}
GENERICS
When students learn programming, they typically start with arrays, which are fixed-size and
non-generic (do not worry what this means, we will now study generics) data structures. These
limitations naturally lead to the introduction of more flexible collections like ArrayList, HashMaps,
HashSet in Java, which are both generic and dynamic.
Arrays are fixed sized and type-safe. Once created, you cannot change the size unless you
create a copy, which is indeed another array. You cannot add more elements and mix types.
int [ ] elements = new int[3]; // cannot shrink or grow. The type is int
String [ ] names = = {“Ali”, “Mehmet”, “Can”}; // cannot shrink or grow. The type is String
elements [0] = 1;
elements [1] = 2;
elements [2] = 3; // cannot add any further. You must manually copy the contents
// it is type-safe. You cannot work with mixed types
5/18
elements [0] = “ali”; → illegal
Since every type is Object, you can create an Object array to mix the types. However, you need
an explicit cast to perform type-specific operations. But it is unsafe because typecast may lead
to a runtime error caused by type mismatches (ClassCastException)..
Object [ ] elements = new Object [3]; // cannot shrink or grow. The type is Object
elements [0] = “murat”;
elements [1] = 2;
elements [2] = “Can”;
// type casting is necessary. But it is unsafe
String myName = (String)elements [0]; // not a runtime error
String lastName = (String)elements [1]; → runtime error, ClassCastException
The moral is we detect errors in runtime. Generics let us detect errors in compile time. The
concept of generics refers to the use of parameterized types. Parameterized types are
important because they allow the creation of classes, interfaces, and methods that can
operate on various data types specified as parameters. Such classes, interfaces, or
methods that work with type parameters are referred to as generic.
With this capability (parameterized types), the compiler can substitute parameterized types
with concrete types.
Before defining our own generic classes or methods. Let’s see some built-in generics. We have
just discussed the arrays, which suffers from being fixed size and non-generic. Java has already
some built-in collections, which do not suffer from these. A popular example is ArrayList. It can
be dynamically sized, type safe, generic, and has many built-in functionality for collection
operations. You can define a generic ArrayList with the following syntax:
ArrayList<String> list = new ArrayList<>();
Here, the parameterized type is String. We are actually declaring that we want to work with the
String type. The parameterized type is placed between the angle brackets, < >. <String>
defines the type the ArrayList will work with.
// Using ArrayList
ArrayList<String> names = new ArrayList<>();
names.add("Ali");
names.add("Ahmet");
names.add("Bilal");
.
.
.
names.add("Zehra"); // you can create as many as. Dynamic sizing.
6/18
You can create another ArrayList object to work with a different type:
// Using ArrayList
ArrayList<Integer> numbers = new ArrayList<>(); // notice type is Integer not int
numbers .add(1);
numbers .add(2);
numbers .add(3);
.
.
.
numbers .add(1024); // you can create as many as. Dynamic sizing.
Integer i = numbers.get(0); // no casting is needed.
You can create an object of a Wrapper class with the new operator or by directly assigning a
value. However, creating an instance with the new operator is deprecated after Java 9. So, it is
a better idea to avoid it.
Double d = new Double(13.2); → deprecated since version Java 9
Double e = 13.2;
An important question should pop in your mind: “if wrappers are reference types, should i use
equals method instead of == operator to compare two instances of it for the value equality?”
→ The answer is YES. Avoid using the == operator to compare values for equality. For instance,
with Integer objects, the == operator may yield accurate results for small values, but this
behavior does not hold for large values. The JVM caches objects representing small integer
values (usually within the range of -128 to 127). Consequently, the == operator might return true
in such cases, even though the objects being compared are distinct.
Integer i1 = 5;
Integer i2 = 5;
i1 == i2; → returns true in my jShell
i1 = 10293;
7/18
i2 = 10293;
i1 == i2; → returns false in my jShell
Self study:
ArrayList (collections in general) has many built-in functionalities as Wrapper classes do. Our
main focus is generics now. So, please explore them.
public T get() {
return value;
}
}
// GenericInterface.java
public interface GenericInterface<T> {
void add(T item);
T get(int index);
int size();
}
When creating an instance, you must be giving the concrete type. Below, we specify the
concrete type as Integer.
GenericClass <Integer> myGenericClass = new GenericClass<>();
8/18
Before starting: Java provides a built-in LinkedList class as part of the java.util package. We will
name our implementation as CustomLinkedList since this is a handcrafted version designed for
educational purposes. This name distinguishes itself from the standard library.
Let’s revise LinkedList: LinkedList is an ADT (abstract data type) that consists of a sequence of
elements, called nodes, where each node contains data and a reference/pointer to the next
node in the sequence. We will use a single directional list. Operations we will provide are listed
below:
● Add: Inserts elements at the end of the list.
● Remove: Deletes a specific element by value.
● Size: Returns the number of elements.
● IsEmpty: Checks if the list is empty.
● PrintList: Displays the elements in sequence.
● Get: Retrieves an element at a specific index.
Nodes
A single element will be represented as a node. We will create a Node class for it. Obviously,
this class should have a field for the data and a reference/pointer to the next node. We want our
Node class to be generic.
public class Node<T> {
private T data;
private Node<T> next; //reference/pointer to the next Node
// Constructor
public Node(T data) {
this.data = data;
this.next = null;
}
9/18
}
}
The Node class has a field named data. This field holds the value stored in the node. It is
generic, allowing the Node to store any type of data (T). So, our LinkedList will be versatile
and reusable for different data types. The second field is called next. This field is a
reference/pointer to the next Node in the linked list. If the node is the last in the sequence, this
reference should be null, indicating the end of the list. The Node constructor initializes the node
with a given value (data) and sets the next reference to null. Notice how parameterized types
are utilized throughout the code. For example, the constructor accepts a type that will be
determined at runtime.
CustomLinkedList class
The CustomLinkedList class is a generic implementation of a singly linked list. It provides a
dynamic (by adding and removing elements), linear data structure (singly linked) where
elements are connected through nodes. This class encapsulates the essential operations to
manage a linked list, such as adding, removing, and accessing elements.
import java.util.Iterator;
// Constructor
public CustomLinkedList() {
this.head = null;
this.size = 0;
}
10/18
current.setNext(newNode); // set this node as the last
}
size++; // increase the size (the number of elements)
}
11/18
System.out.println("null");
}
The CustomLinkedList has a private reference/pointer to the first node in the list called head. It
is the entry point for traversing the linked list. If the list is empty, head is null. The class also
has a private integer field called size that keeps track of the number of elements in the list. It
ensures efficient size retrieval without requiring traversal of the entire list. The class constructor
initializes an empty linked list. It sets head to null and size to 0, meaning that the list is initially
empty.
The associated methods are summarized as follows:
● add: The add method inserts an element at the end of the list. It creates a new Node
with the provided data and sets head to the new node if the list is empty. Otherwise,
traverses to the last node and updates its next reference to point to the new node.
Finally, it increments the size counter to keep track of the number of elements in the list.
● remove: The remove method deletes a node containing the specified data. It updates
head if the element to be removed is the first node. Otherwise, traverses the list to
locate the node before the target (the one to be removed) and adjusts its next
reference to bypass the target node (by getting the target's next). Only if the element is
found and removed, the size counter is decremented.
● get: The get method retrieves the data of a node at a given index. It ensures the index is
within bounds and traverses the list to the desired position.
● size: The size method returns the number of elements in the list.
● isEmpty: The isEmpty method checks if the list is empty by comparing size to 0.
● printList: The printList method traverses the list from head to the last node, printing the
data of each node followed by an arrow (->) to indicate the connection.
By using generics (<T>), the CustomLinkedList class is highly flexible, allowing it to store and
manage any type of data. This generic design makes the class reusable across different use
cases without being tied to a specific data type.
QA session
12/18
Q: Why are we using the equals() method instead of == in the remove method?
→As you know, the == operator checks reference equality. The equals method checks value
equality. By default, the equals method in the Object class performs the same operation as ==.
However, many classes (e.g., String, Integer or the classes you have created) override it to
compare the actual content or value of objects rather than their memory locations. Remember
that the Node class is generic (<T>), meaning it can store any type of object. For custom
objects or wrapper classes like String or Integer, you typically want to compare values, not
references. Using equals ensures that the comparison is based on the logical equality defined
for the type T.
Usage Example
public class App{
public static void main(String[] args) {
//A new CustomLinkedList of type Integer is created
CustomLinkedList<Integer> list = new CustomLinkedList<>();
// Add elements
list.add(10);
list.add(20);
list.add(30);
list.printList(); // Output: 10 -> 20 -> 30 -> null
// Remove an element
list.remove(20);
list.printList(); // Output: 10 -> 30 -> null
// Get size
System.out.println("Size: " + list.size()); // Output: Size: 2
// Check if empty
System.out.println("Is empty: " + list.isEmpty()); // Output: Is empty: false
13/18
nameList.add("Alice");
nameList.add("Bob");
nameList.add("Charlie");
nameList.printList(); // Output: Alice -> Bob -> Charlie -> null
// Remove an element
nameList.remove("Bob");
nameList.printList(); // Output: Alice -> Charlie -> null
// Add elements
numberList.add(10);
numberList.add(20);
numberList.add(30);
for (int index = 0; index < numberList.size(); index++) {
Integer number = numberList.get(index);
System.out.println(number);
}
Notice the above code that the user of our code needs to know how to get the length of the
CustomLinkedList, and how to get an element from it. These two functionalities are required to
traverse the CustomLinkedList. At this point, remember that we used to have another loop type
in Java that supports collections, which is foreach. It is designed to simplify code by abstracting
the traversal logic, focusing on the action to be performed (this refers to what are you going to
14/18
do with the elements in the collection) on each element rather than the mechanics of iteration
(this term refers to the requirements to traverse the collection. In our case, it is to get the length
of the collection and get an element out of it).
Let’s try to use foreach with our CustomLinkedList to traverse it. The below code generates an
error.
CustomLinkedList<Integer> numberList = new CustomLinkedList<>();
// Add elements
numberList.add(10);
numberList.add(20);
numberList.add(30);
for (Integer number : numberList) { → ERROR: CANNOT PERFORM THIS RIGHT NOW
System.out.println(number);
}
You might be wondering what the problem is because we were using a very similar notation with
arrays and it used to work with arrays (by the way you can use foreach with ArrayList as well).
The reason is that foreach structure iterates over collections, arrays, or any other objects that
implement the Iterable interface.
If you go to the above link, you will see that it is a generic interface with only one abstract
method to be implemented (go to the method summary section and click abstract methods),
which is iterator() whose return type is Iterator<T>, as shown below.
Iterator <T> iterator();
If you have a look at default methods, you will see that there are already two default methods
implemented in this interface, which are given below.
default void forEach(Consumer<? super T> action)
default Spliterator<T> spliterator()
As you all know, we do not need to implement these two methods because their
implementations are already provided. By the way, the Consumer and SplitIterator are other
interface types, and we will soon (next week) discuss what <? super T> means in the foreach
method.
Now, we know that we need to implement the Iterable interface and the method we certainly
need to implement in this interface is iterator. Wait a minute! The return type of the iterator
method is a generic interface, Iterator <T>.
15/18
What is the Iterator interface?
→ It is in java.util (you will need to import it, import java.util.Iterator;). It is used to traverse
elements in a collection sequentially. It defines a standard protocol for accessing elements
one at a time, which is especially useful for collections like lists, sets, and custom data
structures. Documentation: https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html
If you go to the above link, you will see that it is a generic interface with two abstract
methods to be implemented (go to the method summary section and click abstract methods),
which are hasNext() and next() whose return type are boolean and E (generic), respectively,
as shown below (It has two default methods as well called, forEachRemaining and remove).
boolean hasNext()
E next()
We need to implement the Iterator interface just before we implement Iterable because the
iterator method of the Iterable will return an Iterator. Below is our implementation of Iterator
class:
import java.util.Iterator;
// constructor
// assign head
public CustomLinkeListIterator(Node<T> head) {
this.currentNode = head;
}
@Override
public boolean hasNext() {
return currentNode != null;
}
@Override
public T next() {
// calling next before hasNext will cause an exception
// if there is no next
if(!hasNext())
throw new java.util.NoSuchElementException();
T temp = currentNode.getData(); // get the current
currentNode = currentNode.getNext(); // make the current next anymore, move to next
return temp; // return the current
}
}
The CustomLinkeListIterator class is a generic implementation of the Iterator interface. This
iterator class (CustomLinkeListIterator) provides functionality to iterate through the nodes of a
16/18
linked list and access their data sequentially. It is parameterized with a type <T>, which ensures
type safety, allowing it to be used with any data type.
● The class has a single private field, currentNode, which keeps track of the current
position within the linked list. This field is initialized in the constructor with the head
node of the linked list (we will provide CustomLikedList head) and is updated as the
iterator moves through the list (next method).
● The constructor of the CustomLinkeListIterator takes a Node<T> object, representing
the head of the linked list. This head node will be the starting point for iteration.
● The hasNext method determines whether there are more elements to iterate over in
the linked list. It returns true if currentNode is not null, indicating that the iterator has
not yet reached the end of the list. If currentNode is null, it signifies that there are no
more nodes to process. So, the method returns false.
● The next method retrieves the data stored in the current node and advances the
iterator to the next node in the linked list. Before accessing the data, the method checks
whether the next element exists by calling hasNext. If no elements are left, it throws a
NoSuchElementException to signal that the end of the list has been reached. Otherwise,
it fetches the data from the currentNode, updates currentNode to point to the next node,
and returns the retrieved data.
Now that we have completed our implementation of Iterator, let’s go back to our
CustomLinkedList class to implement Iterable. Below is the implementation:
import java.util.Iterator;
@Override
public Iterator<T> iterator() {
return new CustomLinkeListIterator<T>(this.getHead());
}
}
The implementation code for the iterator method returns an Iterator instance,
CustomLinkeListIterator. That is all we need to do here. The method wants us to return an
instance of Iterator, so we returned it. It looks so easy that you might think there is something
missing, but nope, it is really that simple this time!
Let’s now use it in our foreach loop to see it runs (you try it yourself, I will just say it is OK). The
code just now traverses the CustomLinkedList instance without a problem and lists all the
numbers inside.
CustomLinkedList<Integer> numberList = new CustomLinkedList<>();
17/18
// Add elements
numberList.add(10);
numberList.add(20);
numberList.add(30);
// Add elements
numberList.add(10);
numberList.add(20);
numberList.add(30);
while(linkedListInterator.hasNext())
{
Integer number = linkedListInterator.next();
System.out.println(number);
}
An iterator is created for numberList by calling the iterator() method on it. The type
Iterator<Integer> indicates that the iterator will return Integer objects as it moves through the list.
The hasNext() method checks if there are more elements to be accessed in the list. If hasNext()
returns true, the next() method retrieves the next element from the list, which is stored in the
number variable. We have implemented an interface (Iterator) that defines the behavior within a
loop. Here, you can see that the above is not actually different from our own implementation.
18/18