What will you build

You will build some small applications that will demonstrate the different usage of some of the data structure of Java. For these example, we will use the class Tea that have been defined in What is encapsulation. We will also implemented some new Classes.

What You'll Learn

What will you need

You say already the arrays (one dimension and multi-dimensional), but many data structure exist and have been already implemented in Java. We will some of the principle data structure.

Java, offers the collections framework that will provide Interfaces, concrete and abstract Classes and methods to manage a set of Objects. This API Collections provide proposes 4 big family of collections:

You should look at the tutorial of Java about Collections.

We will see some of the concrete implementation of this API in this codelabs.

In contrary of array, these data structure are provided with more advance methods and can change size dynamically.

You should have a look at the collection Framework documentation

To have a look at how an ArrayList is implemented

An vector is a dynamically re-sizeable array of objects (it can grow or shrink). Vector is part of the AbstractList class. It is very useful when you need to add or remove items after the Vector has been created.

The vector tries to optimize storage management with the capacity and the capacityIncrement. The capacity is always at least as large as the number of elements in the vector. It is increasing in chunks, and can be defined with the capacityIncrement, but by default, the vector will double everytime that you add an element, and the vector did reach the maximum capacity.

Vector has several methods that you can use to add, remove, etc.

We will use the Tea class as a based class for this example, and implement new methods in it. The class Tea had a small modification, I added a new constructor that is taking two arguments: the type of tea and the price.

public class Tea{

    private String typeTea;
    private double price;

    public Tea(){
        typeTea = "darjeeling";
        price = 0.10;
    }

    public Tea(String typeTea){
        this.typeTea = typeTea;
    }
    public Tea(String typeTea, double price){
        this.typeTea = typeTea;
        this.price=price;
    }

    public void setPrice(double price){
        this.price = price;
    }

    public double getPrice(){
        return price;
    }

    public String getTypeTea(){
        return typeTea;
    }
}

We will implement a small application that will be able to manage several type of tea, display the price of the tea and the type of the tea. For this, we will implement a class Store.

This class store has a name of the store, an address and a vector of Tea. Of course, in the real life, this class will be more complex.

To use a Vector, it needs to be imported. To import, you need to had, before the declaration of the class the import keyword and the package you want to import.

This class has several methods to display the vector, it is only to show how different ways of displaying a vector. These methods also show the StringBuffer class which allows to append String together, and it shows how to override the method toString that is available for all object.

import java.util.Vector;

public class Store {
    private String nameOfTheStore;
    private String addressStore;
    private Vector <Tea> teas = new Vector<>();

    public Store (String nameOfTheStore, String addressStore) {
        this.nameOfTheStore = nameOfTheStore;
        this.addressStore = addressStore;
    }

    public String getNameOfTheStore () {
        return nameOfTheStore;
    }

    public String getAddressStore () {
        return addressStore;
    }

    public Vector<Tea> getTeas () {
        return teas;
    }

    public void addTea(Tea tea){
        teas.add(tea);
    }

    public String printTeaInStore(){

        StringBuffer sb = new StringBuffer();
        for(Tea t: teas){
            sb.append("The name of the tea is "+t.getTypeTea());
            sb.append(", the price of this tea is "+t.getPrice());
            sb.append("\n");
        }
        return sb.toString();
    }

    @Override
    public String toString () {
        return "Store{" +
                "nameOfTheStore='" + nameOfTheStore + '\'' +
                ", addressStore='" + addressStore + '\'' +
                ", teas=" + printTeaInStore() +
                '}';
    }
}

A small class to test the Store class.

public class Main {

    public static void main(String[] args) {
	    Store storeCardiff = new Store("Cardiff", "Cardiff Queen");
	    storeCardiff.addTea(new Tea("English Breakfast", 10.0));
	    storeCardiff.addTea(new Tea("Chai Tea",5.90));
	    storeCardiff.addTea(new Tea("Assam",20.50));
		System.out.println(storeCardiff.toString());
		System.out.println("\n print with Iterator Forward");
		storeCardiff.printWithIteratorForward();
		storeCardiff.removeATea("Breakfast");
		System.out.println("list of tea after remove the breafast");
		storeCardiff.printWithIteratorForward();
    }
}

Vector documentation

Until now, to iterate through a collection of object stored in a data structure we used the for and the for each. Another way of iterating through a collection of object is to use an Iterator, or a ListIterator.

In the following example, we will use ListIterator to remove a Tea element from the Store. List Iterator or Iterator needs to be imported in the beginning of the class (import java.util.ListIterator;).

 public void removeATea(String teaName){
        ListIterator<Tea> teaListIterator = this.getTeas().listIterator(); //declare, instantialise and initialise a list Iterator with the Vector teas.
        while (teaListIterator.hasNext()) { //iterate through the listIterator as long as it's return true
            if(teaListIterator.next().getTypeTea().endsWith(teaName)){ //remove an element that will end with the name that is given in the argument of this method.
                teaListIterator.remove();
            }

        }
    }

Documentation for ListIterator

An arrayList is almost the same than a Vector. However, the capacity increment is different, the vector is Synchronous and you have some minor difference in the API.

More and more developper are using ArrayLists instead of Vector.

For the example, we will create a class Customer, and add a ArrayList of Customer in the Store that will store all the customers of the Store.

The class customer will have three instance variable: a name for the customer of type String, an address of type String, and a Date of Birth of type Date. The type Date needs to be imported. The class SimpleDateFormat needs also to be imported and has several methods that could be used to parse a String to a Date. I am implementing one of these, for the other one, you can look at the documentation.

The parsing of the date with SimpleDateFormat will raise an exception if the result is not correct, you will need to import the class of Exception it is raising and to add it in the signature of the method using the method parse().

We will see more in details the exception in the codelabs Exception.

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Customer {
    private String name;
    private String address;
    private Date dob;

    public Customer (String name, String address, String dob) throws ParseException {
        this.name = name;
        this.address = address;
        this.dob = new SimpleDateFormat("dd/MM/yyyy").parse(dob);
    }

    public String getName () {
        return name;
    }

    public String getAddress () {
        return address;
    }

    public Date getDob () {
        return dob;
    }
    public String getDobString(){
        return dob.toString();
    }
}

We will use this class in the StoreWithCustomer class, where we will be able to add customer in an ArrayList. I added the ArrayList customer to the class and I implemented a method that will display the customers.

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

public class StoreWithCustomer {
    private String nameOfTheStore;
    private String addressStore;
    private Vector<Tea> teas = new Vector<>();
    private List<Customer> customers = new ArrayList<>();

    public StoreWithCustomer (String nameOfTheStore, String addressStore) {
        this.nameOfTheStore = nameOfTheStore;
        this.addressStore = addressStore;
    }

    public String getNameOfTheStore () {
        return nameOfTheStore;
    }

    public String getAddressStore () {
        return addressStore;
    }

    public Vector<Tea> getTeas () {
        return teas;
    }

    public void addTea (Tea tea) {
        teas.add(tea);
    }

    public void addCustomer(Customer c){
        this.customers.add(c);
    }

    public String printTeaInStore () {

        StringBuffer sb = new StringBuffer();
        for (Tea t : teas) {
            sb.append("The name of the tea is " + t.getTypeTea());
            sb.append(", the price of this tea is " + t.getPrice());
            sb.append("\n");
        }
        return sb.toString();
    }

    private String printCustomer () {
        StringBuffer sb = new StringBuffer();
        for (Customer cu: customers){
            sb.append("The name of the customer "+cu.getName()).append(" the dob "+cu.getDobString()).append("\n");
        }
        return sb.toString();
    }

    @Override
    public String toString () {
        return "StoreWithCustomer{ \n" +
                "name Of The Store='" + nameOfTheStore + '\'' +
                ", address Store='" + addressStore + "\n" +
                this.printTeaInStore() +
                this.printCustomer() +
                '}';
    }


}

As usual you can test these two classes in a test Class. We will use the same than for the Vector, with a new StoreWithCustomer instance to test the new class and methods.

Again, we will need to import the ParseException and add it to the signature of the main method.

import java.text.ParseException;

public class Main {

    public static void main(String[] args) throws ParseException {
	    Store storeCardiff = new Store("Cardiff", "Cardiff Queen");
	    storeCardiff.addTea(new Tea("English Breakfast", 10.0));
	    storeCardiff.addTea(new Tea("Chai Tea",5.90));
	    storeCardiff.addTea(new Tea("Assam",20.50));
		System.out.println(storeCardiff.toString());
		System.out.println("\n print with Iterator Forward");
		storeCardiff.printWithIteratorForward();
		storeCardiff.removeATea("Breakfast");
		System.out.println("list of tea after remove the breafast");
		storeCardiff.printWithIteratorForward();


		StoreWithCustomer swc = new StoreWithCustomer("Bristol","Bristol Street");
		swc.addCustomer(new Customer("Helene","Bristol","20/03/1980"));
		swc.addCustomer(new Customer("Carl","Bristol channel","20/03/1980"));
		swc.addTea(new Tea("English Breakfast", 15.0));
		System.out.println(swc.toString());

    }
}

Documentation for SimpleDateFormatDocumentation for DateDocumentation for ArrayList

A Set is an unordered collection of data that will have NO duplicates. In ArrayList and Vector, the order will be kept for you (as long as you didn't change it) and you can have as many duplicate as you want. In a Set, you cannot have any duplicate. by consequence, if an element is already in the Set, it will be ignore.

In Java, you have two different data structures that are managing the Set: hash tables and trees. You have different type of Set such as the TreeSet, the HashSet and the NavigableSet. I will not enter into the details of the implementation and in the details of all the different sets, however, I strongly suggest you to look at the documentation of these Sets.

Documentation for the Set Interface

To demonstrate the Set, we will add a new information, the name of the employee. In this shop, no employee can be named the same named. We will use a Set to avoid this. Again, in a real life application, this could be a very bad analyse of the requirement.

To use the class TreeSet, we will need to import it. the class TreeSet is naturally ordered as you will be able to see when you will run the code below.

import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;

public class StoreWithSet {
    private String nameOfTheStore;
    private String addressStore;
    private List<Tea> teas = new ArrayList<>();
    private TreeSet<String> namesEmployee = new TreeSet<>();

    public StoreWithSet (String nameOfTheStore, String addressStore) {
        this.nameOfTheStore = nameOfTheStore;
        this.addressStore = addressStore;
    }

    public String getNameOfTheStore () {
        return nameOfTheStore;
    }

    public String getAddressStore () {
        return addressStore;
    }

    public List<Tea> getTeas () {
        return teas;
    }

    public void addTea(Tea tea){
        teas.add(tea);
    }
    public void addEmployee(String name){
        namesEmployee.add(name);
    }

    public String printTeaInStore(){

        StringBuffer sb = new StringBuffer();
        for(Tea t: teas){
            sb.append("The name of the tea is "+t.getTypeTea());
            sb.append(", the price of this tea is "+t.getPrice());
            sb.append("\n");
        }
        return sb.toString();
    }


    @Override
    public String toString () {
        return "StoreWithSet{" +
                "nameOfTheStore='" + nameOfTheStore +
                ", addressStore='" + addressStore + "\n" +
                this.printTeaInStore() +
                "namesEmployee=" + namesEmployee.toString() +
                "\n" +"}";
    }
}

I will add a new class in the test Class, to test this new Class.

public class Main {

    public static void main(String[] args) throws ParseException {
	   
		StoreWithSet sws = new StoreWithSet("Newport","Main Street");
		sws.addTea(new Tea("English Breakfast",10.0));
		sws.addEmployee("Helene");
		sws.addEmployee("Anna");
		sws.addEmployee("Helene");
		System.out.println(sws.toString());
    }
}

When you will print the result of this test on the console, you will be able to constat to things.

First, the second employee Helene have been ignore, and you will have only two employees.

Second, is that I did enter Helene, Anna, and if I did used an HashSet, I will not be sure in which order it will be display. However, as I am using a TreeSet, the JVM order it in a natural order, and it will display Anna, Helene.

Documentation for Set

An object on type Map contains a key and a value(s), a map cannot contains duplicate keys. Each key need to be unique.

As for the Set, you have different type of Map, look at the documentation to see the differences.

Map is a very useful data structure when you need to keep two values together that are linked.

A small application where a map is composed with a variable of type String and a Variable of type Integer. For this little demo we will use the TreeMap, however, you should look at the documentation to see the difference between the different type of Map implemented in Java.

As for the other data structure, you will need first to import the class Map and TreeMap.

In the first little method, I am only implementing a method that will return a TreeMap composed of a String and Integer.

To declare the type of variable we want to use, we have to use the wrapper class of int which is Integer. We cannot declare a TreeMap, you need to declare it using the classes.

As you can notice, I am having as a String value 2 times Helene, twice starting with an Upper Case, one starting with an lower case. As Java is a case sensitive language programming, and in this case it is a String, I will have two different keys, one Helene, one helene, however, the second Helene will be ignore.

My method is returning a TreeMap.

I did implement a read Map as a method. You have several way of iterating through a Map, this is one of the simplest one (other than using Lambda). We are converting the map in a set of entries and iterating through them using the standard for each. We will have access to the key using the method getKey() and to the value with the method getValue().

import java.util.TreeMap;
import java.util.Map;

public class DemoMap {

    public TreeMap<String,Integer> demoMap(){
        TreeMap<String,Integer> nameAge = new TreeMap<>();
        nameAge.put("helene",100);
        nameAge.put("Anna",1000);
        nameAge.put("Helene",100);
        nameAge.put("Carl",2000);
        nameAge.put("Helene",200);
        return nameAge;
    }
    
    public void readMap(TreeMap<String,Integer> maps){
        for(Map.Entry<String,Integer> m: maps.entrySet()){
            System.out.print("key "+m.getKey());
            System.out.println(", value "+m.getValue());
        }
    }
    
    public static void main (String[] args) {
        DemoMap dm = new DemoMap();
        TreeMap<String, Integer> results = dm.demoMap();
        System.out.println("The read map method");
        dm.readMap(results);
    }
}

Documentation for the Map)

LinkedList is data structure used for collecting a sequence of objects. LinkedList are very useful when the order in the List is important, and some news entry will be added in specific places. It is very costly for an Array to perform this operation, however, it is not the case for a LinkedList. LinkedList are very useful when you know that you will display the elements in a sequential order, and you want to insert and remove elements efficiently.

Doubly-Linked List implements the List and Deque interface.

import java.util.LinkedList;

public class DemoLinkedList {

    private LinkedList<String> employees = new LinkedList<>();

    public void addEmployee(String name){
        employees.add(name);
    }

    public LinkedList<String> getEmployees () {
        return employees;
    }
    
    public String removeAEmployee(){
        String pop = employees.pop();
        return pop;
    }

    @Override
    public String toString () {
        return getEmployees().toString();
    }
    
    public static void main (String[] args) {
        DemoLinkedList dll = new DemoLinkedList();
        dll.addEmployee("helene");
        dll.addEmployee("linda");
        dll.addEmployee("titi");
        System.out.println(dll.toString());
        System.out.println(dll.getEmployees().size());
        for(int i = 0; i<=dll.getEmployees().size();i++) {
            String removed = dll.removeAEmployee();
            System.out.println("employee removed "+removed);
        }
        System.out.println(dll.toString());
    }
}

As the LinkedList is implementing the interface Deque, we have access to interesting methods from this interface, such as pop. In this example, I am implementing a small method that remove the employee of the LinkedList using pop().

Documentation for the LinkedList

You can find the code for this example in https://git.cardiff.ac.uk/ASE_GROUP_2020/code_for_codelabs.git