Sie sind auf Seite 1von 38

Class Design

Introduction
Consider a car manufacturing. A motor giant BMW does not make all parts for its
new model of a car. Various pieces (wheels, doors, seats, breaks, sparks...) come from
different manufacturers. This model keeps the cost down and let BMW response to
the market needs in the most efficient way. The main BMW's responsibility is to
design a car, and then to assemble it from already existent parts (a small amount of
completely new details might be necessary). Object-oriented programming (OOP)
springs from the same idea of using preassembled modules. The OOP provides the
programmer with a natural way to divide an application into small reusable pieces,
called objects. At the same time, the OOP provides an elegant way to construct
applications in more efficient way by building up from a collection of reusable
components.

All software objects have state and behavior. For example, a student as an object has


a name, address, list of courses, and grades. All these are a state (or data). On the
other hand, the student has behaviors (or methods), such as adding a new course,
changing the address. Therefore,each object is a collection of data and methods (or
algorithms) applied to its internal data.

An object should never allow an external manipulation (the client access) over the
internal data or expose data to other objects. Why not give the client direct access to
the fileds? Because this will create problems in the long run. For example, if you
change the name of theat field, the client will no longer work. Also, the object should
never expose details of an algorithm implementation. Clients need only know what
your methods do, not how you implement them. In short,

 Data is private
 Implementation is hidden

Encapsulation is the idea of hiding data and methods implementations from the
client. By encapsulation you reduce data dependency and maximize reusability.

How do we describe the objects? By designing classes. Think of a class as a template


for objects. We can create several objects from one class. Such process is called
an instantiation. An object, in effect, is an ainstantiated class. There are three ways to
design classes: by composition, via inheritance, and via interface.

Composition (or aggregation) is achieved by using existing class as a part of a new


class. For example, the ArrayStack class includes an array of objects.

Inheritance allows you to define a new class in terms of an old class. The new class
automatically inherits all public members of the original class. Inheritance is useful
for creating specialized objects that share comon behavior. We will see more on
inheritance later in the course.

Interfaces act like inheritance in the way that they define a set of properties,
methods,and events, but unlike classes they do not provide implementation. A class
that implements the interface muct implement every method of that interface exactly
as it is defined. We will see more on interfaces later in the course.

Class Declaration
Freely speaking, a Java program is a set of packages, where each package is a set of
classes. A class can contain any number of fields and methods - they are
called members of a class. The implementation design might suggest using private and
public members. The private methods mostly used as helper methods to assist in the
implementation of public methods. The data in a class is typically private, therefore
you must ensure that a class implements public methods to access (and maybe
modify) the data. We will call public methods getters or accessors if they are
designed to only access the data. We will call public methods setters or mutators if
they allow to modification of the internal data.

Constructor
All Java classes have constructors (one ore more) that are used to allocate memory for
the object and to initialize the data fields. Constructors are not methods, though they
look like a method. There are three differences between constructors and methods:

1. A constructor has the same name as the class.


2. A constructor has no return value.
3. A constructor is called with the new operator when a class is instantiated or by
using a special reference this().

When writing your own class, you don't have to provide constructors for it.
The default constructor is automatically provided. The default constructor doesn't do
anything. However, if you want to perform some initialization, you will have to write
one ore more constructors for your class.

Method Overloading

Java enables several methods of the same name to be defined as long as these methods
have a different set of parameters: the number of parameters, the types of the
parameters and the order of the parameters. This is called method overloading. When
an overloaded method is called, the Java compiler selects the proper method by
examining the number, types and order of the arguments in the call. Here is an
example of legal declarations:
public void methodA()
public void methodA(int z)
public void methodA(int z, String str)
public void methodA(String str, int z)

Method overloading is commonly used to create several methods with the same name
that perform similar tasks, but on different data types. A method's name along with
the number, type and order of its parameters is called the method signatures. If you
attempt to specify two methods with identical signature, the compiler issues an error
message.

Scope

The scope of an identifier for a variable, reference or method is the portion of the
program in which the identifier can be referenced. A local variable or reference
declared in a block can be used only in that block or in blocks nested within that
block. The scopes for an identifier are class scope and block scope. Methods and
instance variables of a class have class scope. Class scope begins at the opening left
brace, {, of the class definition and terminates at the closing right brace, }, of the class
definition. Class scope enables methods of a class to blockquoteectly invoke all
methods defined in that same class and to blockquoteectly access all instance
variables defined in the class.

Identifiers declared inside a block have block scope. Block scope begins at the
identifier's declaration and ends at the terminating right brace } of the block. Local
variables of a method have block scope as do method parameters, which are also local
variables of the method.

For example, the following code fragment


int i = 1;
for(int sum = 0; i ‹ 2; i++) sum += i;

while(i < 3) {
sum += i;
i++;
}
will result in the error "cannot resolve symbol sum". Since the variable sum is defined
inside the for-loop it can be referenced only within that scope.

Pass by value

In Java objects are passed by value. Pass by value means that when an argument is
passed to a method, the method receives a copy of the original value. In the example
below, the string "change me" has two references s and x assigned to it.
public class Demo
{
public static void main(String[] args)
{
String s = "change me";
action(s);
System.out.println(s);
}

public static void action(String x)


{
x = "changed";
}
}
These two references live in different scopes. When we are back to
the main() method, the formal parameter x from the action() method is garbag
collected and the new object "changed" is destroyed. Therefore, the object "change
me" passed into the method won't be actually changed. The picture below illustrates
the internal mechanism.
In other words, pass-by-value means that the method cannot change the whole object,
but can invoke the object's methods and modify the parts within the object. If you
want to actually change the whole object, you need to explicitly return it as in the
code below
public class Demo
{
public static void main(String[] args)
{
String s = "change me";
s = action(s);
System.out.println(s);
}

public static String action(String x)


{
x = "changed";
return x;
}
}

If the above data type was an array you would have an ability to internally modify the
object. Consider the following code fragment:
public class Demo
{
public static void main(String[] args)
{
int[] a = {1,2,3};
action(a);
System.out.println( Arrays.toString(a) );
}

public static void action(int[] x)


{
x[0] = 5;
}
}

It demonstrates that the first element of the array has been changed.

Static vs. Non-Static

A class may contain static and instance members (fields and methods). The static


members have a keyword static included in their declarations. They are called by
using the class name.

Static members do not formally belong to an object; they exist before the object was
created, Static members are created when you compile your program. In runtime,
when you instantiate a class, only instance members are created. Moreover, every new
object instantiated from the same class, has a new set of instance variables. In
contrary, there is only one copy of static members, regardless how many object you
created. Consider the following code fragment
public class StaticDemo
{
public static void main(String[] args)
{
Demo obj1 = new Demo();
Demo.number++;
Demo obj2 = new Demo();
System.out.println(obj2.getX());
}
}
class Demo
{
private int x;
static int number = 0;

public Demo() {number++;}


public int getX() {x = number; return x;}
}

In this code example we created two objects ob1 and obj2 that share a static variable
number. This variable was incremented three times: during instantiation of ob1,
during instantiation of obj2 and by a direct call to it. The staic variable number leaves
in a global context and can be invoked either using references obj1 and obj2 or using
the class name Demo.

OO design principles
There are many heuristics associated with object oriented design. For example,

 all member variables should be private


 global variables should be avoided

In this chapter we briefly glimpse a design principle that is fundamental to these


heuristics -- the open-closed principle. When a single change to a program results in a
cascade of changes, the program becomes fragile and unpredictable. The open-closed
principle says that you should design modules that never change.
Software modules should be open for Extension and closed for Modification.

To extend the behavior of the system, we do not modify old code that already works. -
we add new code, How can we do this? Let us consider a code fragment. Given the
Part class
public class Part
{
private double price;

public Part(double p)
{
price = p;
}

public double getPrice()


{
return price;
}
}

and the totalPrice method of some other class:


public double totalPrice(Part[] parts)
{
double total = 0.0
for(int k = 0; k < parts.length; k++)
total += parts[k].getPrice();

return total
}
that totals the price of each part in the specified array of parts. The code looks quite
innocent and you would not suspect a problem with it in the future. Now assume that
the accounting department comes out with a new pricing policy for some parts, for
example DVD_Drives are 20% off sale. To reflect this change we have to rewrite the
totalPrice method
public double totalPrice(Part[] parts)
{
double total = 0.0
for(int k = 0; k < parts.lebgth; k++)
{
if(parts[k] instanceof DVD_Drive)
total += 0.8 * parts[k].getPrice();
else
total += parts[k].getPrice();
}

return total
}

This is an example of bad design - every time the accounting department comes out
with a new pricing policy, we have to modify the totalPrice method. The code is not
closed for modification. What can we do? A better idea is not to fix the price but to
introduce various pricing policies via a PricingPolicy class.
public class Part
{
private double price;
private PricingPolicy policy;

public Part(double p)
{
price = p;
}

public double getPrice()


{
return price * policy.getPolicy();
}

public double setPricingPolicy(PricingPolicy newPolicy)


{
policy = newPolicy;
}
}

With this solution we can dynamically set pricing policies for each modified part. The
general idea is that we try to design and implement classes so that they use abstract
classes and interfaces. That way, we can extend the functionality by simply adding
new subclasses which add new behavior.

Victor S.Adamchik, CMU, 2009


Self-Review Questions
1. Does declaration create an object? 
No.
2. When you create an object do you always need a variable associated with the
new object? 
No, anonymous objects exist
3. What are three differences between constructors and methods?
1. A constructor has the same name as the class.
2. A constructor has no return value.
3. A constructor is called with the new operator when a class is instantiated or
by using a special reference this()
4. Can static methods call non-static methods in the same class?
No.
5. What does the keyword final specified for a variable mean? 
Means the value of the variable won't change.
6. What is encapsulation?

1. data hiding
2. implementation hiding
3. all of the above
4. none of the above
b. What is a method's signature?

1. the name of the method along with the number of its parameters.
2. the name of the method along with the number and type of its
parameters.
3. the name of the method along with the number, type and order of its
parameters.
4. the name of the method, its return type and the number, type, order of its
parameters.
b. Examine the following code segment
c. public class Demo
d. {
e. public static void main(String[] ags)
f. {
g. Test o1 = new Test();
h. Test o2 = new Test();
i. Test o3 = o2;
j. System.out.println(o1.number);
k. }
l. }
m. class Test
n. {
o. static int number=0;
p. public Test()
q. {
r. number++;
s. }
t. }

What is the output?

1. 0
2. 1
3. 2
4. none of the above
b. Analyze the following code and choose the best answer:
c. public class Analyze
d. {
e. private int x;
f.
g. public static void main(String[] args)
h. {
i. Analyze tmp = new Analyze();
j. System.out.println(tmp.x);
k. }
l. }

1. Since x is private, you cannot access it;


2. You can access x without creating an object;
3. Since x is an instance variable, it can be accessed through an object;
4. You cannot create a self-referenced object;
b. Examine the following code segment
c. class Scope
d. {
e. private int x;
f.
g. public Scope () {x = 0;}
h.
i. public void method()
j. {
k. int x = 1;
l. System.out.print( this.x );
m. }
n. }

What is the output?

1. 0
2. 1
3. none of the above
Concept of Hashing
Introduction

The problem at hands is to speed up


searching. Consider the problem of
searching an array for a given value. If the
array is not sorted, the search might require
examining each and all elements of the
array. If the array is sorted, we can use the
binary search, and therefore reduce the
worse-case runtime complexity to O(log
n). We could search even faster if we know
in advance the index at which that value is
located in the array. Suppose we do have
that magic function that would tell us the
index for a given value. With this magic
function our search is reduced to just one
probe, giving us a constant runtime O(1).
Such a function is called a hash function .
A hash function is a function which when
given a key, generates an address in the     
table.

The example of a hash function is a book call number. Each book in the library has
a unique call number. A call number is like an address: it tells us where the book is
located in the library. Many academic libraries in the United States, uses Library of
Congress Classification for call numbers. This system uses a combination of letters
and numbers to arrange materials by subjects.

A hash function that returns a unique hash number is called a universal hash
function. In practice it is extremely hard to assign unique numbers to objects. The
later is always possible only if you know (or approximate) the number of objects to be
proccessed.

Thus, we say that our hash function has the following properties

 it always returns a number for an object.


 two equal objects will always have the same number
 two unequal objects not always have different numbers

The precedure of storing objets using a hash function is the following.


Create an array of size M. Choose a hash function h, that is a mapping from
objects into integers 0, 1, ..., M-1. Put these objects into an array at indexes
computed via the hash function index = h(object). Such array is called a hash
table.

How to choose a hash function? One approach of creating a hash function is to use
Java's hashCode() method. The hashCode() method is implemented in the Object
class and therefore each class in Java inherits it. The hash code provides a numeric
representation of an object (this is somewhat similar to the toString method that gives
a text representation of an object). Conside the following code example
Integer obj1 = new Integer(2009);
String obj2 = new String("2009");
System.out.println("hashCode for an integer is " + obj1.hashCode());
System.out.println("hashCode for a string is " + obj2.hashCode());
It will print
hashCode for an integer is 2009
hashCode for a string is 1537223

The method hasCode has different implementation in different classes. In the String
class, hashCode is computed by the following formula
s.charAt(0) * 31n-1 + s.charAt(1) * 31n-2 + ... + s.charAt(n-1)
where s is a string and n is its length. An example
"ABC" = 'A' * 312 + 'B' * 31 + 'C' = 65 * 312 + 66 * 31 + 67 = 64578

Note that Java's hashCode method might return a negative integer. If a string is long


enough, its hashcode will be bigger than the largest integer we can store on 32 bits
CPU. In this case, due to integer overflow, the value returned by hashCode can be
negative.
Review the code in HashCodeDemo.java.

Collisions

When we put objects into a hashtable, it is possible that different objects (by
the equals() method) might have the same hashcode. This is called a collision. Here is
the example of collision. Two different strings ""Aa" and "BB" have the same key: .
"Aa" = 'A' * 31 + 'a' = 2112
"BB" = 'B' * 31 + 'B' = 2112

How to resolve collisions? Where do we put the


second and subsequent values that hash to this
same location? There are several approaches in
dealing with collisions. One of them is based on
idea of putting the keys that collide in a linked
list! A hash table then is an array of lists!! This
technique is called a separate chaining collision
resolution.
    

The big attraction of using a hash table is a constant-time performance for the basic
operations add, remove, contains, size. Though, because of collisions, we cannot
guarantee the constant runtime in the worst-case. Why? Imagine that all our objects
collide into the same index. Then searching for one of them will be equivalent to
searching in a list, that takes a liner runtime. However, we can guarantee an expected
constant runtime, if we make sure that our lists won't become too long. This is usually
implemnted by maintaining a load factor that keeps a track of the average length of
lists. If a load factor approaches a set in advanced threshold, we create a bigger array
and rehash all elements from the old table into the new one.

Another technique of collision resolution is a linear probing. If we cannoit insert at


index k, we try the next slot k+1. If that one is occupied, we go to k+2, and so on.
This is quite simple approach but it requires new thinking about hash tables. Do you
always find an empty slot? What do you do when you reach the end of the table?

HashSet

In this course we mostly concern with using hashtables in applications. Java provides
the following classes HashMap, HashSet and some others (more specialized ones).

HashSet is a regular set - all objects in a set are distinct. Consider this code segment
String[] words = new String("Nothing is as easy as it
looks").split(" ");

HashSet<String> hs = new HashSet<String>();

for (String x : words) hs.add(x);

System.out.println(hs.size() + " distinct words detected.");


System.out.println(hs);

It prints "6 distinct words detected.". The word "as" is stored only once.

HashSet stores and retrieves elements by their content, which is internally converted
into an integer by applying a hash function. Elements from a HashSet are retrieved
using an Iterator. The order in which elements are returned depends on their hash
codes.

Review the code in HashSetDemo.java.

The following are some of the HashSet methods:

 set.add(key) -- adds the key to the set.


 set.contains(key) -- returns true if the set has that key.
 set.iterator() -- returns an iterator over the elements

Spell-checker

You are implement a simple spell checker using a hash table. Your spell-checker will
be reading from two input files. The first file is a dictionary located at the
URLhttp://www.andrew.cmu.edu/course/15-121/dictionary.txt . The program should
read the dictionary and insert the words into a hash table. After reading the dictionary,
it will read a list of words from a second file. The goal of the spell-checker is to
determine the misspelled words in the second file by looking each word up in the
dictionary. The program should output each misspelled word.

See the solution here Spellchecker.java.

HashMap

HashMap is a collection class that is designed to store elements as key-value pairs.


Maps provide a way of looking up one thing based on the value of another.
We modify the above code by use of the HashMap class to store words along with
their frequencies.
String[] data = new String("Nothing is as easy as it looks").split("
");

HashMap‹String, Integer> hm = new HashMap‹String, Integer>();

for (String key : data)


{
Integer freq = hm.get(key);
if(freq == null) freq = 1; else freq ++;
hm.put(key, freq);
}
System.out.println(hm);

This prints {as=2, Nothing=1, it=1, easy=1, is=1, looks=1}.

HashSet and HashMap will be printed in no particular order. If the order of insertion
is important in your application, you should
use LinkeHashSet and/or LinkedHashMap classes. If you want to print dtata in sorted
order, you should use TreeSet and or TreeMap classes

Review the code in SetMapDemo.java.


The following are some of the HashMap methods:

 map.get(key) -- returns the value associated with that key. If the map does not
associate any value with that key then it returns null. Referring to
"map.get(key)" is similar to referring to "A[key]" for an array A.
 map.put(key,value) -- adds the key-value pair to the map. This is similar to
"A[key] = value" for an array A.
 map.containsKey(key) -- returns true if the map has that key.
 map.containsValue(value) -- returns true if the map has that value.
 map.keySet() -- returns a set of all keys
 map.values() -- returns a collection of all value

Anagram solver

An anagram is a word or phrase formed by reordering the letters of another word or


phrase. Here is a list of words such that the words on each line are anagrams of each
other:
barde, ardeb, bread, debar, beard, bared

bears, saber, bares, baser, braes, sabre


In this program you read a dictionary from the web site
at http://www.andrew.cmu.edu/course/15-121/dictionary.txt and build
a Map( ) whose key is a sorted word (meaning that its characters are sorted in
alphabetical order) and whose values are the word's anagrams.

See the solution here Anagrams.java.

Priority Queue
We are often faced with a situation in which certain events/elements in life have
higher or lower priorities than others. For example, university course prerequisites,
emergency vehicles have priority over regular vehicles. A Priority Queue is like a
queue, except that each element is inserted according a given priority. The simplest
example is provided by real numbers and ≤ or ≥ relations over them. We can say that
the smallest (or the largest) numerical value has the highest priority. In practice,
priority queues are more complex than that. A priority queue is a data structure
containing records with numerical keys (priorities) that supports some of the
following operations:

 Construct a priority queue


 Insert a new item.
 Remove an item.with the highest priority
 Change the priority
 Merge two priority queues

Observe that a priority queue is a proper generalization of the stack (remove the
newest) and the queue (remove the oldest).

Elementary Implementations

There are numerous options for implementing priority queues. We start with simple
implementations based on use of unordered or ordered sequences, such as linked lists
and arrays. The worst-case costs of the various operations on a priority queue are
summarized in this table 
 

   insert  deleteMin  remove  findMin  merge


ordered array N 1 N 1 N
ordered list N 1 1 1 N
unordered array 1 N 1 N N
unordered list 1 N 1 N 1

Later on in the course we will see another implementation of a priority queueu based
on a binary heap.

Comparable and Comparator interfaces

The Comparable interface contains only one method with the following signature:
public int compareTo(Object obj);
The returned value is negative, zero or positive depending on whether this object is
less, equals or greater than parameter object. Note a difference between the equals()
and compareTo() methods. In the following code example we design a class of
playing cards that can be compared based on their values:
class Card implements Comparable<Card>
{
private String suit;
private int value;

public Card(String suit, int value)


{
this.suit = suit;
this.value = value;
}
public int getValue()
{
return value;
}
public String getSuit()
{
return suit;
}
public int compareTo(Card x)
{
return getValue() - x.getValue();
}
}

It is important to recognize that if a class implements the Comparable interface than


compareTo() and equals() methods must be correlated in a sense that
if x.compareTo(y)==0, then x.equals(y)==true. The default equals() method
compares two objects based on their reference numbers and therefore in the above
code example two cards with the same value won't be equal. And a final comment, if
the equals() method is overriden than the hashCode() method must also be overriden,
in order to maintain the following properety: if x.equals(y)==true,
then x.hashCode()==y.hashCode().

Suppose we would like to be more flexible and have a different way to compare cards,
for example, by suit. The above implementation doesn’t allow us to do this, since
there is only one compareTo method in Card. Java provides another interface which
we can be uses to solve this problem:
public interface Comparator<AnyType>
{
compare(AnyType first, AnyType second);
}
Notice that the compare() method takes two arguments, rather than one. Next we
demonstrate the way to compare two cards by their suits, This method is defined in its
own class that implements Comparator:
class SuitSort implements Comparator<Card>
{
public int compare(Card x, Card y)
{
return x.getSuit().compareTo( y.getSuit() );
}
}

Objects that implement the Comparable interface can be sorted using the sort()
method of the Arrays and Collections classes. In the following code example, we
randomly generate a hand of five cards and sort them by value and then by suit:
String[] suits = {"Diamonds", "Hearts", "Spades", "Clubs"};
Card[] hand = new Card[5];
Random rand = new Random();

for (int i = 0; i < 5; i++)


hand[i] = new Card(suits[rand.nextInt(4)], rand.nextInt(12));

System.out.println("sort by value");
Arrays.sort(hand);
System.out.println(Arrays.toString(hand));

System.out.println("sort by suit");
Arrays.sort(hand, new SuitSort() );
System.out.println(Arrays.toString(hand));

Objects can have several different ways of being compared. Here is another way of
comparing cards: first by value and if values are the same then by suit:
class ValueSuitSort implements Comparator<Card>
{
public int compare(Card x, Card y)
{
int v = x.getValue() - y.getValue();

return ( v == 0) ? x.getSuit().compareTo(y.getSuit()) : v;
}
}

Victor S.Adamchik, CMU, 2009


Self-Review Questions
1. An iterator over an ArrayList object returns the items in 
a)   insertion order 
b)   comparable order 
c)   random order
2. An iterator over a HashSet object returns the items in 
a)   insertion order 
b)   comparable order 
c)   random order
3. An iterator over a TreeSet object returns the items in 
a)   insertion order 
b)   comparable order 
c)   random order
4. Implement a method that prints a given ArrayList using an Iterator interface
public void printList( ArrayList<Integer> data )
public void printList(ArrayList <Integer> data){
Iterator<Integer> it = data.iterator();
while(it.hasNext()){
System.out.print(it.next() + " ");
}
}

5. Consider the following sequence of integer keys to be hashed into a hash table
using the hash function H(key) = key modulo tablesize:
11, 4, 5, 12, 25, 18

Insert each key above, from left to right, into the hash table below using linear
probing to resolve collisions. 
 

 0   1   2   3   4   5   6   7 


    25     18     11     4     5     12    

6. Given an array of size n. One of its elements is repeated twice. Design an


efficient algorithm that would find that duplicate.

Create a hash set and traverse the array. Check if each element is already in the
hash set, and if it is, then you've found the duplicate. Otherwise, add the
element to the hash set.
7. Given an array of integers. Design an efficient algorithm that finds two
numbers x and y such that x + y = 0. Consider two cases a)sorted array;
b)unsorted array.

a)Do the same thing as in the previous question, but check if the element's
complement is in the hashset. 
b)Do the same thing as in the previous question, but check if the element's
complement is in the hashset.

8. An anagram is a word or phrase formed by reordering the letters of another


word or phrase. Here is a list of words such that the words on each line are
anagrams of each other: 
 

barde, ardeb, bread, debar, beard, bared


bears, saber, bares, baser, braes, sabre
baste, betas, beast, tabes, beats, bates, abets
caster, carets, recast, reacts, cartes, caters, traces, crates
caret, crate, react, trace, cater, recta, carte

Build a Map whose key is a sorted word (meaning that its characters are sorted
in alphabetical order) and whose values are the word's anagrams.

9. Given an array of strings


10. String[] words =
{"Fred","bob","Tom","Mark","john","Steve"};

sort them ignoring the case.


private class CompareIgnoreCase implements Comparator{
public void compare(Object o1, Object o2){
String str1 = ((String)o1).toLowerCase();
String str2 = ((String)02).toLowerCase();

return str1.compareTo(str2);
}
public void equals(Object obj){
String str1 = ((String)o1).toLowerCase();
String str2 = ((String)02).toLowerCase();

return str1.equals(str2);
}
}

Arrays.sort(words, new CompareIgnoreCase());


11.Given an array of email addresses
12. String[] words =
{"Fred@cmu.edu","Bob@yahoo.com","Tom@gmail.com",
13. "Mark@ucla.edu","John@pit.edu", "Steve@microsoft.com"};

sort them by company/institution names.


private class CompareCompany implements Comparator{
public void compare(Object o1, Object o2){
String[] str1 = ((String)o1).split("@.");
String[] str2 = ((String)02).split("@.");

return str1[1].compareTo(str2[1]);
}
public void equals(Object obj){
String str1 = ((String)o1).split("@.");
String str2 = ((String)02).split("@.");

return str1[1].equals(str2[1]);
}
}

Arrays.sort(words, new CompareCompany());


I/O Framework
Introduction
Java views each file as a sequential stream of bytes. When a file is opened, an object
is created and a stream is associated with the object. The streams provide
communication channels between a program and a particular file or device.
Object System.in (standard input stream object) enables a program to input bytes
from the keyboard, object System.out (standard output stream object) enables a
program to output data to the screen, and object System.err (standard error stream
object) enables a program to output error messages to the screen. System.out can be
redirected to send its output to a file. By convention, System.err stream is used to
display error messages.

The java package java.io provides standard library classes to deal with streams.


Classes in the package are divided into input and output streams, and then subdivided
into those, which operate with characters data (text files) and with byte data (binary
files).

The difference between a stream of characters from a stream of bytes is that the
former uses Unicode characters, which are represented by 16 bits - that is it, in java, a
character is made up of two bytes. In contrary, the C language uses 1 byte characters
(ASCII table). The natural question to ask is how Java deals with these encoding
problems: converting bytes to characters and back. The Java has different tables of
encoding and you can choose any of them. To see the default table you print
System.getProperty("file.encoding");
One of the encoding tables is called UTF-8, which can represent ASCII characters (one
byte), and other characters as two or three bytes. This is very important issue if you
are concerned with writing applications that operate in an international context.

Standard Output

To print output values in our programs, we have been


using System.out.print() and System.out.println(). The printf() method gives
us more control over the output. Compare
System.out.println(Math.PI);
System.out.printf("%7.5f", Math.PI);
The first argument of printf() "%7.5f" is a format that describes how the second
argument is to be printed. This format says, that we print a float number of 7
characters that includes 5 digits after the decimal point.

Command-line Input

Any Java program takes input values from the command line and prints output back
to the terminal window. When you use the java command to invoke a Java
program Demofrom the command line, you type something like this
java Demo parameter1 parameter2

Input values parameter1 and parameter2are passed to a main() method as an array of


Strings.

Standard Input

The string of characters that you type in the terminal window after the command
line is the standard input stream. The following code example (let us call this class
Average.java)takes a parameter from the command line, then reads so many
numbers from standard input and computes the average:
int n = Integer.parseInt(args[0]);
double sum = 0.0;
Scanner scanner = new Scanner(System.in); //it's defined in
java.util

for(int k = 0; k < n; k++)


sum += scanner.nextInt();

System.out.println("Average = " + sum/n);


You envoke this program by
java Average 4

Redirecting Standard Output

We can redirect a program's standard output to a file. The following command


java Average 4 > out.txt
redirect the output from the terminal window to a text file out.txt. Similarly, we can
redirect standard input so that a program reads data from a file instead of the
terminal application. For example,
java Average 4 < input.txt
reads four integers from a text file input.txt and prints their average to the terminal
window. We can redirect the ouput to and from a file in one command line
java Average 4 < input.txt > out.txt

Exceptions
Exceptions in the object-oriented language is a quite complex topic that requires time
to understand and apply properly. By now you perhaps met a few exception classes
such as the following

NullPointerException

ArrayIndexOutOfBoundsException

ArithmeticException

NullPointerException

Here are three examples demonstrating what causes the NullPointerException:


String str = null;
int n = str.length();

String[] data = new String[10];


int k = data[5].length();

String s = null;
StringBuffer sb = new StringBuffer(s);

All exceptions are objects. Once an exception is thrown you have three options:

1. do not handle it at all


2. handle it where it occurs
3. handle it later in the program

Consider this code segment in which a user is prompted to enter an integer:


Scanner scanner = new Scanner(System.in);
int m = scanner.nextInt();
If the user enters not integer, the above program will terminate abnormally -
the nextInt() method will throw InputMismatchException.
That is your responsibility (as a programmer) to validate the input. One way to do this
is to handle exceptions, meaning that you have to catch exceptions.

Catching Exceptions

To catch an exception, you have to use try and catch blocks. You wrap try around the


statement (or a group of statements) which may throw an exception. Here is an
example of how you would handle illegal input
Scanner scanner = new Scanner(System.in);
System.out.print("Enter an integer: ");
try
{
int n = scanner.nextInt();
System.out.println("You entered " + n);
}
catch (InputMismatchException e)
{
System.out.println("The input is not an integer");
}
A catch block, which follows try, defines a particular exception you are trying to
catch. Note, you may have several catch-blocks for one try-block: one catch- block
for a particular exception. If an exception is thrown, control is immediately
transferred to a correspondent catch-block, skipping all statements below the
statement which generated an exception.
finally
The finally clause provides a mechanism for executing a section of code whether or
not an exception is thrown or caught. Regardless to what happens in the try-block,
the statements placed inside the finally-block will always execute. Those statements
are usually like closing the input file, or database, or network connection.

Review the code example ExceptionDemo.java class.

Throwing Exceptions

It's common to test parameters of methods or constructors for valid input data. If the
value is illegal, you throw an exception by using the throw keyword along with the
object you want to throw. Examine this code example
public Card(int suit, int value)
{
if (isValidValue(value) && isValidSuit(suit))
{
this.value = value;
this.suit = suit;
}
else
throw new RuntimeException("Illegal suit or value");
}

Throw behaves similar to return, it performs structured transfer from the place in your
program where an abnormal condition was detected to a place where it can be
handled. It also transmits information. The exception object may contain a detail
message that can be retrieved by invoking the getMessage() method

Any method that may throw an exception, must declare the exception in it's method
declaration. This is implemented by the use of the throws clause. Throws clause
follows the method name and lists the exception types that may be thrown:
public static main(String[ ] args) throws IOException
{
}

The throws clause is used

 to propagate an exception.
 in a method declaration to notify that an exception might be thrown by the
method.

When to handle exceptions

     Developer     Client  

 Method body     Throw the exception    Use a try/catch block 

 Method header    Declare the exception    Propagate the exception 

Java distinguishes checked and unchecked exceptions. A checked exception means


that if you ignore it, your program will not compile. What do you do in this case?
Either you use try-catch blocks or propagete the exception usingh throws clause.

Reading Text Files


There are two approaches of reading text files:
I II

classes: FileReader Scanner


BufferedReader File
exceptions: FileNotFoundException
FileNotFoundException
IOException IOException

methods: readLine println


close close

The FileReader class represents an input file that contains character data. The class
throws FileNotFoundException (checked exception), so you have to wrap try and
catch block around each time you open a file. For the efficient reading, we use
the BufferedReader class. It provides the method readLine() that allows to read the
entire text line (a line is a sequence of characters terminated either by '\n' or '\r' or their
combination). The method returns a String, or null if the end of the stream has been
reached. The readLine() method throws IOException, so you have to handle it.

A text file can also be read using the Scanner class. Using the Scanner offers the
advantage for text processing of various data formats.. Scanner has a lot of other
features, with support for regular expressions, delimiter definitions, skipping input,
searching in a line, reading from different inputs and others..

Review the code examples Read.java and ReadHTML.java

Writing to a Text File


writing

classes: FileWriter
BufferdWriter
PrintWriter

exceptions: IOException

methods: println
close

The FileWriter class represents an output file that contains character data. It


throws IOException. The class has minimal method support, like a method write ,
which writes a character or an array of characters or String. Unless prompt output is
required, it is advisable to wrap a BufferedWriter around for efficiency reason. The
class has the newLine() method, which uses the platform's own notion of line
separator. Neither FileWriter nor BufferedWriter provides enough methods to deal
with a formatted output. For such purposes we shall use another class PrintWriter on
the top of the previous two classes. The class uses familiar methods print and println.
Review the code example Write.java

New Line

The line separator string depends on your operating system. On UNIX, the value of
the line separator is "\n", on Windows - "\r\n", and on Mac - "\r" . In Java the line
separator is defined by the system property line.separator , which should be
called as it follows
String out = "any characters";
out += System.getProperty("line.separator");

Appending Data

The class FileWriter has a second argument, which is boolean, indicating whether


or not to append the data written. Here is a code fragment,
BufferedWriter out = new BufferedWriter (new FileWriter("out.txt",
true));

Serialization
Object is serialized by transforming it into a sequence of bytes. Once serialized, the
object (actually its data fields) can be stored in a file to be read later, or sent across a
network to another computer. Serialization allows you to create persistent objects,
objects that can be stored and then reconstituted for later use. The basic mechanism
of serialization is simple. To serialize an object, you create an output stream
FileOutputStream out = new FileOutputStream("output.ser");
ObjectOutputStream outStream = new ObjectOutputStream(out);

and then use a method writeObject() to write an object into a file in a binary format.
It throws IOException and NotSerializableException.
outStream.writeObject("Today");
outStream.writeObject(new Date());
outStream.flush();

You can write primitive data types as well. They are written to the stream with the
methods, such as writeInt, writeFloat, and a few others.

The class of each serializable object is encoded including the class name
and signature of the class, the values of the object's fields and arrays, and the closure
of any other objects referenced from the initial objects. Only objects with
the Serializable interface can be serialized. The interface does not have any
methods, and serves only as a flag to the compiler. For most classes the default
serialization is sufficient. Many classes of API implement Serializable interface, for
example String, Vector.

Reading an object from a stream, like writing, is straightforward. To reverse the


process, we first create an input stream,
FileInputStream in = new FileInputStream ("output.ser");
ObjectInputStream inStream = new ObjectInputStream(inStream);
and then invoke readObject() method to read the information from "output.dat"
file.
String today = (String) inStream.readObject();
Date date = (Date) inStream.readObject();
readObject() throws ClassNotFoundException and IOException checked exceptions.
Primitive data types are read from the stream with the methods, such as readInt,
readFloat.

The default serialization process serializes each field of the object


except static and transient. Using transient, we can exclude particular
information when we serialize the oblect. An example of such excluded information
could be a password.

Review the code example SerializationDemo.java

Victor S.Adamchik, CMU, 2009

Algorithmic Complexity
Introduction
Algorithmic complexity is concerned about how fast or slow particular algorithm
performs. We define complexity as a numerical function T(n) - time versus the input
size n. We want to define time taken by an algorithm without depending on the
implementation details. But you agree that T(n) does depend on the implementation!
A given algorithm will take different amounts of time on the same inputs depending
on such factors as: processor speed; instruction set, disk speed, brand of compiler and
etc. The way around is to estimate efficiency of each algorithm asymptotically. We
will measure time T(n) as the number of elementary "steps" (defined in any way),
provided each such step takes constant time.

Let us consider two classical examples: addition of two integers. We will add two
integers digit by digit (or bit by bit), and this will define a "step" in our computational
model. Therefore, we say that addition of two n-bit integers takes n steps.
Consequently, the total computational time is T(n) = c * n, where c is time taken by
addition of two bits. On different computers, additon of two bits might take different
time, say c1 and c2, thus the additon of two n-bit integers takes T(n) = c1 * n and T(n)
= c2* n respectively. This shows that different machines result in different slopes, but
time T(n) grows linearly as input size increases.

The process of abstracting away details and determining the rate of resource usage in
terms of the input size is one of the fundamental ideas in computer science.

Asymptotic Notations
The goal of computational complexity is to classify algorithms according to their
performances. We will represent the time function T(n) using the "big-O" notation to
express an algorithm runtime complexity. For example, the following statement

T(n) = O(n2)
says that an algorithm has a quadratic time complexity.

Definition of "big Oh"

For any monotonic functions f(n) and g(n) from the positive integers to the positive
integers, we say that f(n) = O(g(n)) when there exist constants c > 0 and n 0 > 0 such
that
f(n) ≤ c * g(n), for all n ≥ n0

Intuitively, this means that function f(n) does not grow faster than g(n), or that
function g(n) is an upper bound for f(n), for all sufficiently large n→∞

Here is a graphic representation of f(n) = O(g(n)) relation:


Examples:

 1 = O(n)
 n = O(n2)
 log(n) = O(n)
 2 n + 1 = O(n)

The "big-O" notation is not symmetric: n = O(n 2) but n2 ≠ O(n).

Exercise. Let us prove n2 + 2 n + 1 = O(n2). We must find such c and n0 that n 2 + 2 n
+ 1 ≤ c*n2. Let n0=1, then for n ≥ 1

1 + 2 n + n2 ≤ n + 2 n + n2 ≤ n2 + 2 n2 + n 2 = 4 n2


Therefore, c = 4.

Constant Time: O(1)

An algorithm is said to run in constant time if it requires the same amount of time
regardless of the input size. Examples:

 array: accessing any element


 fixed-size stack: push and pop methods
 fixed-size queue: enqueue and dequeue methods

Linear Time: O(n)

An algorithm is said to run in linear time if its time execution is directly proportional
to the input size, i.e. time grows linearly as input size increases. Examples:
 array: linear search, traversing, find minimum
 ArrayList: contains method
 queue: contains method

Logarithmic Time: O(log n)

An algorithm is said to run in logarithmic time if its time execution is proportional to


the logarithm of the input size. Example:

 binary search

Recall the "twenty questions" game - the task is to guess the value of a hidden number
in an interval. Each time you make a guess, you are told whether your guess iss too
high or too low. Twenty questions game imploies a strategy that uses your guess
number to halve the interval size. This is an example of the general problem-solving
method known as binary search:

locate the element a in a sorted (in ascending order) array by first comparing a
with the middle element and then (if they are not equal) dividing the array into
two subarrays; if a is less than the middle element you repeat the whole
procedure in the left subarray, otherwise - in the right subarray. The procedure
repeats until a is found or subarray is a zero dimension.

Note, log(n) < n, when n→∞. Algorithms that run in O(log n) does not use the whole
input.

Quadratic Time: O(n2)

An algorithm is said to run in logarithmic time if its time execution is proportional to


the square of the input size. Examples:

 bubble sort, selection sort, insertion sort

Definition of "big Omega"

We need the notation for the lower bound. A capital omega Ω notation is used in this
case. We say that f(n) = Ω(g(n)) when there exist constant c that f(n) ≥ c*g(n) for for
all sufficiently large n. Examples

 n = Ω(1)
 n2 = Ω(n)
 n2 = Ω(n log(n))
 2 n + 1 = O(n)

Definition of "big Theta"

To measure the complexity of a particular algorithm, means to find the upper and
lower bounds. A new notation is used in this case. We say that f(n) = Θ(g(n)) if and
only f(n) = O(g(n)) and f(n) = Ω(g(n)). Examples

 2 n = Θ(n)
 n2 + 2 n + 1 = Θ( n2)

Analysis of Algorithms

The term analysis of algorithms is used to describe approaches to the study of the
performance of algorithms. In this course we will perform the following types of
analysis:

 the worst-case runtime complexity of the algorithm is the function defined by


the maximum number of steps taken on any instance of size a.
 the best-case runtime complexity of the algorithm is the function defined by the
minimum number of steps taken on any instance of size a.
 the average case runtime complexity of the algorithm is the function defined by
an average number of steps taken on any instance of size a.
 the amortized runtime complexity of the algorithm is the function defined by a
sequence of operations applied to the input of size a and averaged over time.

Example. Let us consider an algorithm of sequential searching in an array.of size n.


Its worst-case runtime complexity is O(n) 
Its best-case runtime complexity is O(1) 
Its average case runtime complexity is O(n/2)=O(n)

Amortized Time Complexity

Consider a dynamic array stack. In this model push() will double up the array size if
there is no enough space. Since copying arrays cannot be performed in constant time,
we say that push is also cannot be done in constant time. In this section, we will show
that push() takes amortized constant time.

Let us count the number of copying operations needed to do a sequence of pushes.

 push()   copy   old array size   new array size 


 1    0    1    -  
 2    1    1    2  
 3    2    2    4  
 4    0    4    -  
 5    4    4    8  
 6    0    8    -  
 7    0    8    -  
 8    0    8    -  
 9    8    8    16  

We see that 3 pushes requires 2 + 1 = 3 copies.

We see that 5 pushes requires 4 + 2 + 1 = 7 copies.

We see that 9 pushes requires 8 + 4 + 2 + 1 = 15 copies.

In general, 2n+1 pushes requires 2n + 2n-1+ ... + 2 + 1 = 2n+1 - 1 copies.

Asymptotically speaking, the number of copies is about the same as the number of
pushes.
2n+1 - 1
limit --------- = 2 = O(1)
n→∞ 2n + 1
We say that the algorithm runs at amortized constant time.

Victor S.Adamchik, CMU, 2009