Documente Academic
Documente Profesional
Documente Cultură
1 Introduction
The root of the code will be in a package called oop.ex1. Most of the code will be in packages of the
form oop.ex1.<name>, as described below.
The public interfaces and classes in this package will be visible outside the package; those that are
defined without an access restriction (i.e., have package visibility) will be visible only inside the
package. The above also applies to visibility of methods: when access restriction is not specified for
interface methods, they are implicitly public, but object methods without an access restriction are
“package protected”. Always define your classes and methods with minimum sufficient visibility.
Since we want to define what the sequence does, rather than how it's done, we will start with defining
a Sequence interface which extends java.lang.Iterable and looks like this:
public interface Sequence extends Iterable {
Object first();
boolean isEmpty();
int size();
Object add(Object obj);
Object get(Object obj);
void addMany(Object... objects);
void addAll(Iterable objects);
}
Defining an interface includes adding appropriate documentation (Javadoc) – requirements described
textually below must be written in the documentation.
All methods in interfaces are implicitly public and abstract. Here's what the above methods should do:
● first() method return the first element of the sequence,
● isEmpty() tests whether sequence has any elements,
● size() returns the size of the sequence and zero if it is empty.
● The method add(Object) allows to add elements to the sequence, returning the added element
(or its equivalent, more on that later), and rejects nulls throwing a runtime exception,
● get(Object) returns an object equal to the parameter, if it is found in the sequence, or null
otherwise. If there are duplicate (equal to each other) objects in the sequence, the get method
may return any one of them. The sequence, as we said, should not contain null elements.
Iterable interface will allow our sequences to be used in for loops, e.g.:
Sequence sequence = ...;
for (Object element : sequence) {
//element variable, at each stage of iteration, represents next element in the sequence
//do whatever you need with element variable in the loop body
}
The iterator, which is also a design pattern, allows users of the collection to go over its elements,
performing some action for each one. In Java, iterator is represented by java.util.Iterator interface, and a
collection which allows iteration – java.lang.Iterable, defines java.util.Iterator iterator() method. You
will need to implement iterator() method too, and the actual data structure will have to provide a
corresponding Iterator class, a new instance of which this method will return – iterator instances are
not shared between multiple clients.
The get method can also be implemented using iterator() but how should we compare objects for
equality? We want to allow sub-classes to control this, and hence provide equal(Object,Object) which
is intended to be overridden by sub-classes. It is defined abstract and has no body, since it is intended
for internal use only we declare it as protected.
public abstract class AbstractSequence implements Sequence {
public int size() {
int count = 0;
for (Object element : this) count ++;
return count;
}
public void addMany(Object... objects) {
for (Object obj : objects) add(obj);
}
public Object get(Object obj) ...
public Object first() ...
public void addAll(Iterable objects) ...
protected abstract boolean equal(Object obj1, Object obj2);
}
The rest of the Sequence and Iterable methods (the ones not declared in AbstractSequence) are
automatically provided by the JVM as abstract methods.
When we create a new linked list, it is originally empty – the head pointer points to null. New elements
are added in the beginning of the list, using roughly the following algorithm:
1. Allocate a new node
2. Insert new element into the newly created node
3. Have new node point to old head
4. Update head to point to new node
Create a public class named SimpleSequence which should extend AbstractSequence providing
implementation for the missing methods. It should also override size() method with a more efficient
implementation that returns the size in constant time, and override the first() method. The get method
needs equality comparison, and here it is made using the Object.equals method. This data structure can
contain duplicate elements.
When overriding methods, we add @Override annotation before method modifiers (method modifiers
are public, protected, private, abstract, static, etc.) - this is not mandatory, but highly recommended,
since it allows the Java compiler to detect a misspelling or incorrect signature of overridden methods
that otherwise will likely result in incorrect behavior of our program. (More info on annotations here.)
Iterator: Usually data structures have an additional dedicated class for the iterator. Iterator has access to
internals of the data structure and implements hasNext() and next() method accordingly. To find out
more about iterators in Java, see this tutorial. In this exercise our data structures do not support removal
of elements, therefore our Iterator needs not support the optional remove() method. The convention in
Java is that not-implemented methods throw UnsupportedOperationException, so we implement the
remove() method of the iterator like this:
public void remove() {
throw new UnsupportedOperationException();
}
More on exceptions: Exceptions allow us to write fail-fast code - the sooner an error is spotted, the
easier it is to find its cause and the better are the chances to correct it. To learn more about exceptions,
see this tutorial. In this part of the exercise we will use several predefined Java exceptions, such as
UnsupportedOperationException, NullPointerException and java.util.NoSuchElementException. These
Generics: when looking through Javadoc and other documentation, you may notice things written
within angle brackets, like Iterator<E>. Those are type parameters to classes and methods, also known
as Generics. We will learn Generics later in the course, so in this exercise we won't use them yet. It
means that if you see a method signature which uses a parameter that came from within angle brackets,
but you didn't supply any angle-bracket parameters – the type you should use instead is Object. The
compiler may issue warnings that you are making unchecked calls – this is also caused by ignoring
Generics. To make the warnings go away, add @SuppressWarnings({"unchecked"}) annotation before
method modifiers (it can coexist with @Override annotation, and the order of annotations does not
matter). You are not allowed to use Generics in this exercise.
Comparator: is an object that encapsulates ordering, the interface consists of a single method:
public interface Comparator {
int compare(Object o1, Object o2);
}
The compare method compares its two arguments, returning a negative integer, 0, or a positive integer
depending on whether the first argument is less than, equal to, or greater than the second. If either of
the arguments has an inappropriate type for the Comparator, the compare method throws a
ClassCastException. Comparator can be used in the following way:
int low, high ... //or Integer low, high
Comparator comparator = ...
int comparison = comparator.compare(low,high);
if (comparison > 0) //low is greater than high
System.out.println(“invalid range”);
else if (comparison == 0) //low and high are equal
System.out.println(“single value = ” + low);
else //valid range
System.out.println(“range [” + low + “,” + high + “]”);
You can find more details and examples in this tutorial. Comparator is an example of Strategy design
pattern, it provides a “comparison strategy” to our sequence – any new value is placed in the
The equality comparison here is made using the comparator. This data structure can not contain
duplicates (elements considered equal by the comparator), so when user attempts adding a duplicate, it
has no affect on the data structure and matching collection resident is returned by the add method.
What methods of SimpleSequence do you need to override1?
Include your answer in the README file briefly describing what the overridden method does.
If you need to generate random numbers, you can use nextInt method in java.util.Random like this:
Random r = new Random(); //create instance of Random once per series
final int max = 10; //numbers between 0 and 9 will be generated
int[] randomNumbers = new int[100]; //create array of 100 numbers
for (int i = 0; i < 100; i++) {
randomNumbers[i] = r.nextInt(max); //set the value to a random number between 0 and 9
}
More examples of using Random can be found here.
The test you write can, but does not have to, take advantage of any of the supplied utilities.
1 This assignment does not require improving basic implementation of get
Override the default toString method in Family, PersonObject, FamilyMember and Marriage to produce
a sensible String representation of each of these objects
We will also implement the interface with a public PersonObject class. Name, gender and birth date are
mandatory parameters for its constructor, they cannot be null. PersonObject will store the information
in member fields, but not before performing some validations: that mandatory parameters are not null,
birth dated precedes death date, and death isn't modified after it is set.
3.1.2 Exceptions
To deal with invalid inputs, we will create several exceptions dedicated to family affairs. The
FamilyMember Marriage
FamilyMember will extend a Person and add more functionality to it, such as information on his/her
relatives and the role the person plays in the family, but we'll describe FamilyMember in more detail in
the section 3.3. Let's start with the family:
public class Family {
public Family(Person head) { /* start with a person who is “head of the family” */ }
public FamilyMember getHead() { ... }
public int size() { /* number of family members */ }
public Iterable getAllMembers() { /* list all family members */ }
public FamilyMember find(Person p) { /* find a Person in the family or null*/ }
//...
}
So except for head of the family, how does a person become a family member?
Our Family maintains its integrity, like any good Object should. One of the things we want to
ensure is that only one FamilyMember object within a family corresponds to a given person – if
the person already is in the family, we will not create new FamilyMember instance, but instead
return an existing one. Logic like that cannot be placed in a constructor, because constructor
always creates new instances, that's why we designate another method for family member
creation – we create an internal (package protected) method in the Family class that is dedicated
to creating family members (Factory design pattern – family is a “factory” of its members).
Every FamilyMember creation should go through this method, and only this method is allowed
to call FamilyMember constructor. Here is how the method should look like:
FamilyMember accept(Person p) {
if (p == null) throw new MissingInformationException("can't accept null");
return (FamilyMember) _index.add(new FamilyMember(this, p));
}
You need to implement all these operations, as well as provide string representations for your objects.
Find the right place in the code for each method – keep computations close to the data they operate on
and where the user would expect to find them. Don't repeat computations.
Reflect these assumptions in the Javadoc documentation of your classes. You should not
implement validations for things not listed above, for example: same person can be
simultaneously married in 2 different families, or have different parents – it's beyond the scope
of the current exercise to prevent that.
3.5.2 Security – control not just what comes in, but also what goes out
While internally we will store elements in sequences, we will not return Sequence, because
2 The guidelines in this exercise are made up for the purpose of teaching object-oriented programming and Java. They do
not represent political, social, or religious views of the OOP course staff or the Hebrew University.
We provide you with a basic test for family functionality – oop.ex1.test.family.FamilySmokeTest, that
reuses some utilities in oop.ex1.test package. After you finish the family implementation,
FamilySmokeTest has to run successfully, producing following output:
Test success rate = 7/7, results:
[alive person: success,
dead person: success,
family: success,
head of family: success,
members of family: success,
find in family: success,
marriage: success]
The test you write can, but does not have to, make any use of the provided test.
4 Guidelines
In this exercise you may use all interfaces and classes available in java.lang, but no others, except for
classes introduced in class, like java.util.Iterator, java.util.Date, java.util.Comparator and
java.util.Random. In particular, do not use any existing JDK (or other library) implementation of
java.util.Collection. When importing classes, do it per individual class, do not use bulk import
<package>.*. Static methods should also be imported individually, except in enums, where you are
allowed to do static import <enum-class>.*. Also you are allowed to use java.util.Arrays.deepToString
and java.util.Arrays.deepEquals utility methods.
The submission guidelines require that you code runs w/o errors or warnings. Read the submission
guidelines – they are binding! Submit a file named FamilyTree.jar, with the java files of your program
(but NO class files), and a README file. Be sure to follow the submission and styling guidelines of
the OOP course. Before submitting, pass your jar file through the filter ~oop/www/ex1/check_ex1 as
follows: ~oop/www/ex1/check_ex1 ex1.jar.
Using the code provided to you. Below is the list of packages and classes provided to you, some
complete, some are templates for you to add implementation. Do not change the structure of the code:
no renaming or moving of packages, public classes or public methods is allowed.
Tests: you need to make sure that the tests which we supply to you pass. If they don't, it likely indicates
a problem in your code. As for the tests that you have written – they will help you check your code, but
are not going to be tested or run by the course staff, so you are free to implement them as you wish.
Your grade will be affected only by the results of the provided test, and other tests that we (the staff)
have written.
6 Grading policies
● Working according to Coding style guidelines - 20%
● Design, README file - 30%
● Correct implementation – 50%
7 References
Data structures
“Introduction to Algorithms, 2nd Edition” by Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and
Clifford Stein (info)
“Data Structures and Algorithms in Java, 4th Edition” by Michael T. Goodrich and Roberto Tamassia (info)
Design patterns
“Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph
Johnson and John Vlissides (info)
Java
On-line documentation: http://java.sun.com/javase/reference/index.jsp
Javadoc http://java.sun.com/j2se/javadoc/
Annotation http://java.sun.com/docs/books/tutorial/java/javaOO/annotations.html
Iterator http://java.sun.com/docs/books/tutorial/collections/interfaces/collection.html#Iterator