0% found this document useful (0 votes)
15 views182 pages

COSC 121 - Computer Programming II

Uploaded by

williamkusuma10
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
0% found this document useful (0 votes)
15 views182 pages

COSC 121 - Computer Programming II

Uploaded by

williamkusuma10
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 182

COSC 121 - Computer Programming

II
OOP Revision

The Basics

What are ‘software’ objects?


- In a Java program, objects represent entities in the real-world
- Each object has its own space in the memory to save information about this
object

Coding with objects


- How are objects created in the real-world?
- Two phases. Example: cars.
Phase 1: Designing Objects
- A class represents the blueprint of a group objects of the same type
- This class defines the attributes and behaviours for objects
- Attributes
- Defined as variables inside our class
- We call them “instance variables”
- Behaviour (actions)
- Defined as methods inside our class

- Example: the farmer class

Phase 2: Creating and Using Objects


- Next, we need to create objects based on our class
Using the Updated Design
- Using the new keyword
- We will do this inside the main method

Creating Several Objects

Default Values
- Data fields (object attributes or instance variables) can be of the following types”
- Primitive
- Example: int, double, etc
- Default values:
- 0 for a numeric type
- False for a boolean type, and
- \u0000 for a char type
- Reference types
- Example: string, arrays, or other class types
- Default values
- Null, which means that the data field does not reference
any object

Constructors
- Constructors play the role of initializing objects
- Constructors are a special kind of method
- They have 3 peculiarities
- Constructors must have the same name as the class itself
- Constructors do not have a return type – not even void
- Constructors are invoked using the new operator when an object is created

The Default Constructor


- A default constructor is provided automatically only if no constructors are explicitly
defined in the class
- It sets the attributes to their default values:
- String → null
- Numeric → zero
- Boolean → false
- In the previous example, the programmer included a four-argument constructor, and
hence the default constructor was not provided

More on Basic OOP

Public/Private Visibility Modifiers


- Access modifiers are used for controlling levels of access to class members in Java:
- Public:
- The class, data, or method is visible to any class in any package
- Private:
- The data or methods can be accessed only the declaring class

Data Field Encapsulation


- It is preferred to declare the data fields private in order to
- protect data from being mistakenly set to an invalid value
- Example: c1.radius = -5 //this is logically wrong
- Make code easy to maintain
- You may be to provide two types of methods:
- A getter method (also called an ‘accessor’ method):
- Write this method to make a private data field accessible
- A setter method (also called a ‘mutator’ method):
- Write this method to allow changes to a data field
- Usually, constructors and methods are created public unless we want to “hide them.

The “this” Keyword


- this is used inside a method or a constructor to refer to the current object, whose
method/constructor is being called.
- Use ‘this’ to avoid naming conflicts in the method/constructor of your object.
- For what ‘items’ can I use ‘this’?
- To reference class members within the class.
- Class members can be referenced from anywhere within the class
- Examples:
- this.x = 10;
- this.amethod(3,5);
- To enable a constructor in invoke another constructor of the same class
- A constructor can only be invoked from within another constructor
- Examples:
- this(10,5);
Practice
- Code these two classes in Java
- Make sure that no invalid values are assigned to the attributes
- Use “this” whenever possible

The ‘static’ Modifier


- Static class members:
- Static variables (also known as class variables) are shared by all the instances
(objects) of the class.
- Static methods (also known as class methods) are not tied to a specific object
(they carry out a general function)
- Example: Math.max(3, 5);
- Remember that, unlike static class members:
- Instance-variables belong to a specific instance (i.e object).
- Instance methods are invoked by an instance of the class

Passing Objects to Methods


- Remember: Java uses pass-by-value for passing arguments to methods:
- Passing primitive variable:
- The value is passed to the parameter, which means we will have two
distinct primitive variables.
- Example: changes that happens inside the method do not influence the
original variable
- Passing reference variable
- The value is reference to the objects, which means the two references
(the argument and the parameter) will refer to the same object. Changes
that happen inside the method using the passed reference are applied to
that object

Array of Objects
- To create an array of objects, you need to follow two steps:
1. Declaration of reference variables:
a. You can create an array of objects, for example,

b. An array of objects is actually an array of reference variables. We don’t


have any objects created yet.
2. Instantiation of objects:
a. To initialize circles, you can use a for loop like this one:
- You may then invoke any method of the circle objects using a syntax similar to this:

- This involves two levels of referencing:


- Circles references the entire array, and
- Circles[1] references to a circle object
- Example:
Inheritance
Inheritance Overview
- Inheritance is a mechanism for enhancing and extending existing, working classes
- In real life, you inherit some of the properties from your parents when you are
born. However, you also have unique properties specific to you.
- In Java, a class that extends another class inherits some of its properties
(methods, instance variables) and can also define properties of its own.
- Extends is the key word used to indicate when one class is related to another by
inheritance.
- Syntax: class subclass extends superclass
- The superclass is the existing, parent class
- The subclass is the new class which contains the functionality of the superclass
plus new variables and methods
- A subclass may only inherit from one superclass/

Why Use Inheritance?


- The biggest reason for using inheritance is to reuse code.
- Once a class has been created to perform a certain function it can be reused for
other programs
- Further, using inheritance the class can be extended to tackle new, more
complex problems without having to re-implement the part of the class that
already works.
- The alternative is copy and paste which is bad, especially when code changes
- Example

-
- A better solution is to have a superclass, e.g Shape, that has the common code
and then have Circle and Rectangle inherit from Shape

What is Inherited?
- When a subclass inherits (or extends) a superclass:
- Instance variable inheritance:
- All instance variables of the superclass are inherited by the subclass
- However, if a variable is private, it can only be accessed using methods,
defined by the superclass.
- Method Inheritance:
- All superclass methods are inherited by the subclass, but they may be
overridden.
Inheritance Example
What Can You Do in a Subclass?
- A subclass inherits from a superclass. You can:
- Use inherited class members (properties and methods)
- Add new class members.
- Methods:
- Override instance methods of the superclass
- To modify the implementation of a method defined in the
superclass
- The method must be defined in the subclass using the the same
signature and the same return type as in its superclass
- Hide static methods of the superclass
- By writing a new static method in the subclass that has the same
signature as the one in the superclass
- Constructors:
- Invoke a superclass constructor from within a subclass constructor
- Either implicitly
- Or explicitly using the keyword super

Overriding Methods

Overriding Methods
- Overriding allows a subclass to modify the behaviour of an inherited method as needed.
- I.e: provide a different implementation of a method that is already provided by the
superclass
- Overriding happens when you implement a method in a subclass that has the same
- Signature (name and parameters) and
- Return type (or subtype)
- as a method in its superclass
Overriding vs Overloading
- Overridden methods are in different classes related by inheritance
- Overloaded methods can be either in same class or different class
- Overridden methods has the same signature and return type
- Overloaded methods have the same name but a different parameter list

this and super Keywords

The “this” Keyword


- The “this” keyword is the name of the reference that an object can use to refer to itself
- Uses:
- To reference class members within the class
- Class members can be referenced from anywhere within the class
- Examples:
- this.x = 10;
- this.amethod(3,5);

- To enable a constructor to invoke another constructor of the same class


- A constructor can only be invoked from within another constructor
- Example:
- this(10,5);

The “super” Keyword


- The keyword “super” refers to the superclass of the class in which super appears.
- Uses:
- To reference class members in the superclass
- Example:
- super.amethod(3,5);
- super.toString();
- To enable a constructor to invoke another constructor of the superclass
- A constructor can only be invoked from within another constructor:
- Example:
- super(10,5);

Example: “this” and “super” for class members


Superclass Constructors

Explicit & Implicit Calling of Superclass Constructor


- If no constructor is called within a given constructor, Java implicitly calls the super
constructor. For example, the following two segments of code are equivalent:

Example
- This example is based on the Circle class present a few slides ago

- In above output, we created a White, Filled circle, although these attributes were not
coded in the Circle constructor:

- The reason is, the Circle constructor calls the super constructor by default
Constructor Chaining
- Constructing an instance of a class invokes all the superclasses’ constructors along the
inheritance chain. This is known as constructor chaining.

Example on the Impact of a Superclass Without No-arg Constructor

- No code inside the subclass


- Define apple constructor
- It must address the parent class into the child class

“Final” Modifier
The “final’ Modifier
- A “final” local variable is a constant inside a method
- The “final” class cannot be extended:

- The “final” method cannot be overridden by its subclasses

Visibility Modifiers Revisited

Visibility Modifiers
- Access modifiers are used for controlling levels of access to class members in Java. We
shall study two modifiers:
- “Public”,
- The class, data, or method is visible to any class in any package
- “Private”,
- The data or methods can be accessed only by the declaring class
- If no access modifier is used, then a class member can be accessed by any class in the
same package
A Subclass Cannot Weaken Accessibility
- A subclass may override a protected method in its superclass and change its visibility to
public. However, a subclass cannot weaken the accessibility of a method defined in the
superclass. For example, if a method is defined as public in the superclass, it must be
defined as public in the subclass.

The “Object” Class and Its Methods

The “Object” Class


- Classes in Java are descendants of java.lang.Object class

- Several methods are inherited from Object such as:


- public String toString()
- Returns a string representation of the object
- public boolean equals(Object obj)
- Indicates whether some other object is “equal to” this one

The “toString()” Method


- The toString() method returns a string representation of the object.
- Usually you should override the toString method so that it returns a descriptive string
representation of the object.
- For example, the toString method in the “Object” class was overridden in the
“Shape” class presented earlier as follows:

Polymorphism

Polymorphism Part A

What is ‘Polymorphism’?
- Term: A class defines a type. A type of a subclass is called a subtype, and a type of its
superclasses is a subtype
- E.g: Circle is a subtype of shape and shape is a supertype for circle
- Polymorphism: The ability of an object to take on “many forms”
- In Java, a reference variable of a supertype can refer to any of its subtype objects
- This allows us to perform a single action (method) in different ways
- More about this shortly
- Every instance of a subclass is also an instance of its superclass, but not vice
versa
- E.g: Every circle is a shape object, but not every shape is a circle
The Three Rules
- Rule 1: A reference of a supertype can be used to refer to an object of a subtype (not
vice versa)
- Rule 2: You can only access class members known to the reference variable
- Rule 3: When invoking a method using a reference variable x, the method in the object
referenced by x is executed, regardless, of the type of x

Polymorphism in Java
- A reference variable of a supertype can refer to any of its subtype objects, but not vice
versa
Passing References to Methods
Dynamic Binding and Rule #3
- Assume
- C1 is a subclass of C2, C2 is a subclass of C3,..., and Cn-1 is a subclass of Cn
- An object obj is an instance of C1 (and hence it is also an instance of C2,...,Cn)
- How does dynamic binding work?
- If we invoke a method obj.p(), the JVM searches the implementation for the
method p() in C1, C2,..., Cn-1 and Cn in this order, until it is found. Once an
implementation is found, the search stops and the first-round implementation is
invoked (RULE 3)
Polymorphism Part B

Generic Programming
- Writing methods that are sued genetically for different types of arguments
- Here we use polymorphism
- If a method’s parameter type is a supertype (e.g Object), you may pass an object
to this method of any of the parameter’s subtype (e.g Student or String).
- When an object is used in the method, the particular implementation of the
method of that object that is invoked is determined dynamically
Method Matching vs Dynamic Binding
- Matching a method signature and dynamically binding a method implementation are two
issues.
- Dynamic binding is used for overridden method and happens during the runtime, Here,
Java tries to bind the method call to a method definition up a class-inheritance hierarchy
- First searches a class for the method’s implementation then its parent(s).
- Static Binding: In case of methods overloading, the compiler finds a matching method
according method signature at compilation time
- Method signature = parameter type, number of parameters, and order of the
parameters
Exercise

Example
Project P1

Summary of Some Uses of Polymorphism


- Potential benefits of polymorphism:
1. Generic programming
- Writing methods that are used generically (by means of polymorphism) for a wide
range of object arguments
- Example: a method that displays the name of an item

2. Creating an array that contains objects of different but related types, all children
of one super type
instanceof Operator
- Use the instanceof operator to test whether an object is an instance of a class
- Imagine that you have two classes, A and B. And you have a reference x of a supertype
that points to an object of either A or B, but you don’t know which one. You may use this
code:
instanceof and Inheritance
- An object is an instance of its own class type and also of all its parent classes

Implicit Objects
- Casting objects refers to that one object reference can be typecase into another object
reference

- The above statement is legal because an instance of Student is automatically an


instance of Object

Explicit Casting
- Suppose you want to assign the reference obj to a variable of a Student type as follows:

- Why error?
- Object obj is not necessarily an instance of Student. Even though you can see
that obj is really a Student object, the compiler is not so clever to know it
- To fix this error
- Tell the compiler that obj is a Student object, use explicit casting:
Example: Objects Casting + instanceof
- The displayShapeInfo method displays the diameter if the object is a circle and the
height & width if the object is a rectangle

Exercise

Caution
- For casting to be successful, you must make sure that the object to be case is an
instance of the subclass
- If the superclass object is not an instance of the subclass, a runtime ClassCastException
occurs
The Object’s equals Method
- The “==” operator is use to compare the references of the object. Comparing two
references to equality does not compare the contents of the objects referenced
- public boolean equals(Object O) is a method provided by the Object class. The default
implementation uses “==” operator to compare two objects as follows:

- It is expected that programmers will override this method so that it is used to compare
the values of two objects
- Try the following code with the current implementation of the Circle class (slide 6):

- The output is false as the two references c1 and c2 point to different objects. However,
we can override the equals method in the Circle class to compare the values in the
objects instead. The simplest implementation is as follows:

- And the value of c1.equals(c2) would be true

Abstract Class and Interfaces

Abstract Classes

Abstract Classes and Methods


- Abstract classes are usually used to define common features to their subclasses
- You cannot create objects of an abstract-class type
- An abstract class may contain abstract methods

- Syntax:
- Abstract method have a header but not a body

- Syntax:
- Concrete classes (i.e not abstract) cannot contain abstract methods
- In a concrete subclass extended from an abstract class, all abstract methods
must be implemented (overridden), even if they are not used in the subclass
- If a subclass of an abstract superclass does not implement all the abstract
methods, the subclass must be defined abstract
- Why abstract methods
- Abstract methods are used to specify common behaviour for subclasses that
have different implementation of those methods

Abstract Class Example


Notes About Abstract Classes/Methods
- A class that contains abstract methods must be abstract
- It is also possible to define abstract class with no abstract methods
- A subclass can be abstract class can override a concrete method from its superclass to
define it abstract
- This is rare, but useful when implementation of the method in the superclass
becomes invalid in the subclass
- You can define constructors in an abstract class, which are invoked in the constructors of
its subclasses
- Remember that you cannot create objects of abstract classes
- You can create reference variables of an abstract-class type
- Example: Shape shp = new Circle();
Interfaces

Interfaces
- An interface is a class-like construct that contains:
- Constants
- Methods: abstract, default, or static
- An interface defines common behaviour for classes (including unrelated classes) and
provide default implementation of some of that behaviour
- For example, you can specify that the unrelated objects are comparable, edible,
and/or cloneable using interfaces

Abstract Classes vs Interfaces


- In many ways, an interface is similar to an abstract class. But remember that an
interface is not a class
- For example, interfaces cannot have constructors or instance variables
- Furthermore, conceptually they are “not parents” to implementing classes but rather
define common behaviour and allow multiple inheritance
- As we will see shortly
- Similar to abstract classes:
- You cannot create objects of an interface type
- You can create reference variables of an interface type

How To Define an Interface


- Interfaces have zero or more of the following items:

- Example: Edible interface to specify whether an object is edible


Example

Notes on Code in Previous Slide


1) The statement:

is equivalent to
2) The above array uses references of the Object type, which means it can contain
instances of any class type
3) Since howToEat() and sound() are not methods of the Object, elements retrieved from
the array had ot be cast to the appropriate types using Edible or Animal.

Some Rules
- A class can implement several interfaces

- An interface can extend one or more other interfaces

- Omitting modifiers in an interface:


- All data fields are public static final
- All methods are public abstract in an interface

- Accessing constants:
- A constant defined in an interface can be accessed using the dot operator along
with the name of the interface or the implementing class (or an instance of that
class)
Exercise

More on Interfaces

Interface Structure
- Interfaces have zero or more of the following items:

default Methods
- “default” methods provide default implementation in an interface
- Why need them? For backwards compatibility (i.e support adding new methods to
current interfaces without breaking the old code

static Methods
- “static” methods are implemented methods that are called using the interface name

- Static methods can only be called by interface name as follows:

private Methods
- “private” methods are accessible only within that interface
- Why?
- Default and static methods are public by default. If you want part of the code to
be accessible only in the interface, use private
Java Standard Interfaces

Java Standard Interfaces


- Java provides standard interfaces to serve different purposes
- For example:
- Comparable
- This interface allows comparing or sorting objects
- Cloneable
- This interface allows cloning objects (creating exact copies)

The Comparable Interface

Comparable

- When you want to compare two objects, e.g two employees: is e1 > e2? The two
objects must be comparable
- Any class that implements the “Comparable” interface must have a method compareTo
- The “compareTo” method allows the class to define an appropriate measure of
comparison
- The Comparable interface is a generic interface
- The generic type T is replaced by a concrete type when implementing this interface

Comparable: Example
Comparable: Exercise

The Cloneable Interface

Cloneable
- In order to create a clone of an object (i.e, a field for field copy of the object) a class must
implement the Cloneable interface
- By convention, classes that implement this interface should overrible
Object.clone (which is protected with a public method)
A Few More Notes About Cloneable
- The Object’s clone method returns the type ‘Object’ which must be cased to the desired
type

- The cloneable interface is a marker interface (i.e empty)


- We implement Cloneable to indicate to the clone() method that it is ok for it ot make a
field-to-field copy of instances of that class.
- If you try to invoke the clone method on an instance that does not implement Cloneable,
CloneNotSupportedException will be thrown
- Handling exceptions (e.g CloneNotSupportedException) is covered in Chapter 12

Cloneable: Shallow vs Deep Copy


- The Object’s clone metho performs a ‘Shallow Copy’
- Assume

- For a primitive field, its value is copied


- E.g the value of x and y (int type) are copied from robot1 to robot2
- For an object field, the reference of the field is copied
- This means robot1.b and robot2.b will refer to the same Battery object
- In order to perform Deep Copy, you have to clone each usb-object within the
containing class’s clone method

- This is more detailed code, but serves the same purpose


The Project

Exception Handling
What are Java Exceptions?
- When a program runs into a runtime error, the program terminates abnormally. How can
you handle the runtime error so that the program can continue to run or terminate
gracefully?
- Exception handling enables a program to deal with exceptional situations and continue
its normal execution
- An exception is an object that represents an error or a condition that prevents execution
from proceeding normally
- Examples:
- A program tries to access an out-of-bound array;
- A program tries to connect to a website using an invalid address;
- A program runs out of memory
- A program tries to read a file of integers but finds a string value in the file
- If the exception is not handled, the program will terminate abnormally
Example

How Exception Handling Works?


1. When an error is detected, Java stops the normal flow of program execution
a. The code throws an exception
2. An “exception object” is created
a. This object stores information about the error
b. There are different kinds of exception classes
3. Java transfers control from the part of the program where the error occurred to an
exception handler, which deals with the situation
4. The exception object is passed to the exception handler code where you can use it to
decide on the appropriate action
- Note that: Exceptions are objects, and objects are defined using classes. The root class
for exceptions is java.lang.Throwable

Java Exceptions
Unchecked Exceptions
- RuntimeException, Error and their subclasses
- They can occur anywhere in a program, usually because of programming logic errors
- To avoid cumbersome overuse of try-catch blocks, Java does not require you to
write code to handle them
- Example
- Subclasses of Error
- VirtualMachineError the JVM is broken or has run out of the resources it
needs in order to continue operating
- Subclasses of RunTimeException
- ArithmeticException: Dividing an integer by zero
- IndexOutOfBoundsException: Accessing an index (e.g of an array)
outside the valid limits
- NumberFormatException: When you try to convert a String to a numeric
value, but that the string does not have the appropriate format
- E.g int x = Integer.parselnt(s); //error if e.g, s = ‘abc’
- NullPointerException: Accessing an object through a reference variable
before an object is assigned to it

Checked Exceptions
- Exception subclasses except RuntimeException
- The compiler forces you to check and deal with them
- No default action when encountered (i.e Java doesn’t throw nor catch the
exception… Needs you to tell it what to do)
- Examples:
- Subclass of IOException
- EOFException occurs when a program attempts to read past the end of a
file
- FileNotFoundException can occur when a program attempts to open or
write to an existing file, but the file is not found
- MalformedURLException indicates that an invalid form of URL (such as a
website address) has occurred

How to Deal with Exceptions?


- For checked exceptions, you must do one of two things (you can also use them with
unchecked exceptions, but you don’t have to):
1. Declaring exceptions: Declare in the method header that an exception may be
thrown. In this case you don’t handle the exception within the method, but pass it
onto the calling method
2. Handling exceptions: Catch the exception and deal with it within the method,
using a try-catch statement
- For unchecked exceptions, Java uses technique (1) by default (unless you write code to
change that)

Declaring Exceptions
- Declaring an exception causes the methods where the exception occurs to throw the
exception to the calling method
- To declare an exception in a method, use the keyword “throws” followed by the
type of exception in the method header
- If the method might throw multiple exceptions, use the following notation

Handling Exceptions
- To deal with an exception within a method, use “try-catch”
- Example: When this handler code is executed, the variable within the catch clause will
contain a reference to the exception object that has been thrown

- You can also catch an exception using a parent class of the throw exception
- Why is this possible?
- If more than one type of exception is possible, we can add additional catch clauses, like
this:

- Each catch block is examined in turn, from first to last, to see whether the type of the
exception object is an instance of the exception class in the catch block
- It is possible to have more than on try statement within a method. This clearly defines
the area of code where each exception is expected to arise

- The “finally” clause is always executed regardless whether an exception occurred or not.
It may be used to ‘clean up’ by releasing any resources such as memory or files that a
method has been using before the exception was thrown
Exception Propagation
- If no handler is found in a method, Java exits the method, passes the exception to the
method that invoked the method, and continues the same process to find the handler
- If no handler is found in the chain of methods being invoked, the program terminates and
prints an error message on the console
Example 1: Using Try-catch

Example 2: No Try-catch
Example 3: Using a Parent Exception Class

Example 4: Catch with a Different Type


Example 5: Several Catch Statements

Getting Info from Exceptions


- You may use the following methods with an exception object to get valuable information
about the exception
- getMessage(): String
- Returns the message that describes this exception object
- toString(): String
- Returns “name of exception class : getMessage()”

When to use Exception Handling?


- Defensive programming
- It is appropriate when the potential error is predictable and localized
- E.g, checking that a queue is not full before adding a new element
- Exception handling
- It is intended for conditions that are…
- Unpredictable: Caused by external events out of the control of the
program, such as file errors
- Widespread: Occurring at many places in the program
- Exception handling separates error-handling code from normal flow, thus making
program easier to read and to modify
- Be aware, however, that the exception handling usually requires more time and
resources because it requires instantiating a new exception object, rolling back the call
stack, and propagating the errors to the calling methods
Exercise

TextIO

Input and Output Streams


- A stream is a sequence of bytes, representing a flow of data from a source to a
destination
- Sources and destinations include:
- Keyboard, screen, data files, and networked computers

- Any read or write is performed in three simple steps


1. Define an object of a class used to read or write from a stream
2. Read or write data using the methods of the above object
3. Close the stream
Text I/O Classes

Scanner
- Note that for this code to work properly, we need to write some error handling code to
handle IO exceptions
- Common Scanner Methods:
- Input methods:

- Test methods:
Caution
- Not having the right format in your text file may lead to errors. For example, both
programs below generate exceptions

Solution
- Solution 1: make sure you have the right format
- Not good enough. You need to protect your code from crashing!
- Solution 2: Use defensive programming or exception handling

PrintWriter

- You can use print(), println(), and printf() methods


- Data in aaa.txt will be overridden
- You need to write some error handling code to handle the situation when the file cannot
be created
BufferedReader
- The BufferedReader wraps another reader class and improves performance
- InputStreamReader to get input from keyboard as bytes and converts it to
characters
- FileReader to get input from character files
- FileReader is a subclass of InputStreamReader
- The BufferedReader as a wrapper
- Provides a readLine method to read a line of text
- Improves efficiency (faster than Scanner)
- Java uses similar “writer” classes for writing data
- OutputStreamWriter, FileWriter, BufferedWriter

BufferedReader with InputSteamReader


BufferedReader with FileReader

Text I/O
The File Class
- The file class provides an abstraction that deals with most of the machine-dependent
complexities of files and path names in a machine-independent fashion. The filename is
a string. The File class is a wrapper class for the file name and its directory path
- Some useful File methods:
- Exists(): boolean
- Returns true if the file or the directory exists
- canRead(): boolean
- Returns true if the file exists and can be read
- isDirectory(): boolean
- Return true if the File object represents a directory
- isFile(): boolean
- Returns true if the File object represents a file

Try-With-Resources
- Programmers often forget to close the file. JDK 7 provides the following new
try-with-resources syntax that automatically closes the files

Reading from the Web


- After a URL object is created, you can use the openStream() method defined in the URL
class to open an input stream and use this stream to create a Scanner object as follows:
Idea to Check the Validity of Input TYPE

Note: InputMismatchException is thrown by Java when the input does not match the type. It
can also be explicitly thrown by you whenever the input doesn’t match your own criteria, e.g:
Example 1

Example 2
Defensive Programming Solution

Exception Handling Solution


Binary IO

Text I/O vs Binary I/O

Storing Data on a Computer


- Computers do not differentiate between files and text files. All files/data are stored in
binary format. And thus all files are essentially binary files
Text I/O vs Binary I/O
- Text I/O is build upon binary I/O in order to provide a level of abstraction for character
encoding and decoding
- Example: suppose you write number 199 to a file…
- Using text I/O: Each character is written to the file using the file’s encoding
scheme. E.g, for character ‘1’, it can be written in 8 bits if ASCII is used (0x31) or
16 bits for Unicode (0x0031)

- Using binary I/O: the numeric binary equivalence of 199 is written


Remember: Text I/O Classes

Binary I/O Classes


InputStream/OutputStream

Binary I/O Classes

InputStream
- +read(): int
- Reads the next byte of data as int (0 to 255)
- -1 is returned at the end of stream
- +read(b: byte[]): int
- Reads up to b.length bytes into array b
- +read(b: byte[], off: int, len: int): int
- Stores read bytes into

- +skip(n: long): long


- Skips n bytes of data from this stream. Actual # of bytes skipped returned
- +available(): int
- Return the number of bytes remaining in the input stream
- available ()==0 indicates the end of file (EOF)
- +close(): void

OutputStream
- +write(int b): void
- Writes (byte) b to this output stream
- +write(b: byte[]): void
- Writes all the bytes in array b to the output stream
- +write(b: byte[], off: int, len: int): void

- Writes into the


output stream
- +flush(): void
- Flushes this output stream and forces any buffered output bytes to be written out
- +close(): void
- Closes this output stream

FileInputStream/FileOutputStream

Binary I/O Classes

FileInputStream
- Methods:
- All methods from InputStream
- Constructors
- public FileInputStream (String filename)
- public FileInputStream(File file)
- Exceptions

FileOutputStream
- Methods:
- All methods from OutputStream
- Constructors:
- public FileOutputStream(String filename)
- public FileOutputStream (File file)
- If the file doesn’t exist, a new file would be created
- If the file already exists, the current contents in the file are deleted
- public FileOutputStream(String filename, boolean append)
- public FileOutputStream(File file, boolean append)
- Used to retain the current content and append new data into the file
- Exceptions:
- Throws IOException

Exercise 1

More Exercises
Exercise Solutions

Exercise4 (hidden)
DataInputStream/DataOutputStream

Binary I/O Classes

DataOutputStream
- Wraps around FileOutputStream
- Converts primitive types or strings → bytes and output the bytes to the stream
- DataOutputStream out = new DataOutputStream(new FileOutputStream(“file.dat”));
- Methods: All methods of OutputStream in addition to:

DataInputStream
- Wraps around FileInputStream
- It reads bytes from the input stream → interprets binary data as primitive types or
strings
- DataInputStream in =
- new DataInputStream(new FileInputStream(“file.dat”));

- String chaining is a way of connecting several streams to get the data in the form
required
- Methods: All methods of InputStream in addition to:
Exercise

DataOutputStream and Strings


- How do wer know the end of a string?

- Writes the number of bytes taken by s in the first two bytes, then s itself in
modified-UTF8 format
- Modified UTF8 stores a character using 1 or more bytes, depending on
the character
- ASCII characters are stores in 1 byte, Other characters may
require more than one byte
- The initial bits of a UTF-8 text indicate the length of the character
- Example

Aside: Modified UTF format


- This first two bytes are called the UTF length and specify the number of additional bytes
to be read. These bytes are then converted to characters by considering them in groups
- The length of each group is computed from the value of the first byte of the group

- After every group has been converted to a character by this process, the characters are
gathered, in the same order in which their corresponding groups were read from the
input stream, to form a String, which is returned.

Exercise
Checking End of File
- So how do you check the end of a file?
- Remember that DataInputStream is a descendent of InputStream → use
available() method to check the stream, input.available() == 0 indicates the end
of a file

Exercise

Efficiency?
- available() method is not very efficient method (i.e take some time to return result).
- An alternative way is to keep reading until your reader object fails to get any data.
- If you try to read past the end-of-life, DataInputStream will throw an exception
EOFException. We can use this in our code as follows:
ObjectInputStream/ObjectOutputStream

ObjectOutputStream
- Can be used to write serializable objects and primitive data
- Wraps FileOutputStream
- Converts primitive types or strings → bytes and output the bytes to the stream
- ObjectOutputStream out =
- New ObjectOutputStream(new FileOutputStream(“file.dat));

ObjectInputStream
- Can be used to read serializable objects and primitive data
- Wraps FileInputStream
- It reads bytes from the input stream → converts them to primitive types, strings,
objects
- ObjectInputStream in =
- new ObjectInputStream(new FileInputStream(“file.dat”));

The Serializable Interface


- Not all objects can be written to an output stream. Objects that can be written to an
object stream is said to be serializable
- The class of serializable object must implement Serializable
- The Serializable interface is a marker interface
- It has no methods, so you don’t need to add additional code in your class that
implements Serializable
- Implementing this interface enables the Java serialization mechanism to automate the
process of storing the objects and arrays.

Example 1:

Example 2: Serializing Arrays

The transient Keyword


- If a serializable object contains non-serializable data fields, can the object be serialized?
- Answer:
- NO. To enable the object to be serialized, you have two options:
1. Make all data fields serializable
a. Their classes must implement serializable
2. Use the transient keyword with these fields so that the JVM ignores them when
sending the object to the output stream

Example:
Improving I/O Performance

Binary I/O Classes

BufferedInputStream / BufferedOutputStream
- Used to speed up I/O by reducing the number of disk reads and writes
- Methods
- All methods from InputStream/OutputStream
- Constructors
- public BufferedInputStream(InputStream.in) //bufferSize = 512
- public BufferedInputStream(InputStream in, intbufferSize)
- public BufferedOutputStream(OutputStream in) //bufferSize = 512
- public BufferedOutputStream(OutputStream in, intbufferSize)
Example: Using BufferedInputStream

Exercise

Tips

Using FileOutputStream with PrintWriter


- An instance of FileOutputStream can be used as an argument to construct a PrintWriter.
You can create a PrintWriter to append text into a file
Recursion
Introduction to Recursion
- Previously, you have seen methods that call other methods

- A recursive method is a method that calls itself

Example 1
- This recursive methods prints the numbers from n to 1
- Can you think of non-recursive ways of writing the print method

- Observations:
- Recursive code can be rewritten using loops
- Both include a condition to end the recursive call or loop

Stopping Condition
- Similar to loops, recursive methods need a “stopping condition” that determines when to
stop the ‘repetition’

So, why Recursion?


- Recursion can be used to solve problems that are difficult to program using simple loops
- That is, recursion provides an easier, more intuitive ways to solve these problems
compared to loops
- For example:
- Traversing through file system
- Traversing through a tree of search results
- Computer science algorithms courses have many examples similar to above two
- Today
- We are going to practice on basic recursive methods that can be easily solved by
either recursion or loops
- Next lecture
- Problems that are much easier to solve with recursion

Remember
- Recursion is a method that calls itself
- Recursive code can be re-coded with loops, but sometimes it is easier to use recursion
instead of loops
- Recursive methods include:
- A stopping condition (just like the condition that we use to control loops)
- A recursive call that involves a sub-problem resembling the original problem

Think Recursively!!

What is a Recursive Problem?


- Whenever you are able to break a problem into sub-problems (smaller problems) that
resembles the original problem
- This means that you can apply the same approach to solve the subproblem
recursively
Think Recursively

Let’s Apply to the print() Method…


- Think recursively about print(5):
- (print 1 to 5) can be broken down to
1) (print 5)
2) THEN (print 1 to 4), which is a simpler problem that resembles (print 1 to
5)
- Remember that we need a stopping condition:
- (Until n = 1)

Other Problems to Think Recursively


- Searching with a maze for an item
- With recursion:
1. Move one step in each direction from where you are standing
2. If target is not there, search for a smaller maze
- Stopping condition: Target Found

- With loops:
- ???
Think Recursively
- Fractals
- A fractal is a geometrical figure that can be divided into parts, each of which is a
reduced-size copy of the whole
- Example: let’s say we want to draw a fractal tree

- Drawing a tree problem


- Can you identify the subproblem in the following draw-tree problem?

- Sierpinski Triangle
1. Start with an equilateral triangle, which is considered to be the fractal of order (or
level) 0
2. Connect the midpoints of the sides of the triangle of order 0 to create a Sierpinski
triangle of order1
3. Leave the center triangle intact. Connect the midpoints of the sides of the three
other triangles to create a Sierpinski of order 2
4. You can repeat the same process recursively till you reach the desired order

- Fractal Art

Back Again to Coding…

Example 2: Factorial
- Factorial Defintion:

- Write a ‘recursive’program to computer n!


- Thinking recursively: computing n! Includes computing (n-1)! Which resembles n! Except
that it is smaller
- Steps:
- Recursion might not be you best option to implement a factorial function. Loops are
simpler and more efficient
Example 3: Fibonacci Numbers

- The Fibonacci series begins with 0 and 1, and each subsequent number is the sum of
the preceding two
- Stopping conditions (Base cases)
- fib(0) = 0;
- fib(1) = 1;
- Recursive call
- fib(index) = fib(index-1) + fib(index-2); index>=2
- Recursion is not efficient, since it requires more time and memory to run the recursive
call. BUT the recursive implementation of fib method is very simple and straightforward.
Lets compare it to loops-based implementation

Summary
- What is a recursive method?
- A method that calls itself
- Recursive methods must have:
1. A recursive call (that calls the same method again)
a. Every recursive call reduces the original problem, bringing it closer to a
base case until it becomes that case
2. One or more base cases used to stop recursion
a. A stopping condition tells the method to stop calling itself when the
problem becomes in its simplest form (i.e the “base case”)
b. The method uses conditionals (e.g if-else, switch) to run different cases
- When to use recursion?
- When solving problems that have recursive structures
- Coding these problems recursively is simpler and more intuitive
- Any problem that can be solved recursively can be solved nonrecusrively (with
iterations) but recursion may provide simpler ways to solve the problem

More Practice

Practice 1

Practice 2
Practice 3

Practice 4

Recursion Part B

How Recursion Really Works

Example 1, again
- As we saw earlier, this method prints the numbers from n to 1
Example 1 (reordered)
- What is the output after reordering the statements?

Example 1 (reordered): Memory Visualization


- Let’s see this again with memory visualization
Example 2: Factorial, Again
Example 2: Factorial, Memory Visualization

Recursive Helper Methods

Recursive Helper Methods


- Recursive helper method
- A recursive method that is called by another method that is usually non-recursive
- Example:
- The recursive isPalindrome method we saw before is not efficient
- Because it creates a new string for every recursive call
- To avoid creating new strings, use two additional parameters, low and high, that
point to the beginning and end of the same string
- The two parameters will point to characters from both ends and moving
towards the middle:
- Example, cont’d
- In the previous slide, you noticed that every time we want to use isPalindrome,
you have to pass two additional arguments,1 and length-1
- This is tedious and could be easily solved by adding an additional non-recursive
method that calls our recursive helper method as in the following slide

Exercises
Some Problems that have a Nice Recursive Solution

Recursive Selection Sort


- Given a list, sort the items in the list in ascending order
- Algorithm:
- Find the smallest element in the list and swap it with the first element
- Ignore the first element and sort the remaining smaller list
- Recursion:
- Problem: Sort list = (put min element first) THEN (sort smaller list)
- stopping condition(Base case): the list only contains one element

- The Algorithm
- As long as there is a “remaining part”, do the following:
- Find the smallest element in the remaining part
- Swap with the current element
- Recursively repeat the process after ignoring the current element
Recursive Selection Sort

Recursive Binary Search


- Algorithm:
- Compare the value with the element in the middle of the array:
1. If (value < middle element), recursively search for the key in the first half
of the array
2. If (value > middle element), recursively search of the key in the second
half of the array
3. If (value == middle element), search ends with a match
- Recursive calls: Cases 1 and 2 reduce the search to smaller list
- Stopping conditions:
- A match is found (Case 3)
- Cannot find a match (search is exhausted without a match)
Some Problems that REALLY Need Recursion

More Recursive Problems


- The preceding examples can easily be solved without using recursion
- You will see now examples of some problems that are hard to solve without recursion
- Directory size
- Tower of Hanoi
- Fractals
- Recursive method are efficient for solving problems with recursive structures

Compute Directory Size


- Objective: Find the size of a directory
- directory size = sum of all file sizes + all subdirectories’ sizes

- Useful Java methods (of the File class):


- length() returns the size of a file in bytes as long number
- listFiles() returns an array File objects under a directory
- isDirectory() returns true if the File object refers to a directory

- Think of this problem as a tree (branch = folder, leaf = file)


- Stopping condition: you reach a leaf (a file: return the size of this file)
- Recursion: you find a branch (a directory: return the sum of the sizes of all
subdirectories and files)
- size(d) = size(item1) + size(item2) + …+ size(itemn)
- Item could be a file or a directory

Practice 6
Tower of Hanoi
- Problem:
- There are n disks labeled 1,2,3,...,n and three towers labeled A, B, and C
- All the disks are initially placed on tower A
- It is required to mvoe all disks to tower B with the assistance of C while observing
the following rules
- No disk can be on top of a smaller disk at any time
- Only one disk can be moved at a time, and it must be the top disk on the
tower
Solution to Tower of Hanoi
- Base case (n = 1)
- If n ==1 simply move the disk from A to B
- Recursion
- Split the original problem into 3 subproblems and solve them sequentially:
1. Move the first n-1 disks from A to C recursively with assistance to B
2. Move disk n from A to B
3. Move n-1 disks from C to B recursively with assistance of A
Fractals
- After you have learned about recursion in Java, can you think of recursive methods to
draw the fractals below?
- Drawing in Java is not covered in this course!

Tail-Recursive Method

Tail Recursive Methods


- A tail-recursive method is the one that has no pending operations to be performed on
return from a recursive call
- Tail recursive methods are desirable
- Compilers can optimize tail recursion to reduce stack size
- Beacuse the method ends when the last recursive call ends, there is no
need to store the intermediate calls in the stack
- A nontail-recursive method can often be converted to a tail-recursive method
- We will not discuss how to do that in this course

Recursion vs Iteration

Recursion vs Iteration
- Negative aspects of recursion:
- Recursion bears substantial overhead
- Each time the program calls a method, the system must assign space for
all of the method’s local variables and parameters
- This can consume considerable memory and may require extra time to
manage the additional space
- Positive aspects of recursion
- Using recursion may provide a clear simple solution for a inherently recursive
problem that are difficult to solve without recursion
- For example: Directory size, Tower of Hanoi, and Fractals
- When to use which
- Use whichever approach can best develop an intuitive solution that naturally
mirrors the problem
- If an iterative solution is obvious , use it. It will generally be more efficient than
the recursive option.

Recursion Efficiency

Let’s Look at Fibonacci Again


- Try the method with higher index values,, e.g fib(300)
- This will take hours to run
- Can you figure out why it takes that long?
- Is there a better way?
- In the graph below, you will notice that the Fibonacci for the same index is computed
more than once
- E.g identify how many times fib(2) is computed

- Now imagine a much larger graph – see how many redundant calculations we need to
perform?
- We don’t really need to compute fib(i) more than once! The idea is, once fib(i) is
calculated once, we need to store it somewhere then read fib(i) whenever we need it
again (instead of re-calculating it).
- Here is how we do this. Let’s say that we have a recursive function F(parameters:
1. Create a global array M (outside F) that:
a. Has the same type as the recursive-method return type
b. Of dimensionality = number of recursive-method parameters
2. Before returning a result from F, store it in M at a index that match the parameter
values of F
3. Add one more stopping condition – if M has a value at indexes = F’s parameters,
then just return that value.

One Last Thing…


- You might want to use another type for array M since the value can be much larger than
what long can hold. A good option is to use BigInteger, a class that can hold very very
large integer values

ArrayLists and Intro to Generics

Java Collections Framework

Java Collections Framework


- A data structure is a collection of data organized somehow
- It is a container object that stores other objects (data)
- A data structure is defined by a class
- Java Collections Framework includes several data structures for storing and managing
data (objects of any type)
- It supports two types of containers
- Collection: For storing a collection of elements
- E.g: Lists (ordered), Stacks (LIFO), and Queues (FIFO)
- Map: For storing key/value pairs
- E.g: HasMap
- All concrete classes in the Collection framework implements the Serializable interface

Collection Class Hierarchy

The ArrayList Class

Intro to ArrayList Class


- An ArrayList object
- Can be used to store list of objects (similar to an array)
- Its capacity can dynamically grow during the runtime
- Capacity: The maximum number of items an array list can currently hold
without being extended
- Size: The current number of items stored in the ArrayList
- Internally it is implemented using an array
- Array are fixed-size data structures

Array vs ArrayList
- Objects of the generic class ArrayList are similar to arrays, but have differences
- Similarities
- Both can store a number of references
- The data can be accessed via an index
- Differences
- ArrayList can automatically increase in capacity as necessary
- Will request more space from the Java run-time system, if
necessary
- ArrayList have methods to do many actions
- E.g can insert/remove elements anywhere in the list
- ArrayList is not ideal in its use of memory
- As soon as an ArrayList requires more space than its current
capacity, the system will allocate more memory space for it. The
extension is more than required for the items about to be added

How to Declare an ArrayList?

- list1 holds string references


- This declaration sets up an empty ArrayList. The initial capacity is set to default value

- list2 holds date references


- This declaration can be used to specify the initial capacity – in this case 100 elements
- This can be more efficient in avoiding repeated work by the system in extending
the list, if you know approximately the required initial capacity
- Since Java 7, the statement

- Can be simplified to
- Compiler is able to infer the type from the variable declaration

- If Type is omitted, the default type is Object

- list3 has references of type object, which means we can store any object types in it

Initializing an ArrayList
- Once you create an array list, you can start adding elements to it using its add method:

- You can initialize an array using another collection

- We can use Arrays.asList method to create a fixed-size list backed by specified


elements

Some Methods
Practice Questions

Practice 1

Practice 2
The Stack
- A stack represents a LIFO (Last In First Out) data structure. The elements are accessed
only from the top of the stack. That is, you can retrieve insert, or remove an element
from the top of the stack

Practice 3
Sample Applications in Gaming

Practical Example

Pseudo Code for a Space Shooter Game

Iterating Over an ArrayList

Iterating Over an ArrayList


- You can iterate over this array in different ways:
Iterators and ArrayList
- An ArrayList (and each collection) is iterable. You can obtain its Iterator to transverse all
its elements
- Two more useful methods in ArrayList:
- iterator()
- listIterator()
- iterator(): Returns an iterator object
- The iterator object implements the Iterator interface
- Methods:
- hasNext(): Returns true if this iterator has more elements to traverse
- next(): Returns the next element from this iterator
- remove(): Removes the last element obtained using the next method

- listIterator(): Returns a ListIterator object


- ListIterator is a subtype of Iterator
- Methods:
- next(), previous()
- hasNext(), hasPrevious()
- remove()
- nextIndex(), previousIndex(): return the next/previous index
- listIterator() also allows initializing the iterator with an index
- To position the iterator some place other than the beginning of the list

- ListIterator.add(): Inserts just before the iterator


- ListIterator.set(): Replaces last element returned by next or previous
Practice 4

Practice 5
Practice 6

Removing all A’s

Experiment
Random Access in ArrayList

ArrayList Supports Random Access


- ArrayLists are implemented using arrays
- Whenever the current array cannot hold new elements in the list, a larger new
array is created to replace the current array

Experiment

Useful Methods For Lists (Arrays and Collections classes)

Array<->ArrayList
- Array → ArrayList
- Creating an ArrayList from an array:
- Syntax: list = Arrays.aslist(array)

- ArrayList → Array
- Creating an array from an ArrayList:
- Syntax: list.toArray(array);

java.util.Collections Methods

Intro to Generics

Generics
- A generic class has at least one member of an unspecified type
- ArrayList is a generic class with a generic type E
- We have also seen generics before, e.g., the comparable interface

- The type(s) you provide on instantiation appear in the API as single letter in angle
brackets after the name of the class, e.g

- All collections support generic (or parameterized) types to indicate what type is stored in
the collection
- It is better to precisely specify the type of objects in a collection so that the compiler can
check for errors
- If you don’t, then the collection can store any type of object as all objects are a
subclass of object

Object Wrapper Classes


- The generic type E must be reference type
- You cannot replace a generic type with a primitive type such as int, double, or
char
- For example, the following statement is wrong:

- To create an ArrayList object for int values, you have to use:

- Java employed object wrapper, which ‘wrap around’ or encapsulate all the primitive data
types and allow them to be treated as objects

But, how am I still able to insert primitives into a list?


- If you have

- You can add a double value to a. For example,

- Java automatically wraps 5.5 into new Double(5.5). This is called auto-boxing
- Examples:
Index or Value-of-item??
- When using generics, each PARAMETER MATCHING takes precedence over
AUTO-BOXING
- Consider the code below:

- Why 9 is considered an index but not a value?


- That is because the exact parameter matching tells Java that 9 as an int
matches remove(int index) - but not remove(Object value), and therefore
it doesn’t try to ‘auto-box’ 9 in an integer class and simply assume 9 is an
index

Defining Generic Classes and Interfaces


- Define a generic class Generic Robot that has two attributes:
- One instance variable of the generic type
- One instance variable of the generic array type
- You have seen before how to create MyStack class that holds instances of the type
Object, which means any type (you can store cars, apples, and humans in the same
stack)
- To force MyStack to accept only a certain type of objects, there are two options:
1. To create individual classes for each type (e.g: HumanStack, AppleStack,
CarStack, etc) – BAD APPROACH!!!
2. To create a generic class, GnereicStack <E>, where E is replaced by the required
type when creating an instance of the class
Lists, Stacks, Queues, and Priority Queues

Collection

Collections Class Hierarchy (simplified)

The Collection Interface


- The Collection interface is the root interface for manipulating a collection of objects. It
declares the methods for:
- Adding elements: add, addAll
- Removing elements: clear, remove, removeAll, retainAll
- Checking for elements: contains, containsAll
- Checking for equality: equals
- Checking the size: size, isEmpty
- Returning as array: toArray
- Returning an iterator: iterator
Example on Collection
Lists

Collections Class Hierarchy

List Interface
- The List interface:
- Ordered (each element has an index)
- Duplicates allowed
- Methods:
- All methods from Collection
- “&” iterator methods: listIterator()
- “&” index-based methods:

- Concrete classes:
- ArrayList, LinkedList

ArrayList<E>
- Methods:
- All methods from List
- “&” extra method
- trimToSize()
- Trims the capacity of this ArrayList instance to be the list’s current
size
- Constructors
- ArrayList()
- Creates an empty list with default capacity
- ArrayList(int initialCapacity)
- ArrayList(c: Collection <? extends E>)
- Create ArrayList from an existing collection of any subtype of E

LinkedList<E>
- Methods:
- All methods from List
- “&” extra methods specifically for first and last elements

- Constructors
- LinkedList()
- Creates a default empty list
- LinkedList(c: Collection <? extends E>)

Initializing a List as We Create it


- In Java 9, all of the following is valid:
a. To create and initialize a specific type of a list (e.g ArrayList):

b. To create a general list (i.e can only use methods from List)

- Aside: difference between Arrays.asList and List.of:


- Note: List.of() is a static (implemented) method in List interface

Example

Practice

When to use Which?


- In order to answer this question, we need to learn a few things first about ArrayLists vs
LinkedLists, namely:
1. The internal structure of the two types of lists (ArrayList vs LinkedList) and they
are stored in the memory
2. How their methods are implements
a. E.g while add(E data) is supported by the two lists, this method is
implemented differently in each list
And the time complexity of the different methods
b. In terms of the Big-Oh notation

Remember: Structure of an ArrayList

Basic Structure of a LinkedList, cont.


- Each data element is contained in an object, called the node
- When a new element is added to the list, a node is created to contain it
- Each node is linked to its next neighbour
- A variable head refers to the first node, and a variable tail to the last node
- If the list is empty, both head and tail are null
How LinkedLists are Stored in Memory?

Other Types of LinkedLists


When to use Which (again)?
- In general, use either one if you need access through an index. However, note the
following (based on the table on the next slide):
- ArrayList:
- Efficient for getting/setting elements by an index
- Good for inserting/removing elements near end (but not beginning)
- Access elements using “Random Access” (good)
- LinkedList:
- Efficient for inserting/removing elements near beginning and end
- Efficient when using iterators
- Access elements using “Sequential Access) (bad)
- Hence it is not good for getting/setting elements away from
beginning/end
- When should I use standard arrays?
- A list can grow or shrink dynamically. An array is fixed once it is created. IF your
application doesn’t require insertion or deletion of elements, the most efficient
data structure is the array
get(i) in ArrayList vs LinkedList

Big-Oh Notation
- Big-Oh notation is a mechanism for quickly communicating the efficiency of an algorithm
- Big-Oh notation indicates the growth rate of a function (efficiency) when the size
of data (n) changes
- The letter O is used because the growth rate of a function is also referred
to as the “Order of a function”
- In big-oh notation:
- The performance is specified as a function of n which is the size of the problem
- E.g n may be the size of an array, or the number of values to compute
- Only the most significant expression of n is chosen:
- E.g if the method performs n^3 + n^2 + n steps, it is O(n^3)
-
- Constants are ignored for big-Oh:
- E.g If the method performs 5n^3 + 4n^2 steps, it is O(n^3)
- This is because we measure the growth rate, not the number of
executions, and hence both n and 10n will grow at the same rate

Common Big-Oh Notation Values


- There are certain classes of functions with common names:
- Constant = O(1)
- Logarithmic = O(log n)
- Linear = O(n)
- Quadratic = O(n^2)
- Exponential = O(2^n)
- These functions are listed in order of fastest to slowest
- For example, for large values of n, an algorithm that is considered O(n) is much
faster than an algorithm that is O(2^n)
- Big-Oh notation is useful for specifying the growth rate of the algorithm execution
time
- How much longer does it take the algorithm to run if the input size has
doubled?
Practice: Big-O Notation

Demo: ArrayList and LinkedList


Experiment: Printing All Elements in Reverse Order

Practice: ArrayList and LinkedList


The Vector and Stack Classes

Collections Class Hierarchy (simplified)

Aside: The Vector Class


- Since Java 2, Vector has become the same as ArrayList, excepts that Vector contains
synchronized methods for accessing and modifying the vector
- Synchronized methods are useful to control concurrent access of data
- Concurrency is outside the scope of this course
- You can also create other synchronized collections (e.g array lists), but this is
also outside the scope of this course
The Stack
- A stack represents a LIFO (last-in-first-out) data structure. The elements are accessed
only from the top of the stack. That is, you can only retrieve, insert, or remove an
element from the top of the stack

Some Stack Applications


- Programming languages and compilers
- Method activation frames are placed onto a stack
- call=push, return=pop
- Compilers use stacks to evaluate expressions
- See case study in textbook
- Matching up relating pairs of things:
- Getting the reverse of an array or a string
- Checking if string is a palindrome
- Checking if braces { } match
- Sophisticated algorithms:
- Searching through a maze with “backstring”
- “Undo stack” of previous operations in a program

The Stack Class


Practice

Printing the Stack Elements


- Let’s say you want to pop and print all elements of a stack. What is wrong with the
highlighted loop? How can you fix this?
- Answer: The size in the above loop will change in every iteration. This will result in
printing e d c (but not a and b). Instead, you should do the following:

- Lets say you want to ONLY print elements of a stack. What is wrong with the highlighted
loop? How can you fix this?

- Answer: This code also destroys the stack. If you want to only print all elements you
need another technique that keeps the original stack intact and yet print the elements
- One way is to have a get(i) method – remember that a stack implements List
interface, so it has index-based operations
- The other way is to clone the stack first, then print th cloned stack using the code
above

Practice
Queues and Priority Queues

Collections Class Hierarchy (not accurate)

Queues
- A queue is a FIFO (first-in-last-out) data structure. Elements are appended to the end of
the queue and are removed from the beginning of the queue

Some Queue Applications


- Operating systems (assuming FIFO prioritization):
- Queue of processes to be run
- Queue of print jobs to send to the printer
- Programming:
- Storing a queue of statements to be evaluated in order
- Modeling a line of customer objects to be served in order
- Real world examples:
- People waiting in a line
- Cars at a gas station
- Etc.
The Queue Interface

Implementation of Queue
- LinkedList and PriorityQueue are two concrete implementation of Queue
- Example on using a LinkedList to implement to queue:
Practice

The PriorityQueue Class


- In priority queue, elements are ordered based on some criteria
- In Java, elements are ordered based on *:
- Their natural ordering (imposed by Comparable), or
- A Comparator provide at queue construction time
- When accessing elements, elements with the higher priority are removed first
- Constructors:
Example: PriorityQueue with Comparable

Example: PriorityQueue with Comparator


- What if we want to add another criteria for comparing the Robots, e.g based on year, and
use it for the priority queue?
Practice

Caution!
- In priority queues, an iterator or (a for-each loop) is NOT guaranteed to transverse the
elements in any particular order. This is because it’s implemented as a priority heap
rather than sorted list
- The only guarantee provided by PriorityQueue is that poll(), peek() return the least
element
- How to show all elements according to their priority without actually remove them from
the priority queue?
- Solutions include
- Create a temporary priority queue and copy all elements from the original one to
the temp one, then use poll() in a loop
- Convert the priority queue to an array and use Arrays.sort

Comparable and Comaprator Interfaces

The Comparable Interface


- Any class that implements the Comparable interface must have a method compareTo

The Comparator Interface


- The Comparator interface can be used to compare objects of a class, similar to the
Comparable interface
- How to use?
- To use Comparator with a class of the type E, you must create ANOTHER class
that implements Comparator and has a method int compare(E e1, E e2)
- This method returns -1 if e1 is less than e2, +1 if e1 is larger than e2, and 0 if e1
is equal to e2
- When/why to use Comparator?
- If your class doesn’t implement Comparable
- E.g you don’t want to write a compareTo method in your class
- Note that Comparable provides what is known as “natural ordering”
- If you want to have more than one way of comparing the objects of a class type
(in addition to the natural order)
- E.g based on weight only, height only, age and height only, etc.
Practice

Implementing Lists, Stacks, and Queues


Two Ways to Implement Lists
1. Using linked lists (we will create MyLinkedList class)
a. A linked structure consists of nodes. Each node is dynamical created to hold an
element. All the nodes are linked together to form a list
2. Using arrays (we will create MyArrayList class)
a. If the capacity of the array is exceeded, create a new larger array and copy all
the elements from the current array to the new array.
- We shall use an abstract class and interface to define the commonalities between
MyArrayList and MyLinkedList

Why Learn Implementing Lists?


- Knowing what’s happening “under the hood” allows you to use built-in structures more
effectively
- Helps you implement custom data structures that is similar but not identical to built-in
classes (which is very likely)
- Do more than what Java built-in classes do
- Custom design (e.g constructor to read elements from a text file)
- Custom methods (e.g insertAfter, modifyAll, deep clone, invertList,
searchSequence)
- ArrayLists and LinkedLists are relatively easy data structures to build
- Needed to understand the more complex structures
- Useful when using another OO language that doesn’t provide built-in collection classes
- Allows you to research ways to improve current collections
- E.g head + tail + other pointers (e.g if you want to insert near middle)
- Helps you practice your Java skills

Today’s Challenge…
- Given:
- The code for MyList and MyAbstractList

- Required:
- Implement MyArrayList
- Implement MyLinkedList
MyList Interface

MyAbstractList Class
Implementing Linked Lists

Several Types of Linked Lists

Before We Implement MyLinkedList


- What is a node?
- It is an instance of a class that has exactly two attributes
- element
- Holds data at this node
- Of a generic type E
- next
- Refers to the next node in the linked structure
- Of the type Node<E>

- What is a Linked List?


- It is an instance of a class that has three attributes
- size
- An integer that keeps track of the number of nodes
- head
- Points to the first element
- Of the type Node<E>
- tail
- Points to the last element
- Of the type Node<E>
- When we implement linked lists today, we will need to consider three cases

Practice: What is the Value of?


- What is the value of head.element?

- What is the value of head.next.element?

- What is the value of head.next.next.next.element?

Useful Illustrations
- Assume x and y are references to nodes in a linked list:
Practice: Add an Element to the End

Let’s Try to Change the Order


Practice: Insert an Element

Let’s Try to Change the Order


Practice: Remove an Element

Now…Let’s Write Some Simple Java Code

Simple Practice 1: Add 3 nodes to a list


- Write a Java code to manually add 3 nodes to an empty linked list and assume you have
access to both head and tail
- Initially, head = tail = null

- Solution:
1. Create a node and insert it to the list:
2. Create the second node and add it to the list

3. Create the third node and add it to the list:

Simple Practice 2: Traversing All Elements in a List


- Write Java code to visit all elements in a LinkedList (and do something. E.g print the
node’s element)
- Solution:
- Start from the beginning (head)
- Run a loop to visit each element and then progress to the next element
- When to stop the loop?
- If the node is the last in the list, next will be null. You can use this
property to detect the last node
Now…Let’s Build a LinkedList!

Implementing MyLinkedList
- In this part, we will implement the linked list shown below

ADDING
REMOVING
GETTING
The ITERATOR
- Here we need to implement the iterator of our MyLinkedList
1) Declare a method iterator in MyLinkedList

2) Create a class MyListIterator the has the required methods


MyLinkedList

Testing MyLinkedList
Practice
- Given the linked list, RobotsList, which maintains information about the position of a
group of robots

Practice
Implementing ArrayLists

ArrayLists
- Array lists use fixed-size arrays
- Whenever you need to expand the capacity, create a new larger array to replace
the current array
- When inserting a new element into the array
1. First, ensure is enough room in the array
2. If not, expand:
a. Create a new array with the size twice as the current one +1
b. Copy the elements from the current array to the new array
c. The new array now becomes the current array

Insertion
- To insert a new element at a specified index
1) Ensure there is enough room for new element (if not, expand)
2) Shift all elements after the index to the right one
a) Obviously, if you are inserting at the end, there will be no shifting
3) Insert the element
4) Increase size by 1

Deletion
- To remove an element at a specified index
1) Shift all elements after the index to the left by one
2) Decrease size by 1

Implementing MyArrayList
- An arraylist uses an array of a generic type E, and has methods that allow inserting,
deleting, etc. below shows some of these methods
Ensuring There is Enough Room When Inserting
- Lets say that our array list has a private variable data

- Whenever you add one element, make sure there is enough space first, and expand as
needed:

Checking Validity of a Given Index


- If you want to check the validity of a given index:
- For all methods except adding method:
- Index must be from 0 to size-1 inclusive

- For add methods


- Index must be from 0 to size inclusive
- Note that index could be equal to size which means add after last
element
ADDING

REMOVING
SETTING, GETTING

contains, indexOf, and lastIndexOf

Clear and trimToSize


The ITERATOR

MyArrayList
Testing MyArrayList

Practice

Stacks & Queues

Design of the Stack and Queue Classes


- One way to implement them is as follows:
- Stack → use an arraylist

- Queue → use a linkedlist


- There are two ways to design to tack and queue classes:
- Using inheritance: You can define the stack class by extending the array list
class, and the queue class by extending the linked list class

- Using composition: You can define an array list as a data field in the stack class,
and a linked list as a data field in the queue class

- Composition is better!
- Because it enables you to define a complete new stack or queue class without
inheriting unnecessary and inappropriate methods from the array/linked list

Remember: Stacks
- You have seen before how to implement a stack using array lists

Queues
- Similarly, you can implement a queue using a linked list
Sorting

Algorithms Efficiency

Why Are Some Programs Faster than Others?


- Recall that an algorithm is a sequence of steps to solve a problem

- More than one algorithm?


- More than one route using more than one means
- Several routes can be used. Some take longer than others
- Several algorithms can be used for the same problem
- But some are more efficient than others
- For 100,000 elements, the reported time
- Algorithm 1: 4.212 sec
- Algorithm 2: 0.016 sec

- In general, the performance of an algorithm when implemented on a computer depends


on the approach used to solve the problem and the actual steps taken
- Although faster hardware makes all algorithms faster, algorithms that solve the same
problem can be compared in a hardware-independent way using big-Oh notation

Best, Average, and Worst Cases


- Very few algorithms have the exact same performance every time because the
performance of an algorithm typically depends on the size of the inputs it processes
- The best case performance of the algorithm is the most efficient execution of the
algorithm on the “best” data inputs
- The worst case performance of the algorithm is the least efficient execution of the
algorithm on the “worst” data inputs
- The average case performance of the algorithm is the average efficient execution of the
algorithm on the set of all data inputs
- Best, worst, and average-case analysis typically express efficiency in terms of the input
size of the data
- The input size is often a function of n

Why Study Sorting?


- Sorting is a classic subject in computer science
- Why you study them?
1. Excellent examples to demonstrate algorithm performance
2. Many creative approaches to problem solving
a. These approaches can be applied to solve other problems
3. Good for practicing fundamental programming techniques
a. Using selection, loops, methods, and arrays
- Note: Java API has several sort methods in these classes:
- java.util.Arrays
- java.util.Collections

Today’s Assumptions: What Data to Sort?


- The data to be sorted might be of almost any type: integers, doubles, characters, or
objects
- For simplicity, this section assumes:
- Data to be sorted are integers
- Data are sorted in ascending order, and
- Data are stored in an array
- The programs can be easily modified to sort other types of data, to sort in descending
order, or to sort data in an ArrayList or a LinkedList
Code Used for Testing!

Demonstration
- Here are a few websites that can be used to demonstrate the different sorting algorithms
in this unit

Simple Sort Techniques

Selection Sort
- Algorithm:
- For each element in the list:
- Find the smallest element
- Swap it with the element
- Repeat with the unsorted part
- Ignore the first element and apply the same algorithm on the
remaining smaller list
- Runtime efficiency
- Worst/Average/Best case: O(n^2). 2 nested loops

Selection Sort: Implementation


- Using two nested loops

- Recursive Selection Sort


Bubble Sort
- IDEA: Starting from the first item, compare the adjacent items and keep “bubbling” the
larger one to the right. Repeat for remaining sublist
- Algorithm:
- Starting on the left: For each pair of elements in the list, swap them if they aren’t
in order. The largest element is bubbled to position N
- Start on the left again, and bubble the second largest element to position N-1
- And so on.
- Runtime efficiency
- Worst/Average case: O(n^2). 2 nested loops
- Best Case: O(n). For almost sorted list

Bubble Sort Code


Improved Bubble Sort Code

Insertion Sort
- IDEA: Start with a sorted list of 1 element. Repeatedly insert an unsorted element into a
sorted sublist until the whole list is sorted
- Algorithm:
- For each element e in the unsorted part
- Keep a copy of e
- Insert e it into the sorted list such that this list remains sorted
- By moving all elements larger than e forward by one step
- Runtime efficiency
- Worst/Average case: O(n^2). 2 nested loops
- Best case: O(n) for nearly sorted list
Insertion Sort, Another Solution

Merge Sort
- IDEA: Divide the array into two halves and apply a merge sort on each half recursively.
After the two halves are sorted, merge them
- Algorithm:

- Runtime efficiency
- Average/worst case: O(n log n)
Merge Two Sorted Lists

Merge Sort Code


Quick Sort
- IDEA: Select a pivot that divides the array into:
- part1 (elements <= pivot) & part2 (elements > pivot)
- Then recursively repeat for each part
- Algorithm:

- Runtime efficiency
- Best/Average case: O(n log n)
- Worst case O(n^2) in rare situations
- When?
- In the best case the pivot divides the array each time into two parts of
about the same size
- In the worst case
- Pivot divides the array each time into one big subarray with the
other array empty. So the algorithm requires (n-1) + (n-2) + … + 2
+ 1 times = O(n^2)
- This happens when the smallest or largest element is always
chosen as the pivot

Quick Sort: Basic Idea


Basic Partitioning

- Partitioning Algorithm:
1. Initialize pivot, low, high
2. while(low < high)
a. Search from both sides:
i. From left (low) for the first element > pivot
ii. From right (high) for first element <= pivot
b. Swap the elements at low <> high
3. Move pivot element at the correct location and return its index

How to Partition?

Practice Question
Improving a Quick Sort
- Better choice of the pivot
- Always choosing the smallest or largest element as the pivot leads to the worst
case scenario O(n^2)
- Can you think of a case which this could happen?
- Early versions of quicksort suggested choosing the leftmost
element as the pivot. What happens if the array is already sorted
(or reverse-sorted)?
- How to avoid the worst case scenario
- Choose a random index for the pivot
- Choose the middle element as the pivot
- Choose the median between the first, middle, and last elements
- Aim: to find a pivot that divides the array into 2 parts ot almost the
same size
- Called “median of three” partitioning → good estimate of the
optimal pivot
- Median-of-three works even with sorted or reverse-sorted array
- Other improvements were suggested
- Outside the scope of this course
- Java uses Dual-Pivot Quicksort technique

Positive Integers Sorting

Bucket Sort and Radix Sort


- Previous sort algorithms are general sorting algorithms
- Work for any types of keys (e.g, integers, strings, and any comparable objects)
- The best average-case performance is O(n log n)
- If the keys are small integers, you can use Bucket or Radix sort without having to
compare the keys

Radix Sort
- If K is too large, using the bucket sort is not desirable. Radix sort is based on bucket
sort, but uses only ten buckets
- IDEA:
- Divide the keys into subgroups based on their radix (base) positions
- Apply a bucket sort repeatedly for the key values on radix positions
- Apply a bucket sort repeatedly for the key values on radix positions, starting from
least-significant position
- Performance: O(dn)
- Where d is the max number of radix position
Practice Questions

Solution for Radix Sort


Next…

You might also like