Testing
Thinking Process
Often, development is an incremental process
that involves lots of tasks switching and on the fly design modification.
Tests provide stability
and scaffolding
:
- Provide
confidence in basic units
and mitigate possibility of breaking them. - Help you focus on
one task
at a time.
In large projects, test also allow you to safely refactor
(still have the same function)!
Four Philosophies
Here we have four testing philosophies:
- Autograder Driven Development (ADD)
- Unit Tests
- Test-Driven Development (TDD)
- Integration Testing
Autograder Driven Development (ADD)
The worst way to approach programming. This workflow is slow and unsafe!
Wait for the autograder to run, and test everything at the end of development.
Unit Tests
But, how do you test units that rely on others. e.g. how do you test addFirst
in Project 1?
Test-Driven Development (TDD)
It is a repeat process
consisting of three parts:
Integration Testing
Idea: Tests cover many units at once
.
Not JUnit’s focus, but JUnit can do this.
But it tests at highest level of abstraction what may miss subtle or rare errors
.
Exercise in Study Guide
1 |
|
Inheritance & Implements
Interface & Implementation
Interface Inheritance (what
):
- Subclass inherits
signatures
, but NOT implementation - So we can also say it is
Signature Inheritance
.
Implementation Inheritance (how
):
- Subclasses can inherit
signatures AND implementation
.
Static vs. Dynamic Type
Every variable in Java has a compile-time type
, a.k.a. static type
.
- This is the type specified at
declaration
. Never changes!
Variables also have a run-time type
, a.k.a. dynamic type
.
- This is the type specified at
instantiation
(e.g. when usingnew
). - Equal to the type of the object really being pointing at.
1 | Animal a = new Animal("Pluto", 10); |
If Y overrides
the method, Y’s method is used instead. This is known as dynamic method selection
.
Implementation inheritance may sound nice, but there are some drawbacks
:
- We are fallible humans, and we can’t keep track of everything, so it’s possible that you overrode a method but forgot you did.
- It may be hard to resolve conflicts in case two interfaces give
conflicting default methods
. - It encourages overly
complex code
.
Encapsulation
When building large programs, our enemy is complexity
.
Ways of managing complexity (ultimately determine whether a programmer is successful), as shown in Software Engineering lectures:
- Hierarchical
abstraction
(List, LinkedList, ArrayList) Design for (future) change
(D. Parnas)- Organize program around
objects
(modules) Let objects decide
how things are done. Outsider needs not to know.Hide information
others don’t need (helper functions)
- Organize program around
Even when writing tests, you don’t (usually) want to peek inside.
Ideally, a user should not be able to observe the internal workings of, say, a data structure
they are using. Java is a great language for enforcing abstraction
barriers with syntax
(language aspect).
Later, we use Abstract Data Types (ADTs) to handle what we need.
Inheritance Breaks Encapsulation:
1st Implementation:
(The upper box is in superclass)
2nd Implementation (Really bad):
By allowing inheritance in a particular situation, we now have a way to peek inside a beautifully encapsulated class.
We didn’t change anything in overridden barkMany
, but someone changed the superclass implementation that they thought it was safe and kapow (爽).
Type Checking & Casting
Type Checking
Static type is also called compile-time type
.
Override:
If a method in SLList is overridden by the VengefulSLList class, then the method that is called at runtime is determined by the run-time type
(dynamic type) of that variable (dynamic method selection
).
- If a method
overridden
: Call fromdynamic type
. - If a method
not overriden
: Call fromstatic type
.
1 | /** |
Compiler is conservative
in type checking.
1 | /* Compile-time Error */ |
In general, the compiler only allows method calls and assignments
based on compile-time types
.
1 | VengefulSLList<Integer> vsl2 = sl; |
Also, expressions using the new
keyword also have compile-time types.
1 | VengefulSLList<Integer> vsl = new SLList<Integer>(); |
Above, the compile-time type of the right-hand side of the expression is SLList
. The compiler checks if SLList
“is-a” VengefulSLList
, which it is not in all cases, and thus a compilation error results.
Further, method calls have compile-time types equal to their declared types
. Suppose we have this method:
1 | public static Dog maxDog(Dog d1, Dog d2) { ... } |
Casting
Java has a special syntax where you can tell the compiler that a specific expression has a specific compile-time type
. This is called casting
. With casting, we can tell the compiler to view an expression as a different compile-time type, ignoring its type checking duties
.
1 | // Pass type checking in compile time. |
Should be careful when doing downcasting
, especially when downcasting a superclass to a subclass.
1 | /* The problem lies in the right hand side. */ |
It’s okay if we are sure that the object whose static type is superclass actually has a subclass dynamic type.
1 | Animal a = new Dog(); |
Method Preferences
Example 0:
1 | Dog d = new Dog(); |
((Animal) d).makeNoise(d)
: In compile-time type checking, when checking to see if a method called makeNoise
exists in Animal, do we check for a makeNoise(Dog)
method or a makeNoise(Animal)
method?
- Compile time (Animal):
- Check
method name
-> matched - Check
parameter types
-> It depends the methods in Animal class.- It can match Animal’s
makeNoise(Animal)
- Or match
makeNoise(Dog)
- But it will definitely goes for the later one because it is a more specific match.
- It can match Animal’s
- Check
- Run time (Dog):
d
is a Dog, not an Animal.- If the method is overridden?
- Yes. Call the overridden version. (Dynamic Method Selection)
- No. Call the superclass version.
Idea:
- Parameter type-checking
prefers specific
if possible. - The order is important:
Class type
Checking:- Static type == Dynamic type:
- Override won’t happen. Overload may happen.
- Static type != Dynamic type:
- If the method is overridden -> dynamic method selection.
- If not -> just call the one matched in superclass.
- Static type == Dynamic type:
- The method we consider later in run time is the one
decided
in compile-time type checking.- In run time, it won’t change its mind from
makeNoise(Dog)
tomakeNoise(Animal)
. - In run time, we check if this method is overridden to decide if we need
dynamic method selection
.
- In run time, it won’t change its mind from
Consider the two examples (Very important):
Example 1:
1 | class Animal { |
Line 1:
- Compile time (Animal):
- Check
method name
-> matched - Check
parameter types
-> compatible (Dog is an Animal), matched
- Check
- Run time (Dog):
a
is Dog. The methodmakeNoise(Animal)
is overridden. So it outputs “Dog1”.
Line 2:
- Compile time (Animal):
- Check
method name
-> matched - Check
parameter types
-> exactly matched
- Check
- Run time (Dog):
a
is Dog. The methodmakeNoise(Animal)
is overridden. So it outputs “Dog1”.
Example 2:
1 | class Animal { |
Line 1:
- Compile time (Animal):
- Check
method name
-> matched - Check
parameter types
-> prefer specific version, exactly matched (makeNoise(Dog)
).
- Check
- Run time (Dog):
a
is Dog. The methodmakeNoise(Dog)
is overridden (not overloaded). So it outputs “Dog2”.
Line 2:
- Compile time (Animal):
- Check
method name
-> matched - Check
parameter types
-> exactly matched
- Check
- Run time (Dog):
a
is Dog. The methodmakeNoise(Animal)
is overridden. So it outputs “Dog1”.
Higher Order Functions (HoFs)
Higher Order Function: A function that treats another function as data
(or input
?).
1 | # In Python |
In old school Java (Java 7 and earlier), memory boxes (variables) could not contain pointers to functions. What that means is that we could not write a function that has a “Function” type, as there was simply no type for functions.
In Java, we can use Interface to do this. Use a Interface class to wrap up a method / function.
Interface:
1 | public interface IntUnaryFunction { |
Implementation:
1 | public class TenX implements IntUnaryFunction { |
Using:
1 | // Use Interface IntUnaryFunction rather than TenX - Abstraction |
Ex (Examprep 4)
Ex 1 (Puppers)
1 | public class Dog { |
For the following main method, at each call to play or bark, tell us what happens at runtime by selecting which method is run or if there is a compiler error or runtime error.
1 | public static void main(String[] args) { |
Ex 2 (Cast the line)
Consider each line independently whether it causes a compilation error, runtime error, or runs successfully.
1 | // Animal Tree |
Ex 3 (SLList Vista)
indexOf:
1 | // IntList class: |
Polymorphism vs. HoFs
Polymorphism
Polymorphism, at its core, means many forms
. In Java, polymorphism refers to how objects can have many forms or types
.
In object-oriented programming, polymorphism relates to how an object can be regarded as:
- an instance of its
own class
, - an instance of its
superclass
, - an instance of its
superclass's superclass
, - and so on.
The above statement is very great.
Subtype Poly. vs. HoF
Explicit HoF Approach:
1 | def print_larger(x, y, compare, stringify): |
Subtype Polymorphism Approach:
1 | def print_larger(x, y): |
In Python or C++, the way that the >
operator works could be redefined (overloaded) to work in different ways when applied to different types.
Unfortunately, Java does not have this capability. Instead, we turn to interface inheritance
to help us out. We do this by wrapping up the needed function in an interface (e.g. Arrays.sort
needs compare
which lives inside the comparator
interface). Arrays.sort
“calls back” whenever it needs a comparison.
1 | public interface OurComparable { // should be "extends Comparable"? |
So we can generalize the max function.
1 | /* Maximizer class */ |
What are the benefits to this approach?
- No need for maximization code in every class (i.e. no
Dog.maxDog(Dog[])
function required.- We don’t need to know that class should be Dog class.
- We have code that operates on multiple types (mostly) gracefully
Easy Quizzes:
Comparable
The OurComparable
interface that we just built works, but it’s not perfect. Here are some issues with it:
Awkward casting
to/from Objects1
2
3
4public int compareTo(Object o) { // or Comparable
Dog uddaDog = (Dog) o; // casting
return this.size - uddaDog.size;
}- We made it up.
- No existing classes implement
OurComparable
(e.g. String, etc.) - No existing classes use
OurComparable
(e.g. no built-in max function that uses OurComparable)
- No existing classes implement
The solution? We’ll take advantage of an interface that already exists called Comparable
. Comparable
is already defined by Java and is used by countless libraries.
Comparable looks very similar to the OurComparable
interface we made, but with one main difference.
1 | public interface Comparable<T> { |
The generic type
will help us avoid casting
an object to a specific type.
Comparator
Let’s start off by defining some terminology.
Natural order
- used to refer to the ordering implied in thecompareTo
method of a particular class. (Dog - size)
What if we’d like to sort Dogs in a different way
than their natural ordering, such as by alphabetical order of their name?
In explicit HoF approach, we can use compare
to specify a different compare function. However, in subtype polymorphism approach, compareTo1
, compareTo2
, and so forth? No!
1 | def print_larger(T x, T y, comparator<T> c): |
Java’s way of doing this is by using Comparator
‘s. Since a comparator is an object, the way we’ll use Comparator
is by writing a nested class
(so it’s static) inside Dog that implements the Comparator
interface.
Note: It is static
because we don’t need to instantiate a Dog to get a NameComparator. Instead, we get it when initializing the class.
1 | public interface Comparator<T> { |
Now, the Dog class should be like:
1 | import java.util.Comparator; |
In DogLauncher, the code is:
1 | Dog.NameComparator nc = Dog.getNameComparator(); |
In summary, a Comparable
says, “I want to compare myself to another object”. It is imbedded within the object itself, and it defines the natural ordering
of a type.
However, a Comparator
, on the other hand, is more like a third party machine
that compares two objects to each other. Since there’s only room
for one compareTo
method, if we want multiple ways to compare, we must turn to Comparator
.
Abstract Data Types (ADT)
Interface
In Java, Deque
is called an interface
. Conceptually, we call deque
an Abstract Data Type
. Deque
only comes with behaviors
, not any concrete ways to exhibit those behaviors. In this way, it is abstract
.
Stack
: LIFO propertyQueue
: FIFO propertyDeque
: Both LIFO and FIFO
In Java, we use interfaces to achieve ADTs:
- Cannot be instantiated.
- Can provide either
abstract
orconcrete
methods.- Use
default
keyword for concrete methods.
- Use
- Can provide only
public static final
variables. - Can provide only
public
methods.
1 | // In an interface class |
Abstract Class
Abstract class doesn’t have to implement the method listed in interface.
Interface vs. Abstract Class
Interfaces:
- Primarily for
interface inheritance
. Limited implementation inheritance. - Classes can implement
multiple interfaces
. (main difference)
Abstract classes:
- Can do anything an interface can do, and more.
- Subclasses
only extend one
abstract class.
List Hierarchy
Why didn’t we use Interface only?
AbstractList
provides default
implementations for methods. But why not just put them in List itself? No default methods in Java interfaces until 2014, and the AbstractList
was public so we can’t just throw it away.
A more of the real list hierarchy:
Even more:
When in doubt, try to use interfaces
in order to build up abstraction, reduce complexity, and hide unnecessary information.
Discussion 5:
Some scenes of application:
Given a news article, find the frequency of each word used in the article:
Use a
map
.Given an unsorted array of integers, return the array sorted from least to greatest:
Use a
priority queue
. For each integer in the unsorted array, enqueue the integer with a priority equal to its value.Implement the forward and back buttons for a web browser:
Use two stacks
, one for each button. Each time you visit a new web page, add the previous page to the back button’s stack, and also clear the forward button stack.When you click the back button, add the current page to the forward button stack, and pop a page from the back button stack.
When you click the forward button, add the current page to the back button stack, and pop a page from the forward button stack.
Ex (Discussion 5)
Ex 1 (Queue)
Two-Stack Version
1 | public class Queue { |
A Stack
can also be implemented by two Queue
s.
Recursive Version
1 | public class Queue { |
Ex 2 (TwoSum)
Given an array of integers $A$ and an integer $k$, return true if any two numbers in the array sum up to $k$, and return false otherwise. How would you do this? Give the main idea and what ADT you would use.
Brute-force: 2 loops.
A smarter way (use a map):
1 | public boolean twoSum(int[] A, int k) { |
Ex 3 (K-most Words)
Find the k-most
common words in a document. Assume that you can represent a string as an array of words, where each word is an element in the array. You might find using multiple data structures
useful.
Keep a count of all the words in the document using a HashMap<String, Integer>.
After we go through all of the words, each word will be mapped to how many times it’s appeared.
Then we can put all the words into a MaxPriorityQueue<String>
, using a custom comparator that compares words based on the counts in the HashMap. We can then pop off the k-most common words by just calling poll() on the MaxPriorityQueue k times.
1 | public static void topFivePopularWords(String[] words, int k) { |
Ex 4 (SortedStack)
Suppose we wanted a data structure SortedStack
that takes in integers, and maintains them in sorted order. SortedStack
supports two operations: push(int i)
and pop()
. Popping returns the next smallest
item in the SortedStack.
For example, if we inserted [10, 4, 8, 2, 14, 3] into a SortedStack, and then popped everything off, we would get [2, 3, 4, 8, 10, 14].
My Way
Not using stacks (iterative way). We have some invariants:
- The smallest item is at the top of stack.
- Push guarantees that the items in stack is in increasing order.
1 | public class SortedStack() { |
Answer
Using stacks:
We have two stacks A and B. A holds all the items, and B is our buffer.
1 | public class SortedStack<Item extends Comparable<Item>> { |
Package
Package names give a canonical
name for everything. “Canonical” means a unique representation for a thing.
Importing Class
Here are some different ways we can use a class:
- Entire name.
1
ug.joshh.animal.Dog d = new ug.joshh.animal.Dog();
- Can use import statement to provide shorthand notation for usage of a single class in a package.
1
2import ug.joshh.animal.Dog;
Dog d = new Dog(); - Wildcard import: Also possible to import multiple classes, but this is often a bad idea!
1
2import ug.joshh.animal.*;
Dog d = new Dog();
Import static
members:
1 | import static org.junit.Assert.assertEquals; |
The built-in java.util
package provides a number of useful interfaces and implementations.
Creating A Package
The reason why we use Package is to address the fact that classes might share names. A package is a namespace
that organizes classes and interfaces.
Naming convention: Package name starts with website address
(backwards).
1 | package bearmaps.hw4.wordladderpuzzle; |
Two steps:
- At the
top
of every file in the package, put the package name. - Make sure that the file is stored in a folder with the appropriate folder name. For a package with name
ug.joshh.animal
, use folderug/joshh/animal
.
The Default Package
Any Java class without a package name at the top are part of the default package
. In other words, from now on, when writing real programs, your Java files should always start with a default package declaration.
Idea: Ensure that we never have two classes with the same name at any situation.
But, you cannot import code from the default package! For example, if I were to create a DogLauncher
class in the default package in a DogLauncher.java
file, I would be unable to access this DogLauncher
class anywhere else outside of the default package
.
1 | package cat.place; |
JAR Files
JAR file are just zip files
.
- They do not keep your code safe!
- Easy to unzip and transform back into .Java files (from .class).
Note: Do not share .jar
files of your projects with other students.
Ex (Guide)
Ex 1 (Q&A)
If an
abstract class
extends anabstract class
, would it need to have function definitions for the abstract methods in the parent class? Similarly would aninterface
that implements anotherinterface
have to implement the non-default methods in the parent interface.No. No. If not implementing in the subclass or subinterface, the sub-subclass or sub-subinterface should do the job.
Can an
abstract class
be the subclass of anormal class
?Yes. (The default package)
Ex 2 (Same Method Name)
Consider the following code:
1 | class Tree { |
The output is:
1 | haha - Dog |
Autoboxing
How it works (wrapper type
and primitive type
):
- If Java code expects a wrapper type and gets a primitive, it is
autoboxed
. - If the code expects a primitive and gets a wrapper, it is
unboxed
.
1 | /* Not a magic */ |
Note:
- Arrays are never autoboxed/unboxed, e.g. an
Integer[]
cannot be used in place of anint[]
(or vice versa). - Autoboxing / unboxing incurs a
measurable
performance impact! - Wrapper types use
much
more memory than primitive types because of object overheads.
Space occupied:
- Addresses are 64 bits.
- ints are 32 bits.
- All Java objects take 64 bits + their fields.
1 | public static void bleepblorp() { |
Access Control
Access Modifier
How do public and private behave with packages and subclasses?
Public: This keyword opens up the access to everyone! This is generally what
clients
of the package can rely on to use.- Once deployed, the public members’ signatures should not change. It’s like a
promise
andcontract
to people using this public code that it will always be accessible to them. - Usually if developers want to “get rid of” something that’s public, rather than removing it, they would call it
deprecated
instead. - TL;DR: Open and promised to the world.
- Once deployed, the public members’ signatures should not change. It’s like a
Protected: Protected members are protected from the
outside
world.- Classes within the
same package
andsubclasses
can access these members, but the rest of the world (e.g. classes external to the package or non-subclasses) cannot! - TL;DR: Subtypes might need it, but subtype clients will not.
- Classes within the
Package Private: This is the default access given to Java members if there is no explicit modifier written.
- Package private entails that classes that belong in the same package can access, but not subclasses!
- The original owners of the class that’s being extended may not want certain features or members to be tampered with, if people choose to extend it — hence, package-private allows those who are familiar with the inner workings of the program to access and modify certain members, whereas it blocks those who are subclassing from doing the same.
- TL;DR: Only classes that live in the same package can access.
Private: Only code from the
given class
can access private members.- It is truly private from everything else, as subclasses, packages, and other external classes cannot access private members.
- TL;DR: Only the class needs this piece of code.
1 | package syntax3.map; |
Subtleties:
Interface methods are public in default.
Nested class
1 | public class Outer { |
In the example above: Anybody can create an Outer, anybody can create an Inner, and anybody can access an Inner’s variable. And an Inner can access this.outvar.
1 | public class Outer { |
In the example above, Inner is private. At this point, the access modifiers to all of inners’ members are irrelevant.
- An Outer
can access
an Inner’s variables. The reason is that “Inner” is a subordinate slave of Outer, and thus has no personal possessions. - We can change public (invar) to anything and have no observable effect (its function has been shadowed).
- Classes other than Outer cannot make or access Inner anymore. Common for things like LinkedListDeque (make your IntNode class private).
1 | public class Outer { |
In the example above, can an Inner access an Outer’s outvar? Yes, it can because Inner is part of the Outer class.
What could I do so that an Inner can’t use outvar?
1 | public class Outer { |
Can I instantiate a non-static inner class (nested class) without an outer instance?
1 | /** 1 */ |
Discussion 6
Exception
There are 2 overarching types of exceptions:
Checked
You must either wrap them in a
try/catch block
or pass the buck by usingthrows exception
in the method header.Unchecked
You don’t need to handle these. They are typically an error on a user’s part, which can’t really be helped.
Iterators and Iterables
Ex (Discussion 6)
Ex 1 (AltList)
1 | public AltList<X, Y> pairSwapped() { |
Ex 2 (Every K-th Element)
1 | public class KthIntList implements Iterator<Integer> { |