Week 7
Week 7
Week 7
https://drive.google.com/drive/folders/1yo4pvaTngQ1L2TkHuWPCzbfapLDyvwUt?usp=sharing
You can check your lab attendance and quiz scores from the following link:
https://docs.google.com/spreadsheets/d/1xKQEqMSGlj3RBo9kLh97UzbTO1fyCvaT/edit?usp=s
haring&ouid=104851561367296854968&rtpof=true&sd=true
1
1) class (static) fields: the variable is stored in the class (one copy that all the objects share).
Class fields exist for the duration of the program. They are given default initial values of 0, 0.0,
false, or null (not a valid address), depending on their type.
2) instance (non-static) fields: a separate variable stored in each instance of the class. Every
instance has its own version. An instance field exists as long as the containing instance exists.
They are given default initial values of 0, 0.0, false, or null
3) method parameter: the variable(s) that store an input to the method. It exists only in the
body of the method. The initial value is set by the method call input
4) local variables: a variable declared inside a method. It exists from the moment created until
the end of the compound statement it is declared it. Tthey are not given an initial value, and they
must be assigned a value as their first use
Introduction to Loops
There are 4 basic loop structures in Java: while loop, for loop, do-while loop, foreach loop. The
first three are general purpose loops. The fourth is a special loop form that can only be used with
arrays and Iterable types. We will cover foreach loops later in the term.
1. The while loop:
while (condition)
loop-body-statement
loop-body-statement is a single statement, simple or compound, condition is any boolean
expression
While loop behavior:
1) the condition is evaluated
2) if the condition is true:
2a) the loop body statement is executed
2b) repeat step 1
if the condition is false, go to the next statement of the program
2
3. The do-while loop
do {
loop-body-statement(s)
} while (condition);
do-while-loop behavior:
1) Execute the loop-body-statement(s)
2) Evaluate the condition
If the condition is true, repeat step 1
If the condition is false, go to the next statement of the program
The do-while loop should be avoided except in cases where you really need the body of the loop
to execute before testing the condition.
The problem with the do-while loop is that, if you accidentally drop the "do", the rest of the code
is still valid Java, but it does something entirely different! It becomes a compound statement
followed by a while loop!
Some guidelines:
• for loop advantage is that the code that describes the loop is all in the header.
• for loops are good when the loop is controlled by variable(s) and there is a simple increment.
• while loop advantage is that the syntax is simpler.
• while loops are good when the increment is complicated
Remember Strings:
Recall that String is a class that represents text. A String object can not be changed once created.
Some useful methods and operators:
+: creates a new String that is the result of concatentating two Strings
length(): returns the number of characters in the String
charAt(5): returns the 6th character of the String. (In Java, the first character is at index 0.)
However, this is not a good solution. The + operator creates a new String each time. If we
are dealing with a very long String, such as if we want to capitalize a DNA code of millions of
characters, we will be creating a LOT of unnecessary Strings and using up our memory.
Java provides a StringBuilder class to create Strings. StringBuilder has all the same methods as
String (charAt, length, etc) plus several others. Once useful one is append that adds new
characters (or other values) to the end of the string being created.
3
How to create an empty StringBuilder? Just like you create any other initial instance:
StringBuilder result = new StringBuilder();
How to add an element to the StringBuilder object? We can use append method like:
result.append(what we want to append)
What do we return at the end? We can't return result because it is not a String. But Object has
a method that returns a String representation, and every class inherits it from Object.
result.toString();
Week #7 Lecture:
SESSION-I
The Java API (Application Program Interface): https://docs.oracle.com/javase/8/docs/api/
API is typically part of any pre-defined software package that you can use in your programs.
It lists how you are to use the predefined programs. The Java API lists all the pre-defined classes
(and other types) in Java.
In the API you can find:
- the package the class is in (what you need to import)
- the header for the class
- a list of all non-private inner classes, constructors, fields, and methods
Suggestion: Keep the API bookmarked because we will be using it a lot in the course.
Example: Let's create a method in Employee that compares employee's by how much money
they make. We created the method earnsMoreThan that compares the salaries of this employee
to another employee.o Suppose we have this:
public boolean earnsMoreThan(Employee e) {
return this.getSalary() > e.getSalary();
}
There is a problem: whoever created Employee assumed that all employee's have salaries.
This is not the case, but we often have situations in Java where the person creating a type does
not think of every situation and makes incorrect assumptions on how it is used. Because the
4
earnsMoreThan method is following proper O-O coding (using the getter methods instead of
the fields), it is easy for other classes to adjust so that their classes properly work.
We will have every employee type define for itself what "salary" means.
For example, an hourly employee can decide that a salary is hours worked * rate per hour.
A sales employee can decide salary is number of sales * commission, and so forth.
HourlyEmployee will "define" how salary works by overriding the salary method:
public double getSalary() {
return this.getHoursWorked() * this.getHourlyRate();
}
Because the true type version of an instance method is always used, the hourly employee will
always report its salary as the product of its hourly rate and hours worked.
- it does not matter if we typecast the hourly employee
- it does not matter if we try to set the salary of the hourly employee It just works!
The moral: everytime you choose to not follow the O-O guidelines, you make it more
challenging for other coders to extend and use your class. So, if you want to violate the O-O
guidelines, you should think carefully to make sure you have a good reason to do so.
Arrays:
An array is a collection of variables of the same type, stored in a contiguous chunk of memory.
We can create an array to hold variables of any type:
int[] array1 array1 is an array that will store ints
JFrame[] array2 array2 is an array that will store JFrames
double[] array3 array3 is an array that will store doubles
double[][] array4 array4 is an array that will store double[]. That is, it is an array that stores
arrays that store doubles.
We can initialize the array using the new operator and stating how many variables (elements)
will be in the array:
array1 = new int[100];
array1 now stores the address for an array that contains 100 int variables.
array2 = new JFrame[30];
array2 now stores the address for an array that contains 30 JFrame variables.
5
Now, to access each element, we again use the square brackets. To store a value in the first
element (variable) of the array:
array1[0] = 12;
Each element of an array is a variable of the given type. So, all the rules of variables apply:
array1[2] = 3.15 Illegal! We are trying to store a double in a variable of type int
array1[2] = 7 Legal!
array1[2] = 'c' Legal! char is narrower than int so Java will automatically widen the 'c' to int
array1[2] = array3[2] Illegal! We are trying to store the value from a double variable into
an int variable
array1[2] = (int)array3[2] Now it is legal because of the typecast.
Array disadvantage: Because the array is stored in contiguous memory, we cannot change the
length (number of variables) of the array once it is created.
Array advantage: Because the array is stored in contiguous memory, we can access each
element in a single step, no matter how large:
If you have a small array, then you don't need a loop. You can enter the elements on at a time, or
you can use an "array constructor" shortcut.
Our first method with arrays: Create a method that reverses the contents of an array. Each slot
in the array is just a variable and so we will use variable assignment. We will need one extra
variable to store values so we do not lose any values.
1. save the value in the back of the array, array[array.length - 1 - index]
6
2. store the value at the front of the array array[index] into the back of the array
array[array.length - 1 - index]
- this overwrites the value in array[array.length - 1 - index]. Good thing we saved it!
3. store the saved value into array[index]
When do we stop the loop? You might be tempted to say when index reaches the end
(array.length), but that is not correct. Can you see why?
Another incorrect assumption is to have an assignment statement: array = newarray. This would
copy the address of newarray to array, but it has the same problem because array is a method
parameter. Thus, array will be lost once the method ends.
Example:
myArray = append(myArray, 11); will create a new array one larger than myArray, copy the
data over, add 11 to the end, and then return the address of the new array, and that address is
stored in myArray. myArray now points to a new array one larger than the array it originally
7
pointed to. The old array is still in memory. Java will eventually de-allocate it if there are no
other variables storing its address.
SESSION-II
Arrays and Typecasting: We can always typecast an array to Object because everything that is
not a primitive value in Java is an object.
int[] array1 array1 will store the location of the array in memory
new int[100] the new operator will return the address of the array
(Object)array1 the typecast is legal because Object is above everything in the class hierarchy.
Typecasting the individual variables in the array: Because a typecast on a non-primitive type
does not change the true type of the object, just its current type, Java allows us to typecast the
types of the variables for arrays of non-primitive types:
JFrame[] frameArray = new JFrame[100]
1- Object[] oArray = (Object[])frameArray; Legal. Object is wider than JFrame. An explicit
typecast is not needed.
2- frameArray = (JFrame[])oArray; Legal as long as the true type of oArray is an array
storing JFrames or something below JFrame in the hierarchy
oArray[0] = new JFrame();
3- oArray[1] = "Hello" Illegal. While Object o = "Hello" is legal, here oArray[1] is referring
to an element of frameArray. frameArray knows that it is only to store JFrames.
What is the subgoal of the loop: It is what must be true after each iteration for the loop to
continue: "x is not in the first i elements of array"
8
Is this the best search method we can write?
In the worst case, how many array locations does it inspect? All of them. So, in the worst
case, the time the method takes will be proportional to the length of the array.
On average, it will need to check half the elements in the array. So, in the average case the time
the method takes is also proportional to the length of the array.
Can we write a faster loop? No! What is the proof? No matter how we decide to run through
the array, until we look at every location, we can not be sure if x is not in the array. So, unless we
know more information about the array, our linearSearch method is optimal.
Searching in a sorted array: Suppse the array is sorted in non-decreasing order. Now can we
write a faster method? Proposed Algorithm (Binary Search)
1. Examine the middle element.
2. If the middle element is smaller than x, repeat on the upper half of the array.
3. If the middle element is larger than x, repeat on the bottom half of the array.
This algorithm looks more complicated to code, but is it fundamentally faster? We start with a
list of n elements, and each time, we cut the number of elements we are considering in half.
n n/2 n/4 n/8 ... 1
So, the number of iterations (and the number of elements of the array) is k where n / (2^k)) = 1.
k = log(base 2) n
So, it is very worth it to write this method. Here is the first attempt we made in class. We are
using a while loop because we do not know, at first, how many times we will need to iterate.
9
int back = array.length - 1; // the largest index of array in the region still being considered
while (front != back) {
int mid = (front + back) / 2;
if (x < array[mid])
back = mid;
else if (x > array[mid])
front = mid;
else // only other case is x == array[mid]
return mid;
}
return -1;
}
Loops are tricky to get completely correct so it is not surprising that our first attempt is not quite
correct. Because of that, computer scientists have developed mathematical logic to reason about
loops.
First, we have to decide what the goal of our loop is. That is something we -should- be able to
do since we are writing the code. (Note, this is another reason why we want our loops to do just
one task. It makes describing the goal easier.)
Given the goal for the loop, we must next determine what the -subgoal- for each loop iteration is.
The subgoal (in formal math terms the subgoal is called the "loop invariant") is what we need to
accomplish with each iteration of the loop in order to achieve the goal. Verifying that a loop is
correct means that we verify that we are able to achieve the subgoal after each iteration, and we
verify that the loop will eventually stop.
Now let us verify the subgoal. Recall the three properties we need:
a) The subgoal is true at the end of each loop iteration.
b) The subgoal is true immediately before the first iteration of the loop starts.
c) When the loop condition becomes false, the subgoal and the condition being false logically
implies the goal.
Property b is easy to verify. Before we start the loop, front = 0 and back = array.length - 1. Of
course, if x is in array it will be in array[0..length-1].
10
Property c is does not hold in our loop: When the loop condition becomes false, the subgoal and
the condition being false implies the goal. The loop condition becomes false when front == back.
Using the logic reasoning, we see that our loop is incorrect. Maybe thorough testing would have
found this problem, but the error will only occur in specific situations (do you see why?). Let us
fix the method by changing the loop condition so that when it is false we do get the loop goal:
Next, we will check point (b): the subgoal must be true after each iteration. The way you verify
this is check that if the loop subgoal is true at the start of the iteration, then it is still true at the
end of the iteration. See if you can reason through the code and verify that we always achieve the
loop subgoal.
Are we done? We verified the loop subgoal so the logic of our loop is correct, but we still must
verify that the loop terminates. Having a logically correct loop does no good if the loop runs
forever.
Are we guaranteed that the loop will terminate, or could it run forever? To guarantee that it
does not run forever, we have to show that the gap between front and back decreases with each
iteration. Thus, either front increases or back decreases. Is it possible to have a situation where
front and back to do not change? YES!! Consider front = 5 and back = 6 and the element we are
looking for is at index 6. Note that in this situation, mid = 5, array[mid] is smaller than the
element, and so front = mid = 5. front never changed! What is our fix? To note that we know
that the element is not at the array[mid] and so we will not include it. Since we know that front
<= mid <= back, setting front = mid + 1 and back = mid - 1 guarantees that either front is
increased or back is decreased on each iteration.
11
return mid;
}
return -1;
}
The moral: using the formal logic reasoning helps us design correct loops before we code them
into the program.
Loop Testing
How to test loops?
Here is a good "rule of thumb" to follow to help you catch most of the usually bugs that can
show up in loops:
1) Test 0, test 1, test many
2) Test first, test middle, test last
Another interpretation is to test where we find the element quickly (the first iteration of the
loop), and one where the loop runs to the end (the element is not in the array). So, this guide
suggests that we have to test the following array lengths: 0, 1, and large, and we have to search
for the middle element, elements not the middle, the element at the front, the element at the end,
and elements that are not in the array: one smaller than all elements in the array, one larger than
all elements in the array, and one that falls between the largest and smallest.
This problem is a little harder than the ones done previously. We will first look at some possible
algorithms, but it turns out that the best, and simplest, algorithm is not going to be the first ones
considered. Often, it is a good idea to think through the benefits and disadvantages of different
possible algorithm and to try to improve the algorithm before writing code.
Here are three algorithms proposed (or slight variations of the ones that were proposed) by
students from the previous years.
12
Proposed algorithm 1:
1) Create a new String (or StringBuilder) that removes all punctuation
2) Create a new String (or StringBuilder) that removes all spaces
3) Make every letter upper case
4) Do the isPalindrome logic or method from last class
Proposed algorithm 2:
1) Create a new String (or StringBuilder) that contains only the letters from the input string
2) Do the isPalindrome logic or method from last class
Proposed algorithm 3:
1) have two indeces, one at the front and one at the back
2) Repeat:
2a) Repeat: until the first index points at a letter, increment it
2b) Repeat: until the second index points at a letter, decrement it
2c) Compare the letters at the two indeces, and return false if they don't match and
increment/decrement the indeces otherwise
How much extra memory did it use? It created one new StringBuilder plus one new String
(the toString method). So, it needed more memory equal to twice the length of the string. (Note
that if we used the + operator instead of StringBuilder, we would need more memory that is
equal to the square of the length of the string!)
13
1- Think about how you would solve the problem.
2- We usually hate doing unnecessary busy work and so our natural solutions avoid a lot of the
"extra memory" isses or "extra traversal" issues of the previous solutions.
3- If we had to solve this problem using pencil and paper, we would never waste time first
copying down the string again without the punctuation, and then copying another time changing
the letters to lower case.
One important skill is to learn how to take what we naturally do in problem solving and then
breaking that down into simple steps that you can write into a program.
for (int front = 0, back = s.length() - 1; front < back; front = front + 1, back = back - 1) {
while (!Character.isLetter(s.charAt(front)) // repeat until front hits a letter
front = front + 1;
while (!Character.isLetter(s.charAt(back)) // repeat until back hits a letter
back = back - 1;
// now test the two characters
if (Character.toUpperCase(s.charAt(front)) != Character.toUpperCase(s.charAt(back))
return false;
}
}
BUT THIS CODE IS NOT RIGHT!!! IT WILL CRASH
We need to add extra conditions in the each while and in the test!
We have to make sure each while does not move the index outside the string or s.charAt(..) will
return an error. We need to make sure, when we test the two characters after the while loop, that
we are testing two letters. Maybe either while loop halted because we reached the end of the
string.
The point is: because we do not have all the loop increment information inside the for-loop
header, the writing the algorithm has become much more complicated. Recall, though, that this
code did have the advantage of a single traversal and no extra memory created. So, a good
algorithm will keep those features but do only a simple increment at each step
Here is a better algorithm that keeps the same general logic as algorithm 3 but uses simple
increments:
1) Have two indeces, one at the front and one at the back.
2) Repeat until done:
2a) If the first is not a letter, move the index up one and repeat 2
2b) Otherwise if the second is not a letter, move the index down one and repeat 2
2c) Otherwise if both matches, move both indeces one character
2d) Otherwise we have letters that do not match and we can return false.
- This algorithm uses no extra space and does a single traversal of the String.
14
- The algorithm is logically simple because we are still doing one task: comparing each letter
at the front to the corresponding letter at the rear.
- The loop has a simple organization because each index will increment or decrement AT
MOST once each iteration, and at least one will change.
- Since either the front or end index changes, it is easy to logically reason that our loop will
eventually terminate.
15