top of page

Week 14

In Week 14 we will discuss comparison of objects, what makes and object or a type. We will then also discuss maps, iterators and the comparable interface.

Object

What can be an Object?

ObjectsExample.png

Let us quickly summarise all kinds of objects we have seen.

  • Instances of Classes: The primary type of object is an instance of a class. Whenever you use the new keyword followed by a class constructor, you create a new object of that class.

  • Arrays: In Java, arrays are objects too. They are dynamically-created objects that can hold primitives or references to other objects and have a member variable length which contains the number of components in the array.

  • Anonymous Objects: These are objects of a class that are declared and instantiated at the same time and do not have a reference variable.

  • Wrapper Class Instances: Instances of wrapper classes (Integer, Character, Boolean, etc.) are objects that encapsulate a primitive value in an object.

  • Strings: Instances of the String class, like any other classes available in Java, including those from the Java standard library (like ArrayList, HashMap, etc.), are objects.

  • Enums: In Java, enums are a special data type that enables for variables to be a set of predefined constants. The Java language specification refers to enums as a class type, and each of the enumerated values becomes an instance of the enum class. More on this later.

Enums

Definition and Usage

EnumExample.png

Enums, short for enumerations, are a special data type in Java that enables for the definition of a set of named constants. They are used to represent a fixed set of constants, such as days of the week, states in a state machine, command options, and more. Enums offer a type-safe way to work with a set of predefined values. Using enums can make your code more readable and maintainable by providing meaningful names for sets of related values.

You define enums using the enum keyword, followed by the name of the enumeration type, and then a list of enum constants enclosed in curly braces. Once an enum is defined, you can use it as a type for variables, parameters, return types, etc. The features of enums are the following.

  • final and public: They are implicitly final and public.

  • extend Enum: They extend the java.lang.Enum class.

  • Class-like: They can have fields, constructors, and methods.

  • Implement interfaces: They can implement interfaces.

Switch Case

Definition and Usage

SwitchExample.png

In Java, a switch statement allows you to select one of many code blocks to be executed. This is a cleaner way of writing a sequence of if-else if statements. It works with the byte, short, char, and int primitive data types, wrapped classes Byte, Short, Character, Integer, enumerated types (enums) and the String class. To the left one can see the basic structure of a switch statement.

The switch expression is evaluated once and compared with the values of each case. If there is a match, the associated block of code is executed. The break keyword is used to exit the switch statement. If it is omitted, the code will continue to run into the next case, which is usually not desirable (this is known as "falling through").

In the example to the left, the switch statement checks the value of day, and assuming it is 4, it matches case 4 and dayString is set to "Thursday". After the code associated with case 4 is executed, the break statement prevents the code from "falling through" to case 5.

Switch Case

Enhanced Switch

EnhancedSwitch.png

The enhanced switch statement, introduced in Java 12 as a preview feature and standardized in Java 14, is a more flexible and simplified version of the traditional switch statement that aims to improve its readability and usability. The main enhancements include the introduction of arrow labels (->) and yield statements for returning values from switch expressions.

The key features which are new in enhanced switch statements are the following.

  • Arrow Labels (->): This syntax separates the case label from the code to execute and automatically includes what would have been an implicit break.

  • Switch Expressions: Switch can now be used as an expression that returns a value. It allows you to directly assign the result of a switch to a variable.

  • Yield: In switch expressions, the yield statement is used to return a value from a block of code associated with a case label.

  • Pattern Matching (as of Java 16): Enhanced switch works with pattern matching for the instanceof operator, allowing more expressive type checks and casts.

The yield is necessary when you want to return a value from a complex block with more than just a single expression.

Repetition

Nested Classes

NestedClasses.png

In Java, inner classes are divided into two categories: non-static inner classes (also known as inner classes) and static inner classes. Here are the key differences.

  • Non-static inner class: An inner class is associated with an instance of the outer class. It can directly access the instance members of the outer class because it carries a reference to its enclosing instance. An object of an inner class can only exist within an instance of the outer class. You need an instance of the outer class to construct an instance of an inner class. Cannot declare static members (except for constant variables) and cannot have top-level access modifiers like public, private, etc., as they are implicitly associated with an object's state. 

  • Static inner class: A static inner class does not have any reference to an instance of the outer class. It can be instantiated without an instance of the outer class and can only access the static members of the outer class. You can instantiate it just like any static member of the outer class and it can be used to group classes that belong together without creating a dependency on the outer class instance. Can have static members and can also be declared with access modifiers just like any other top-level class.

Types

What makes a New Type?

TypesExample.png

We have seen many ways of creating a new type in Java but have rarely said that we created a new type. Let us now look at each of these examples.

  • Class Definitions: A class definition (class MyClass { ... }) creates a new reference type. Classes can include fields, methods, constructors, and inner classes.

  • Interface Definitions: An interface (interface MyInterface { ... }) is a reference type in Java that can contain constants and abstract methods. Implementing an interface allows a class to become more formal about the behaviour it promises to provide.

  • Enum Definitions: An enum type (enum MyEnum { ... }) is a special data type that enables a variable to be a set of predefined constants.

  • Array Types: When you declare an array (int[] myArray;), you are defining a new type that consists of multiple elements of the same type, arranged in a sequential order.

  • Generic Type Parameters: When you define a class or interface with type parameters (class MyClass<T> { ... }), you’re creating a generic type that can be specialized to many different kinds of types while retaining the same class name.

All these entities define new types in Java and can be instantiated (except for interfaces and abstract classes on their own) to create objects that are instances of these types. A type in Java defines a set of properties (fields) and behaviors (methods) that its instances will have.

Comparable

Definition and Usage

Comparable.png

The Comparable interface in Java is used to impose a natural ordering on the objects of each class that implements it. This interface consists of a single method, compareTo, that compares the current object with the specified object for order. It returns a negative integer, zero, or a positive integer if the current object is less than, equal to, or greater than the specified object.

To enable sorting of collections (like lists) of custom objects, the objects’ class must implement the Comparable interface and the compareTo method.

In the example code to the left, the compareTo method compares Person objects based on their age. When Collections.sort is called, it uses this method to determine the order of the objects within the list.

You can reverse the order in which a collection is sorted by using the Collections.reverseOrder() method in conjunction with the Collections.sort() method. The reverseOrder() method returns a comparator that imposes the reverse of the natural ordering on a collection of objects that implement the Comparable interface.

Comparator

Definition and Usage

Comparator.png

The Comparator interface in Java is used to define a custom order for objects when sorting a collection. Unlike the Comparable interface, which requires modifying the class whose instances you want to sort, a Comparator is external to the elements it compares. This means you can define multiple different comparison strategies for a single class without altering the class's definition.

A Comparator is defined as a separate, usually stateless, class or as an anonymous inner class or lambda expression. It must implement a single method called compare, which takes two arguments of the type you want to compare. You can pass a Comparator to the sort method of the Collections class or the Arrays utility class to sort a collection or array respectively.

Java Maps

Definition and Usage

MapExample.png

Maps in Java are part of the Collections Framework and they store key-value pairs, where each key is unique. Maps are used when you want to identify a value by a unique identifier, similar to a real-life dictionary. The three main types of maps are the following.

  • HashMap: This is the most commonly used Map implementation. It stores the key-value pairs in a hash table, which allows for fast access to values based on their key. It does not maintain any order of elements. The keys are hashed to a bucket, and the key-value pairs are stored in the bucket. This can lead to faster lookups (on average O(1) time complexity) because it uses the key's hashcode to find its bucket directly. However, in the worst-case scenario (e.g., when hashcodes collide), operations can be O(n).

  • LinkedHashMap: This is a subclass of HashMap but it also maintains a double-linked list connecting all the entries. This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order). This linked list adds a predictable iteration order to the map without compromising the key lookup efficiency of a HashMap too much. It is slightly slower than HashMap for insertion and deletion because it has to deal with maintaining the linked list.

  • TreeMap: It is implemented based on red-black trees, and it maintains its elements in a sorted order by key. The default order is the natural ordering of keys, but you can specify a custom Comparator during the map's creation to control the order. TreeMap allows for the retrieval of elements in their sorted order, and operations like "find the least key greater than X" (higherKey()), "find the greatest key less than X" (lowerKey()), etc. It generally has a time complexity of O(log n) for operations like get, put, and remove because it has to traverse a tree structure.

Java Sets

Definition and Usage

SetExample.png

Sets in Java are a collection type that, like mathematical sets, store unique elements—they do not allow duplicates. Here are the three main implementations of the Set interface.

  • HashSet: This is the most commonly used Set implementation. Internally, it uses a HashMap to store its elements. The elements are not stored in any particular order; the order is essentially arbitrary and can change over time, especially after rehashes due to resizes. The main benefit of a HashSet is the constant time complexity (O(1)) for the basic operations (add, remove, contains, and size), assuming the hash function disperses the elements properly across the buckets.

  • LinkedHashSet: This is an ordered version of HashSet that maintains a doubly-linked list across all of its elements. This linked list defines the iteration ordering, which is the order in which elements were inserted into the set (insertion-order). A LinkedHashSet is a good choice when you care about the iteration order and still want quick query times.

  • TreeSet: This set is implemented using a red-black tree (a self-balancing binary search tree). It stores its elements in a sorted order, which can be the natural ordering or the order specified by a Comparator provided at set creation time. The log(n) time cost for the basic operations (add, remove, and contains) is a trade-off for the guaranteed ordering.

Java Iterator

Definition and Usage

IteratorExample.png

An Iterator in Java is an interface that provides a way to traverse a collection of objects, one at a time. It is a part of the java.util package. The Iterator interface declares methods that can be used to perform the following operations.

  • Check if there are more elements: The hasNext() method returns true if the iteration has more elements. This means you can use this method to check if you should continue iterating over the collection.

  • Get the next element: The next() method returns the next element in the iteration. If there are no more elements, it throws a NoSuchElementException.

  • Remove the current element: The optional remove() method removes from the underlying collection the last element returned by the iterator. It's important to note that Iterator.remove() is the only safe way to modify a collection during iteration; doing so otherwise may result in a ConcurrentModificationException.​​

Java Iterator

for-each Loop

for  (Type item : collection)  {

        //Loop Body - item can be used here

}

ForEachExample.png

The for-each loop  is a specialised form of the for loop that simplifies the iteration over collections and arrays. It eliminates the need for a counter or iterator, making the code cleaner and less prone to errors.

To the left one can see the general syntax where Type is the type of elements in the collection or array, item is the loop variable that takes on the value of an element from the collection or array with each iteration and collection is the collection or array you want to iterate over.

The for-each loop is internally implemented using the Iterator pattern, so it's also applicable to any class that implements the Iterable interface. This includes all collection classes and even custom classes, if they provide an iterator. It's important to note that the for-each loop does not allow modification of the underlying collection or array, such as adding or removing elements. If you need to modify the collection while iterating, you must use an explicit Iterator.

That is it.

You are done with theory. To make sure you understand the theory you need to apply what you learnt in practice. Solve the exercises.

bottom of page