Software Development

Most newbies confuse coding with programming. As soon as an idea for a program comes to mind, they think in terms of coding it.

Coding is a very small part of programming. Programming is building a software engine. It starts from an idea, designed and documented like a blueprint, then implemented in a programming language.

Before a motor car is built, it is first designed. Each and every part is identified, and its function documented. The connections between the parts and the sequence of operations are shown as drawings. All the parts are then assembled into a single unit. It is then that fuel is taken in and the battery charged, before it is launched on a road with ignition and acceleration.

The development of software has long ago moved out of the studios of coding artists to the factories of practising engineers. The rigor of the engineering discipline has transformed software development from an art form to engineering technology. 

The initial idea for a software program is elaborated in a document called Requirement Specification (RS). This document expresses the idea in a formal structured manner and outlines the scope to limit it to a precisely known functionally.

The functionality is furthered elaborated in a document called Functional Specification (FS). This document defines the users of the software program, their roles and the details of the operations (a.k.a. functions) they are going to perform.

The components of the software system under development are identified and expressed as object and data models in a document called Detailed Design (DD). This document lays out the framework for development. It helps produce the Application Programming Interface (API) that glues all the components into a single functional entity.

It is now time for coding the software application. This phase of the software project is called the implementation. It is what brings the framework to life. It is like the fuel for the car.

Testing is finally performed to ensure that all functional interfaces as outlined in FS are implemented and working as specified. Test cases are written prior to testing so that no tests are ignored or overlooked. 

Advertisements

StringBuffer, StringBuilder

StringBuffer

A buffered representation of a string of characters.

For mutable strings, StringBuffer is more efficient, in terms of performance and memory management.

It is thread-safe. It has several utility methods to manipulate strings.

Where data is not shared among multiple threads, StringBuilder is used. It is modelled in exactly the same way; one can be replaced by the other.

Constructors
– Default:
StringBuffer buffer = new StringBuffer();
– With initial buffer capacity in bytes:
StringBuffer buffer = new StringBuffer(512);
– With literal string:
StringBuffer buffer = new StringBuffer(“a string of characters”);
– With a String object:
String text = “hello, java”;
StringBuffer buffer = new StringBuffer(text);

Methods
append:
adds text to the end of the buffer (eturns the buffer so that the method can be chained)
StringBuffer buffer = new StringBuffer();
buffer.append(“hello”).append(” java,”).append(123);

The append method is overloaded too; it can accept any kind of primitive data type and objects for which toString() has been defined.

insert: like append, it is overloaded; inserts any primitive type at specified​ location

replace(int startChar, int endChar, String chars)

Example

public class BufferDemo
{
	public static void main (String[] args) {
		StringBuffer buffer = new StringBuffer("gable");
buffer.insert(2,'m');
buffer.append("r");
String output = buffer.toString();   // contains gambler

System.out.println(output);
buffer.replace(0,1, "scr");
output = buffer.toString();   // contains scrambler
	
	System.out.println(output);
	}
		
}

StringBuffer is converted back to String using the toString() method.

Annotations

Annotations provide actionable information to the compiler, the runtime engine and other processing tools.

They are not part of the program in the sense that they do not modify the code at all.

What annotations can do

  • provide additional information to the compiler to detect errors or suppress warnings
  • provide information to the tools to generate code
  • provide processing information to the runtime, like to plug in objects

For example, the derived class may override a method in the base class. If the method is decorated with the annotation @Override, the compiler can check for errors in the method signature.

@SuppressWarnings may be used to, well, suppress compiler warnings.

@Deprecated is used to inform the user that the method should no longer be used since it has been superceded.

Java frameworks such as Spring may load classes at runtime based on the annotations​ specified.

A class, a method or a variable may be decorated with one or more annotations.

Annotations can be easily identified by the symbol @ followed by the name of the entity.

Annotations contain elements that may be declared with or without default values.

An example

Annotations are defined much like interfaces.

@interface Signature {
    String director();
    int version() default 1;
    String movie() default "...";
    String[] actors()
}

Usage:


@Signature (
    director = "anand",
    version = 1.1,
    movie = "homies"
    actors = {"Akhil","Nikhil",")
public class HomeProductions {

}

Annotations can be annotated by other annotations.

For example, the above annotation may be added to javadoc-generated documentation by specifying @Documented in its definition.

Using @Retention, it can also be specified whether the annotation is meant to be used at the source level, at compile time or the runtime.

@Target specifies whether the annotation is to be applied for the class, the method or the variable.

Annotations can be inherited from other annotations, using @Inherited.

@Repeatable allows the annotation to be applied to the same entity more than once.

Re-writing the annotation definition above:

@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@interface Signature {
    String director();
    int version() default 1;
    String movie() default "...";
    String[] actors()
}

Map

java.util.Map

Map represents a dictionary in which a pair of objects are stored.

It is a kind of dynamically growing associative array that stores key value pairs: note the (K,V) generic type parameters.

Map is an interface. Some implementations are:
– HashMap
– Hashtable
– Properties
– TreeMap
– LinkedHashMap

HashMap orders elements according to their hashCode. If sorted order is required, TreeMap can be used.

An example

// Song.java
package album;
import java.util.*;

public class Song
{
	private String incipit;
	private String lyricist;
	private String movie;
	private int year;

	public Song(String incipit, String lyricist, String movie, int year)
	{
		this.incipit = incipit;
		this.lyricist = lyricist;
		this.movie = movie;
		this.year = year;
	}

	public void setIncipit(String incipit)
	{
		this.incipit = incipit;
	}

	public String getIncipit()
	{
		return incipit;
	}

	public void setLyricist(String lyricist)
	{
		this.lyricist = lyricist;
	}

	public String getLyricist()
	{
		return lyricist;
	}

	public void setMovie(String movie)
	{
		this.movie = movie;
	}

	public String getMovie()
	{
		return movie;
	}

	public void setYear(int year)
	{
		this.year = year;
	}

	public int getYear()
	{
		return year;
	}

	@Override
	public String toString()
	{
		return incipit+":"+lyricist+":"+movie+":"+year;
	}
	
}

// SongAlbum.java
package album;
import java.util.*;

public class SongAlbum
{
	private Map<String,Song> album = new HashMap<>();
	
	public void add(String director, Song song) {
		album.put(director,song);
	}
	
	public void showAllSongs() {
		Set<Map.Entry<String,Song>> entrySet = album.entrySet();
		Iterator<Map.Entry<String,Song>> entries = entrySet.iterator();
		while(entries.hasNext()) {
			Map.Entry<String,Song> entry = entries.next();
			String doc = entry.getKey();
			Song song = entry.getValue();
			System.out.println(doc+"->"+song);
		}
		
	}
	
	public void showSongsByYear() {
		Set<String> directors = album.keySet();
		ArrayList<Song> songList = new ArrayList<>();
		Iterator<String> keys = directors.iterator();
		while(keys.hasNext()) {
			Song song = album.get(keys.next());
			songList.add(song);
		}
		Collections.sort(songList,new YearSort());
		for(Song song : songList) {
			System.out.println(song);
		}
	}
	
	public static void main(String[] args) {
		Song hawa = new Song("Hawa Mein Udta Jaye / हवा में उड़ता जाए मोरा लाल दुपट्टा", "Jalal Malihabadi", "Barsaat", 1949);
		Song chori = new Song("Chori Chori Chupke Chupke / चोरी चोरी चुपके चुपके", "Anand Bakshi", "aap ki kasam", 1974);
		Song pyar = new Song("Pyaar Hume Kis Mod Pe Le Aaya / प्यार हमे किस मोडपे ले आया", "Gulshan Bawra", "Satte Pe Satta",1982);
		Song andaz = new Song("	Jhoom Jhoom Ke Nacho Aaj", "Majrooh Sultanpuri", "Andaz", 1949);
		SongAlbum alb = new SongAlbum();
		
		alb.add("Rahul Dev Burman", pyar);
		alb.add("Rahul Dev Burman", chori);
		alb.add("Naushad", andaz);
		alb.add("Shankar Jaikishan", hawa);
		
		alb.showAllSongs();
		System.out.println();
		alb.showSongsByYear();
	}
}

// YearSort.java
package album;

import java.util.Comparator;

public class YearSort implements Comparator<Song>
{
	@Override
	public int compare(Song p1, Song p2)
	{
		return p1.getYear() - p2.getYear();
	}

}

Note
– No duplicates
– Ordering of entries using the comparator
– Using the Map.Entry inner class of HashMap to iterate

Set

java.util.Set extends Collection, Iterable

Set is an interface and modelled after the mathematical set. Nulls may be prohibited. Duplicates​ are not allowed.

Primary implementation classes are TreeSet and HashSet.

TreeSet stores elements in sorted order. HashSet does not.

Because of ordering, the TreeSet may run slower than the HashSet.

Between the ordering of the elements and the efficiency of access, a trade-off maybe made depending on the requirement.

Adding null to TreeSet throws NullPointerException. Adding null to HashSet does not.

Methods
A few methods are shown below:

  • Object[] toArray()
  • boolean contains(Object o)
  • boolean addAll(Collection c)

Note
boolean equals(Object o) and hashCode() are related. As per the specification, if equals returns true, then the hashCode must also be the same for both the comparing objects.

An example
Point of sale
– Product, with equals and hashCode override
– Add to cart, prevents duplicates

// Product.java
package pos;
import java.math.*;
import java.util.*;

public class Product
{
	private String id;
	private String name;
	private BigDecimal price;
	private int quantity;
	
	public Product(String name, int quantity, double price) {
		this.name = name;
		this.quantity = quantity;
		this.price = BigDecimal.valueOf(price);
		this.id = uid();
	}

	@Override
	public boolean equals(Object obj)
	{
		return this.id.equals(((Product)obj).getId());
	}

	@Override
	public int hashCode()
	{
		return id.hashCode();
	}
	
	private String uid() {
		// String lUUID = String.format("%040d", new BigInteger(UUID.randomUUID().toString().replace("-", ""), 16));
		UUID uid = UUID.randomUUID();
		String uidStr = uid.toString();
		// remove hiphens
		uidStr = uidStr.replace("-", "");
		BigInteger bigI = new BigInteger(uidStr, 16);
		String uids = String.format("%040d", bigI);
		return uids;
	}

	@Override
	public String toString()
	{
		return id;
	}

	
	public void setId(String id)
	{
		this.id = id;
	}

	public String getId()
	{
		return id;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public String getName()
	{
		return name;
	}

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

	public BigDecimal getPrice()
	{
		return price;
	}

	public void setQuantity(int quantity)
	{
		this.quantity = quantity;
	}

	public int getQuantity()
	{
		return quantity;
	}
}

// Cart.java
package pos;
import java.util.*;

public class Cart
{
	private static Set<Product> items = new HashSet<>();
	private static void showItems() {
		Iterator<Product> it = items.iterator();
		while(it.hasNext()) {
			System.out.println(it.next());
		}
	}
	public static void main(String[] args) {
		Product router = new Product("D-Link",25, 4565.0);
		Product cable = new Product("Ethernet",32,45.75);
		Product hub = new Product("D-Link",25, 4565.0);
		items.add(router);
		items.add(cable);
		items.add(hub);
		items.add(router);
		showItems();
	}
}

Exercise
Rewrite the random numbers example from the Arrays post using the Set.

Generics

Objects may be restricted to using a single type with type parameters.

Type parameters are declared using the angle brackets, immediately following the object declaration.

Generics help code a single class that can be used for several types, instead of defining different classes for different types.

Benefits
– Good for maintenance
– less prone to errors
– compile time type safety
– works for subtypes too

Caution
Never use the Object class as a type parameter. It doesn’t work for Object super type.

A complete example

Here’s an example of a class that is modelled after the array data structure in Java.

import java.util.*;

public class Array<T> implements Iterable
{
	int size;
	int count = 0;
	Object[] o;
	int next = 0;
	private Array() {}
	
	public Array(int size) {
		this.size = size;
		o = new Object[size];
	}

	public void add(T t) {
		if(count < size) {
			o[count++] = t;
		}
	}
	
	public T get(int index) {
		if(index < size && index >= 0) {
			return (T)o[index];
		}
		return null;
	}
	
	public int size() {
		return size;
	}
	
	public Iterator iterator() {
		// reset
		next = 0;
		return new ArrayIterator();
	}
	
	class ArrayIterator implements Iterator
	{
		@Override
		public boolean hasNext()
		{
			return (next < size);
		}

		@Override
		public Object next()
		{
			return get(next++);
		}

		@Override
		public void remove()
		{
			// TODO: Implement this method
		}
	}
	
	public static void main(String[] args) {
		Array<Integer> array =new Array<Integer>(5);
		array.add(5);
		array.add(17);
		array.add(54);
		array.add(37);
		array.add(33);
		array.add(66);
		
		Array.ArrayIterator it = array.new ArrayIterator();
		while(it.hasNext()) {
			System.out.println(it.next());
		}
		
		System.out.println("---------");
		
		Array<String> strings = new Array<String>(3);
		strings.add("Sarita");
		strings.add("Akash");
		strings.add("Romeo");
		
		Iterator iterator = strings.iterator();
		while(iterator.hasNext()) {
			System.out.println(iterator.next());
		}
		
		System.out.println("---------");
		
		for(int i=0; i<strings.size(); i++) {
			System.out.println(strings.get(i));
		}
		
		System.out.println("---------");
		
		it = strings.new ArrayIterator();
		while(it.hasNext()) {
			System.out.println(it.next());
		}
		
	}
	
}

List

An interface from the collection classes.

Any non-trivial program needs a dynamically growing collection for storing objects created at runtime.

The java.util package provides several classes to suit different programming requirements.

The Iterator provides the methods to loop through the List, like a for loop. The difference here is that the underlying data structure is not accessed directly.

ArrayList

It extends AbstractList and implements Collection, List and Iterable interfaces.

The ArrayList is an implementation of a dynamically growing array.

The ArrayList stores its elements in insertion order.

Like an array, the index is zero-based, and its size re-adjusts whenever elements are added or removed.

It stores only objects, so primitives will be auto-boxed for storing.

It is possible to add any kind of object into an array, but while reading back the elements, they need to be cast to the correct object, otherwise a ClassCastException will be thrown.

ArrayList list = new ArrayList();
list.add("Xander Cage");
list.add("Toretto");
int size = list.size();
for(int i=0; i<size; i++) {
	String str = (String)list.get(i);	// cast required, from Object to String
}

Parameterised Types a.k.a Generics

To prevent class cast exceptions, Java allows collection classes to store only parameterised types of objects. It also provides compile-time type checking.

More on Generics in a later post.

An example

// Product.java
package ecom;

public class Product
{
	private String name;
	private int quantity;
	private double price;

	public Product(String name, int quantity, double price)
	{
		this.name = name;
		this.quantity = quantity;
		this.price = price;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public String getName()
	{
		return name;
	}

	public void setQuantity(int quantity)
	{
		this.quantity = quantity;
	}

	public int getQuantity()
	{
		return quantity;
	}

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

	public double getPrice()
	{
		return price;
	}

	@Override
	public String toString()
	{
		return name+":"+quantity+":"+price;
	}
}

// Catalogue.java
package ecom;
import java.util.*;

public class Catalogue
{
	private ArrayList<Product> productList;
	
	public Catalogue() {
		productList = new ArrayList<Product>();
	}
	
	public void add(Product product) {
		productList.add(product);
	}
	
	public void showProducts() {
		Iterator<Product> iter = productList.iterator();
		while(iter.hasNext()) {
			// no cast necessary
			Product P = iter.next();
			System.out.println(P);
		}
	}
	
	public static void main(String[] args) {
		Product shoes = new Product("Adidas",200,1200);
		Product skirts = new Product("Neeru's",50,300);
		Catalogue shelf = new Catalogue();
		shelf.add(shoes);
		shelf.add(skirts);
		shelf.showProducts();
	}
}

LinkedList
Implemented as a double-linked list, LinkedList doesn’t allow insertion​ and removal of elements at arbitrary positions.

Traversal can begin from any node and navigate towards the beginning or the end of the list.

It is used when applications perform frequent insertions and deletions from a list, and where contiguous memory locations are not a requirement.