Object-Oriented Programming with Java, part I + II

cc

This material is licensed under the Creative Commons BY-NC-SA license, which means that you can use it and distribute it freely so long as you do not erase the names of the original authors. If you make changes in the material and want to distribute this altered version of the material, you have to license it with a similar free license. The use of the material for commercial use is prohibited without a separate agreement.

Authors: Arto Hellas, Matti Luukkainen
Translators to English: Emilia Hjelm, Alex H. Virtanen, Matti Luukkainen, Virpi Sumu, Birunthan Mohanathas, Etiënne Goossens
Extra material added by: Etiënne Goossens, Maurice Snoeren, Johan Talboom

The course is maintained by Technische Informatica Breda


Interfaces

Interface is an instrument we have to define the functionality our classes should have. Interfaces are defined as normal Java classes, but instead of the definition “public class …”, we write “public interface …”. The interfaces influence class behaviour by defining the method names and return values, but they do not contain method implementation. The access modifier is not specified, because it is always public. Let us have a look at the interface Readable, which defines whether an object can be read.

public interface Readable {
    String read();
}

The interface Readable defines the method read(), which returns a string object. The classes which implement an interface decide in which way the methods defined in the interface have to be implemented, in the end. A class implements an interface by adding the keyword implements between the class and the interface name. Below, we create the class SMS which implements Readable interface.

public class SMS implements Readable {
    private String sender;
    private String content;

    public SMS(String sender, String content) {
        this.sender = sender;
        this.content = content;
    }

    public String getSender() {
        return this.sender;
    }

    public String read() {
        return this.content;
    }
}

Because the class SMS implements the interface Readable (public class SMS implements Readable), the class SMS must implement the method public String read(). The implementations of methods defined in the interface must always have public access.

An interface is a behavioural agreement. In order to implement the behaviour, the class must implement the methods defined by the interface. The programmer of a class which implements an interface has to define what the behaviour will be like. Implementing an interface means to agree that the class will offer all the actions defined by the interface, i.e. the behaviour defined by the interface. A class which implements an interface but does not implement some of the interface methods can not exist.

Let us implement another class which implements the Readable interface, in addition to our SMS class. The class EBook is the electronic implementation of a book, and it contains the book name and page number. The EBook reads one page at time, and the methodpublic String read() always returns the string of the following page.

public class EBook implements Readable {
    private String name;
    private ArrayList<String> pages;
    private int pageNumber;

    public EBook(String name, ArrayList<String> pages) {
        this.name = name;
        this.pages = pages;
        this.pageNumber = 0;
    }

    public String getName() {
        return this.name;
    }

    public int howManyPages() {
        return this.pages.size();
    }

    public String read() {
        String page = this.pages.get(this.pageNumber);
        nextPage();
        return page;
    }

    private void nextPage() {
        this.pageNumber = this.pageNumber + 1;
        if(this.pageNumber % this.pages.size() == 0) {
            this.pageNumber = 0;
        }
    }
}

Classes which implement interfaces generate objects as well as normal classes, and they can be used as ArrayList types too.

    SMS message = new SMS("ope", "Awesome stuff!");
    System.out.println(message.read());

    ArrayList<SMS> messages = new ArrayList<SMS>();
    messages.add(new SMS("unknown number", "I hid the body.");
Awesome stuff!
    ArrayList<String> pages = new ArrayList<String>();
    pages.add("Split your method into short clear chunks.");
    pages.add("Devide the user interface logic from the application logic.");
    pages.add("At first, always code only a small program which solves only a part of the problem.");
    pages.add("Practice makes perfect. Make up your own fun project.");

    EBook book = new EBook("Programming Hints.", pages);
    for(int page = 0; page < book.howManyPages(); page++) {
        System.out.println(book.read());
    }
Split your method into short clear chunks.
Divide the user interface logic from the application logic.
At first, always code only a small program which solves only a part of the problem.
Practice makes perfect. Make up your own fun project.

Exercise interfaces-1: NationalService

In the exercise layout, you find the premade interface NationalService, which contains the following operations:

  • public int getDaysLeft() which returns the number of days left on service
  • public void work(), which reduces the working days by one. The working days number can not become negative.
    public interface NationalService {
     int getDaysLeft();
     void work();
    }
    

Exercise interfaces-1.1: CivilService

Create the class CivilService which implements your NationalService interface. The class constructor is without parameter. The class has the object variable daysLeft which is initialised in the constructor receiving the value 362.

Exercise interfaces-1.2: MilitaryService

Create the class MilitaryService which implements your NationalService interface. The class constructor has one parameter, defining the days of service (int daysLeft).

An Interface as Variable Type

When we create a new variable we always specify its type. There are two types of variable types: primitive-type variables (int, double, …) and reference-type (all objects). As far as reference-type variables are concerned, their class has also been their type, so far.

    String string = "string-object";
    SMS message = new SMS("teacher", "Something crazy is going to happen");

The type of an object can be different from its class. For instance, if a class implements the interface Readable, its type is Readable, too. For instance, since the class SMS implements the interface Readable, it has two types: SMS and Readable.

    SMS message = new SMS("teacher", "Awesome stuff!");
    Readable readable = new SMS("teacher", "The SMS is Readable!");

    ArrayList<String> pages = new ArrayList<String>();
    pages.add("A method can call itself.");

    Readable book = new EBook("Recursion Principles", pages);
    for(int page = 0; page < ((EBook)book).howManyPages(); page++) {
        System.out.println(book.read());
    }

Because an interface can be used as type, it is possible to create a list containing interface-type objects.

    ArrayList<Readable> numberList = new ArrayList<Readable>();

    numberList.add(new SMS("teacher", "never been programming before..."));
    numberList.add(new SMS("teacher", "gonna love it i think!"));
    numberList.add(new SMS("teacher", "give me something more challenging! :)"));
    numberList.add(new SMS("teacher", "you think i can do it?"));
    numberList.add(new SMS("teacher", "up here we send several messages each day"));

    for (Readable readable: numberList) {
        System.out.println(readable.read());
    }

The class EBook implements the interface Readable. However, notice that even though the type of the class EBook is an interface, EBook is not the type of all the classes which implement the Readable interface. It is possible to assign an EBook object to a Readable variable, but the assignment does not work in the opposite way without a particular type change.

    Readable readable = new SMS("teacher", "The SMS is Readable!"); // works
    SMS message = readable; // not possible

    SMS transformedMessage = (SMS) readable; // works

Type casting works if and only if the variable’s type is really what we try to change it into. Type casting is not usually a best practice; one of the only cases where that is legitimate is in connection with the method equals.

An Interface as Method Parameter

The real use of interfaces becomes clear when we use them for the type of a method parameter. Because interfaces can be used as variable type, they can be used in method calls as parameter type. For instance, the below method print of class Printer receives a Readable variable.

public class Printer {
    public void print(Readable readable) {
        System.out.println(readable.read());
    }
}

The real value of the print method of class Printer is that its parameter can be whatever class instance which implements our Readable interface. When we call the method of an object, the method will work regardless of the class of this object, as long as the object implements Readable.

    SMS message = new SMS("teacher", "Wow, this printer is able to print them, actually!");
    ArrayList<String> pages = new ArrayList<String>();
    pages.add("{3, 5} are the numbers in common between {1, 3, 5} and {2, 3, 4, 5}.");

    EBook book = new EBook("Introduction to University Mathematics.", pages);

    Printer printer = new Printer();
    printer.print(message);
    printer.print(book);
Wow, this printer is able to print them, actually!
{3, 5} are the numbers in common between {1, 3, 5} and {2, 3, 4, 5}.

Let us implement another NumberList class, where we can add interesting readable stuff. The class has an ArrayList instance as object variable where we save things to read. We add items to our number list through the add method which receives a Readable variable as parameter.

public class NumberList {
    private ArrayList<Readable> readables;

    public NumberList() {
        this.readables = new ArrayList<Readable>();
    }

    public void add(Readable readable) {
        this.readables.add(readable);
    }

    public int howManyReadables() {
        return this.readables.size();
    }
}

Number lists are usually readable, so we can implement the interface Readable to the class NumberList. The number list method read reads all the objects of the readables list, and it adds them one by one to a string which is returned by the read() method.

public class NumberList implements Readable {
    private ArrayList<Readable> readables;

    public NumberList() {
        this.readables = new ArrayList<Readable>();
    }

    public void add(Readable readable) {
        this.readables.add(readable);
    }

    public int howManyReadables() {
        return this.readables.size();
    }

    public String read() {
        String read = "";
        for(Readable readable: this.readables) {
            read += readable.read() + "\n";
        }

        this.readables.clear();
        return read;
    }
}
    NumberList joelList = new NumberList();
    joelList.add(new SMS("matti", "have you already written the tests?"));
    joelList.add(new SMS("matti", "did you have a look at the submissions?"));

    System.out.println("Joel has " + joelList.howManyReadables() + " messages to read");
Joel has got 2 messages to read

Because the type of NumberList is Readable, we can add NumberList objects to our number list, too. In the example below, Joel has a lot of messages to read, luckily Mikael deals with it and reads the messages on behalf of Joel.

NumberList joelList = new NumberList();
for (int i = 0; i < 1000; i++) {
    joelList.add(new SMS("matti", "have you already written the tests?"));
}

System.out.println("Joel has " + joelList.howManyReadables() + " messages to read");
System.out.println("Let's delegate some reading to Mikael");

NumberList mikaelList = new NumberList();
mikaelList.add(joelList);
mikaelList.read();

System.out.println();
System.out.println("Joel has " + joelList.howManyReadables() + " messages to read");
Joel has 1000 messages to read
Let's delegate some reading to Mikael

Joel has 0 messages to read

The method read which is called in connection to Mikael’s list parses all the Readable objects contained in the list, and calls their method read. At the end of each read method call the list is cleared. In other words, Joel’s number list is cleared as soon as Mikael reads it.

At this point, there are a lot of references; it would be good to draw down the objects and try to grasp how the read method call connected to mikaelList works!

Exercise interfaces-2: Boxes and Things

Exercise interfaces-2.1: ToBeStored

We need storage boxes when we move to a new apartment. The boxes are used to store different things. All the things which are stored in the boxes have to implement the following interface:

public interface ToBeStored {
   double weight();
}

Add the interface to your program. New interfaces are added almost in the same way as classes: you choose new Java interface instead of new Java class.

Create two classes which implement the interface Book and CD.

The constructor of Book has the following parameters

  • its writer (String)
  • name (String)
  • weight (double)

The constructor of CD has the following parameters:

  • artist (String)
  • title (String),
  • publishing year (int) All CDs weigh 0.1 kg.

Remember that the classes also have to implement the interface ToBeStored. The classes have to work in the following way:

public static void main(String[] args) {
   Book book1 = new Book("Fedor Dostojevski", "Crime and Punishment", 2);
   Book book2 = new Book("Robert Martin", "Clean Code", 1);
   Book book3 = new Book("Kent Beck", "Test Driven Development", 0.5);

   CD cd1 = new CD("Pink Floyd", "Dark Side of the Moon", 1973);
   CD cd2 = new CD("Wigwam", "Nuclear Nightclub", 1975);
   CD cd3 = new CD("Rendezvous Park", "Closer to Being Here", 2012);

   System.out.println(book1);
   System.out.println(book2);
   System.out.println(book3);
   System.out.println(cd1);
   System.out.println(cd2);
   System.out.println(cd3);
}

Print output:

Fedor Dostojevski: Crime and Punishment
Robert Martin: Clean Code
Kent Beck: Test Driven Development
Pink Floyd: Dark Side of the Moon (1973)
Wigwam: Nuclear Nightclub (1975)
Rendezvous Park: Closer to Being Here (2012)

Attention! The weight is not reported here.

Exercise interfaces-2.2: Box

Create the class Box, which has to store things that implement the interface ToBeStored. The constructor have one parameter; maximum weight, expressed in kilograms (double). The Box can’t be added more things than its maximum capacity allows for. The weight of the items contained in the box can never exceed the box maximum capacity.

The following example clarifies the box use:

public static void main(String[] args) {
   Box box = new Box(10);

   box.add( new Book("Fedor Dostojevski", "Crime and Punishment", 2) ) ;
   box.add( new Book("Robert Martin", "Clean Code", 1) );
   box.add( new Book("Kent Beck", "Test Driven Development", 0.7) );

   box.add( new CD("Pink Floyd", "Dark Side of the Moon", 1973) );
   box.add( new CD("Wigwam", "Nuclear Nightclub", 1975) );
   box.add( new CD("Rendezvous Park", "Closer to Being Here", 2012) );

   System.out.println( box );
}

Printing:

Box: 6 things, total weight 4.0 kg

Note: because the weights are represented as double, the rounding can cause small mistakes. You don’t need to care about it when you do the exercise.

Exercise interfaces-2.3: Box weight

If you created the object variable double weight in your box which records the weight of your things, replace it now with a method which calculates the weight:

public class Box {
   //...

   public double weight() {
       double weight = 0;
       // it calculates the total weight of the things which had been stored
       return weight;
   }
}

When you need the box weight – if you have to add a new thing, for instance – you can simply call the method which calculates it.

In fact, the method could work well even though it retured the value of an object variable. However, we train a situation where you don’t need to maintain the object variable explicitly, but you can calculate it when you need it. With the following exercise, the information stored in a box object variable would not necessarily work properly, however. Why?

Exercise interfaces-2.4: Boxes are Stored too!

Implementing the interface ToBeStored requires that the class has the method double weight(). In fact, we just added this method to the class Box. Boxes can be stored!

Boxes are objects where we can store object which implement the interface ToBeStored. Boxes also implement this interface. This means that you can also put boxes inside your boxes!

Try this out: create a couple of boxes in your program, put things into the boxes and put smaller boxes into the bigger ones. Try also what happens when you put a box into itself. Why does it happen?

An Interface as Method Return Value

As well as any other variable type, an interface can also be used as method return value. Below you find Factory, which can be used to produce different objects that implement the interface Item. In the beginning, Factory produces books and disks at random.

public class Factory {
  public Factory(){
      // Attention: it is not necessary to write an empty constructor if there are no other constructors in the class.
      // In such cases, Java creates a default constructor, i.e a constructor without parameter
  }

   public Item produceNew(){
       Random random = new Random();
       int num = random.nextInt(4);
       if ( num==0 ) {
           return new CD("Pink Floyd", "Dark Side of the Moon", 1973);
       } else if ( num==1 ) {
           return new CD("Wigwam", "Nuclear Nightclub", 1975);
       } else if ( num==2 ) {
           return new Book("Robert Martin", "Clean Code", 1 );
       } else {
           return new Book("Kent Beck", "Test Driven Development", 0.7);
       }
   }
}

It is possible to use our Factory without knowing precisely what kind of classes are present in it, as long as they all implement Item. Below you find the class Packer which can be used to get a boxful of items. The Packer knows the factory which produces its Items:

public class Packer {
   private Factory factory;

    public Packer(){
        factory = new Factory();
    }

    public Box giveABoxful() {
        Box box = new Box(100);

        for ( int i=0; i < 10; i++ ) {
            Item newItem = factory.produceNew();
            box.add(newItem);
        }

        return box;
    }
}

Because the packer doesn’t know the classes which implement the interface Item, it is possble to add new classes which implement the interface without having to modify the packer. Below, we create a new class which implements our interface Item - ChocolateBar. Our Factory was modified to produce chocolate bars in addition to books and CDs. The class Packer works fine with the extended factory version, without having to change it.

public class ChocolateBar implements Item {
    // we don't need a constructor because Java is able to generate a default one!

    public double weight(){
        return 0.2;
    }
}

public class Factory {
    // we don't need a constructor because Java is able to generate a default one!

    public Item produceNew(){
        Random random = new Random();
        int num = random.nextInt(5);
        if ( num==0 ) {
           return new CD("Pink Floyd", "Dark Side of the Moon", 1973);
        } else if ( num==1 ) {
           return new CD("Wigwam", "Nuclear Nightclub", 1975);
        } else if ( num==2 ) {
           return new Book("Robert Martin", "Clean Code", 1 );
        } else if ( num==3 ) {
           return new Book("Kent Beck", "Test Driven Development", 0.7);
        } else {
           return new ChocolateBar();
        }
    }
}

Using interfaces while programming permits us to reduce the number of dependences among our classes. In our example, Packer is not dependent on the classes which implement interface Item, it is only dependent on the interface itself. This allows us to add classes wihout having to change the class Packer, as long as they implement our interface. We can even add classes that implement the interface to the methods which make use of our packer without compromising the process. In fact, less dependences make it easy to extend a program.

Made-Up Interfaces

Java API offers a sensible number of made-up interfaces. Below, we get to know some of Java’s most used interfaces: List, Map, Set and Collection.

List

The interface List defines lists basic functionality. Because the class ArrayList implements the interface List, it can also be initialized through the interface List.

List<String> strings = new ArrayList<String>();
strings.add("A String object within an ArrayList object!");

As we notice from the List interface Java API, there are a lot of classes which implement the interface List. A list construction which is familiar to hakers like us is the linked list. A linked list can be used through the interface List in the same way as the objects created from ArrayList.

List<String> strings = new LinkedList<String>();
strings.add("A string object within a LinkedList object!");

Both implementations of the List interface work in the same way, in the user point of view. In fact, the interface abstracts their internal functionality. ArrayList and linkedList internal construction is evidently different, anyway. ArrayList saves the objects into a table, and the search is quick with a specific index. Differently, LinkedList builds up a list where each item has a reference to the following item. When we search for an item in a linked list, we have to go through all the list items till we reach the index.

When it comes to bigger lists, we can point out more than evident performance differences. LinkedList’s strength is that adding new items is always fast. Differently, behind ArrayList there is a table which grows as it fills up. Increasing the size of the table means creating a new one and copying there the information of the old. However, searching against an index is extremely fast with an ArrayList, whereas we have to go thourgh all the list elements one by one before reaching the one we want, with a LinkedList. More information about data structures such as ArrayList and LinkedList internal implementation comes with the course Data structures and algorithms.

In our programming course you will rather want to choose ArrayList, in fact. Programming to interface is worth of it, anyway: implement your program so that you’ll use data structures via interfaces.

Map

The Map Interface defines HashMap basic fuctionality. Because HashMaps implement the interface Map, it is possible to initialize them trough the interface Map.

Map<String, String> translations = new HashMap<String, String>();
translations.put("gambatte", "tsemppiä");
translations.put("hai", "kyllä");

You get HashMap keys thourgh the method keySet.

Map<String, String> translations = new HashMap<String, String>();
translations.put("gambatte", "good luck");
translations.put("hai", "yes");

for(String key: translations.keySet()) {
    System.out.println(key + ": " + translations.get(key));
}
gambatte: good luck
hai: yes

The keySet method returns a set made of keys which implement interface Set . The set which implement the interface Set can be parsed with a for-each loop. HashMap values are retrieved through the method values, which returns a set of values which implement the interface Collection. We should now focus on interfaces Set and Collection.

Set

The interface Set defines the functionality of Java’s sets. Java’s sets always contain 0 or 1 element of a certain type. Among the others, HashSet is one of the classes which implement the interface Set. We can parse a key set through a for-each loop, in the following way

Set<String> set = new HashSet<String>();
set.add("one");
set.add("one");
set.add("two");

for (String key: set) {
    System.out.println(key);
}
one
two

Notice that HashSet is not concerned on the order of its keys.

Collection

The Collection interface defines the functionality of collections. Among the others, Java’s lists and sets are collections – that is, List and Set interfaces implement the Collection interface. Collection interface provides methods to check object existence (the contains method) and to check the collection size (size method). We can parse any class which implements the Collection interface with a for-each loop.

We now create a HashMap and parse first its keys, and then its values.

Map<String, String> translations = new HashMap<String, String>();
translations.put("gambatte", "good luck");
translations.put("hai", "yes");

Set<String> keys = translations.keySet();
Collection<String> keySet = keys;

System.out.println("Keys:");
for(String key: keySet) {
    System.out.println(key);
}

System.out.println();
System.out.println("Values:");
Collection<String> values = translations.values();
for(String value: values) {
    System.out.println(value);
}
Keys:
gambatte
hai

Values:
yes
good luck

The following example would have produced the same output, too.

Map<String, String> translations = new HashMap<String, String>();
translations.put("gambatte", "good luck");
translations.put("hai", "yes");

System.out.println("Keys:");
for(String key: translations.keySet()) {
    System.out.println(key);
}

System.out.println();
System.out.println("Values:");
for(String value: translations.values()) {
    System.out.println(value);
}

In the following exercise we build an online shop, and we train to use classes through their interfaces.

Exercise interfaces-3: Online Shop

Next, we create some programming components which are useful to manage an online shop.

Exercise interfaces-3.1: ToBeStored

Create the class Storehouse with the following methods:

  • public void addProduct(String product, int price, int stock), adding to the storehouse a product whose price and number of stocks are the parameter values
  • public int price(String product) returns the price of the parameter product; if the product is not available in the storehouse, the method returns -99

Inside the storehouse, the prices (and soon also the stocks) of the products have to be stored into a Map<String, Integer> variable! The type of the object so created can be HashMap, but you should use the interface Map for the variable type (see 5.4.2)

The next example clarifies storehouse use:

Storehouse store = new Storehouse();
store.addProduct("milk", 3, 10);
store.addProduct("coffee", 5, 7);

System.out.println("prices:");
System.out.println("milk:  " + store.price("milk"));
System.out.println("coffee:  " + store.price("coffee"));
System.out.println("sugar: " + store.price("sugar"));

Prints:

prices:
milk:  3
coffee:  5
sugar: -99

Exercise interfaces-3.2: Product Stock

Store product stocks in a similar Map<String, Integer> variable as the one you used for their prices. Fill the Storehouse with the following methods:

  • public int stock(String product) returns the stock of the parameter product.
  • public boolean take(String product) decreases the stock of the parameter product by one, and it returns true if the object was available in the storehouse. If the product was not in the storehouse, the method returns false, the product stock cannot go below zero.

An example of how to use the storehouse now:

Storehouse store = new Storehouse();
store.addProduct("coffee", 5, 1);

System.out.println("stocks:");
System.out.println("coffee:  " + store.stock("coffee"));
System.out.println("sugar: " + store.stock("sugar"));

System.out.println("we take a coffee " + store.take("coffee"));
System.out.println("we take a coffee " + store.take("coffee"));
System.out.println("we take sugar " + store.take("sugar"));

System.out.println("stocks:");
System.out.println("coffee:  " + store.stock("coffee"));
System.out.println("sugar: " + store.stock("sugar"));

Prints:

stocks:
coffee:  1
sugar: 0
we take coffee true
we take coffee false
we take sugar false
stocks:
coffee:  0
sugar: 0

Exercise interfaces-3.3: Listing the Products

Let’s add another method to our storehouse:

  • public Set<String> products() returns a name set of the products contained in the storehouse

The method can be implemented easily. Using the Map’s method keySet, you can get the storehouse products by asking for their prices or stocks.

An example of how to use the storehose now:

Storehouse store = new Storehouse();
store.addProduct("milk", 3, 10);
store.addProduct("coffee", 5, 6);
store.addProduct("buttermilk", 2, 20);
store.addProduct("jogurt", 2, 20);

System.out.println("products:");
for (String product : store.products()) {
   System.out.println(product);
}

Prints:

products:
buttermilk
jogurt
coffee
milk

Exercise interfaces-3.4: Purchase

We add purchases to our shopping basket. With purchase we mean a specific number of a specific product. For instance, we can put into our shopping basket either a purchase corresponding to one bread, or a purchase corresponding to 24 coffees.

Create the class Purchase with the following functionality:

  • a constructor public Purchase(String product, int amount, int unitPrice), which creates a purchase corresponding the parameter product. The product unit amount of purchase is clarified by the parameter amount, and the third parameter is the unit price
  • public int price(), which returns the purchase price. This is obtained by raising the unit amount by the unit price
  • public void increaseAmount() increases by one the purchase unit amount
  • public String toString() returns the purchase in a string form like the following

An example of how to use a purchase

Purchase purchase = new Purchase("milk", 4, 2);
System.out.println( "the total price of a purchase containing four milks is " + purchase.price() );
System.out.println( purchase );
purchase.increaseAmount();
System.out.println( purchase );

Prints:

the total price of a purchase containing four milks is 8
milk: 4
milk: 5

Note: toString follows the pattern product: amount, but the price is not reported!

Exercise interfaces-3.5: Shopping Basket

Finally, we can implement our shopping basket class!

The shopping basket stores its products as Purchase objects. The shopping basket has to have an object variable whose type is either Map<String, Purchase> or List<Purchase>. Do not create any other object variable for your shopping basket in addition to the Map or List needed to store purchases.

Attention: if you store a Purchase object in a Map helping variable, it will be useful to use the Map method values() in this and in the following exercise; with it, it’s easy to go through all the stored Purchase objects.

Let’s create a constructor without parameter for our shopping basket, as well as the following methods:

  • public void add(String product, int price) adds a purchase to the shopping basket; the purchase is defined by the parameter product, and its price is the second parameter.
  • public int price() returns the shopping basket total price

Example code of using basket

ShoppingBasket basket = new ShoppingBasket();
basket.add("milk", 3);
basket.add("buttermilk", 2);
basket.add("cheese", 5);
System.out.println("basket price: " + basket.price());
basket.add("computer", 899);
System.out.println("basket price: " + basket.price());

Prints:

basket price: 10
basket price: 909

Exercise interfaces-3.6: Printing out the Shopping Basket

Let’s create the method public void print() for our shopping basket. This prints out the Purchase objects which are contained by the basket. The printing order is not important. The output of the shopping basket in the previous example would be:

butter: 1
cheese: 1
computer: 1
milk: 1

Note that the number stands for the unit amount of the products, not for their price!

Exercise interfaces-3.7: Only One Purchase Object for One Product

Let’s update our Shopping Basket; if the basket already contains the product which we add, we don’t create a new Purchase object, but we update the Purchase object corresponding to the existing product by calling its method increaseAmount().

Example:

ShoppingBasket basket = new ShoppingBasket();
basket.add("milk", 3);
basket.print();
System.out.println("basket price: " + basket.price() +"\n");

basket.add("buttermilk", 2);
basket.print();
System.out.println("basket price: " + basket.price() +"\n");

basket.add("milk", 3);
basket.print();
System.out.println("basket price: " + basket.price() +"\n");

basket.add("milk", 3);
basket.print();
System.out.println("basket price: " + basket.price() +"\n");

Prints:

milk: 1
basket price: 3

buttermilk: 1
milk: 1
basket price: 5

buttermilk: 1
milk: 2
basket price: 8

buttermilk: 1
milk: 3
basket price: 11

This means that first, we add milk and buttermilk, creating new Purchase objects for them. When we add more milk to the basket, we don’t create a new purchase object for the milk, but we update the unit amount of the purchase object representing the milk we already have in the basket.

Exercise interfaces-3.8: Shop

Now, all the parts of our online shop are ready. Our online shop has a storage house, which contains all products. We have got a shopping basket to manage all our customers. Whenever a customer chooses a purchase, we add it to the shopping basket if the product is available in our storage house. Meanwhile, the storage stocks are reduced by one.

Below, you find a ready-made code body for your online shop. Create the class Shop to your project, and copy the code below into it.

import java.util.Scanner;

public class Shop {

   private Storehouse store;
   private Scanner reader;

   public Shop(Storehouse store, Scanner reader) {
       this.store = store;
       this.reader = reader;
   }

   // the method to deal with a customer in the shop
   public void manage(String customer) {
       ShoppingBasket basket = new ShoppingBasket();
       System.out.println("Welcome to our shop " + customer);
       System.out.println("below is our sale offer:");

       for (String product : store.products()) {
           System.out.println( product );
       }

       boolean running = true;
       while (running) {
           System.out.print("what do you want to buy (press enter to pay):");
           String product = reader.nextLine();
           if (product.isEmpty()) {
               break;
           }

           // here, you write the code to add a product to the shopping basket, if the storehouse is not empty
           // and decreases the storehouse stocks
           // do not touch the rest of the code!

       }

       System.out.println("your purchases are:");
       basket.print();
       System.out.println("basket price: " + basket.price());
   }
}

The following main program fills the shop storehouse and manages the customer Pekka:

Storehouse store = new Storehouse();
store.addProduct("coffee", 5, 10);
store.addProduct("milk", 3, 20);
store.addProduct("milkbutter", 2, 55);
store.addProduct("bread", 7, 8);

Shop shop = new Shop(store, new Scanner(System.in));
shop.manage("Pekka");

The shop is almost ready. There are comments in the method public void manage(String customer), showing the part that you should implement. In that point, implement the code to check whether the object the customer wants is available in the storehouse. If so, reduce the storehouse stocks by one unit, and add the product to the shopping basket.

Now you have done something! verkkokauppa.com!