/* This is wrapper class...
 Objective would be to push more functionality into this Class to enforce consistent definition
 */
public abstract class Collectable implements Comparable <Collectable> {
	public final String masterType = "Collectable";
	private String type;	// extender should define their data type

	// enumerated interface
	public interface KeyTypes {
		String name();
	}
	protected abstract KeyTypes getKey();  	// this method helps force usage of KeyTypes

	// getter
	public String getMasterType() {
		return masterType;
	}

	// getter
	public String getType() {
		return type;
	}

	// setter
	public void setType(String type) {
		this.type = type;
	}
	
	// this method is used to establish key order
	public abstract String toString();

	// this method is used to compare toString of objects
	public int compareTo(Collectable obj) {
		return this.toString().compareTo(obj.toString());
	}

	// static print method used by extended classes
	public static void print(Collectable[] objs) {
		// print 'Object' properties
		System.out.println(objs.getClass() + " " + objs.length);

		// print 'Collectable' properties
		if (objs.length > 0) {
			Collectable obj = objs[0];	// Look at properties of 1st element
			System.out.println(
					obj.getMasterType() + ": " + 
					obj.getType() +
					" listed by " +
					obj.getKey());
		}

		// print "Collectable: Objects'
		for(Object o : objs)	// observe that type is Opaque
			System.out.println(o);

		System.out.println();
	}
}
/*
 * Animal class extends Collectable and defines abstract methods
 */
public class Animal extends Collectable {
	// Class data
	public static KeyTypes key = KeyType.title;  // static initializer
	public static void setOrder(KeyTypes key) { Animal.key = key; }
	public enum KeyType implements KeyTypes {title, name, age, color}

	// Instance data
	private final String name;
	private final int age;
	private final String color;

	/* constructor
	 *
	 */
	public Animal(String name, int age, String color)
	{
		super.setType("Animal");
		this.name = name;
		this.age = age;
		this.color = color;
	}

	/* 'Collectable' requires getKey to help enforce KeyTypes usage */
	@Override
	protected KeyTypes getKey() { return Animal.key; }

	/* Getters
	 * 
	 */
	public String getName() { return this.name; }
	public int getAge() { return this.age; }
	public String getColor() { return this.color; }

	
	/* 'Collectable' requires toString override
	 * toString provides data based off of Static Key setting
	 */
	@Override
	public String toString()
	{
		String output="";
		if (KeyType.name.equals(this.getKey())) {
			output += this.name;
		} else if (KeyType.age.equals(this.getKey())) {
			output += "00" + this.age;
			output = output.substring(output.length() - 2);
		} else if (KeyType.color.equals(this.getKey())) {
			output += this.color;
		} else {
			output += super.getType() + ": " + this.name + ", " + this.color + ", " + this.age;
		}
		return output;
		
	}

	// Test data initializer
	public static Animal[] animals() {
		return new Animal[]{
				new Animal("Lion", 8, "Gold"),
				new Animal("Pig", 3, "Pink"),
				new Animal("Robin", 7, "Red"),
				new Animal("Cat", 10, "Black"),
				new Animal("Kitty", 1, "Calico"),
				new Animal("Dog", 14, "Brown")
		};
	}
	
	/* main to test Animal class
	 * 
	 */
	public static void main(String[] args)
	{
		// Inheritance Hierarchy
		Animal[] objs = animals();

		// print with title
		Animal.setOrder(KeyType.title);
		Animal.print(objs);

		// convert to Coolection and sort in name order
		Animal.setOrder(KeyType.name);
		List<Animal> animals = new ArrayList<Animal>(Arrays.asList(objs));  // Array has asList conversion
		Collections.sort(animals);
		Animal.setOrder(KeyType.title);
		for (Animal animal : animals)
			System.out.println(animal);
	}
}
Animal.main(null);
class [LREPL.$JShell$13C$Animal; 6
Collectable: Animal listed by title
Animal: Lion, Gold, 8
Animal: Pig, Pink, 3
Animal: Robin, Red, 7
Animal: Cat, Black, 10
Animal: Kitty, Calico, 1
Animal: Dog, Brown, 14

Animal: Cat, Black, 10
Animal: Dog, Brown, 14
Animal: Kitty, Calico, 1
Animal: Lion, Gold, 8
Animal: Pig, Pink, 3
Animal: Robin, Red, 7

Hashmap Lookup

import java.util.HashMap;
import java.lang.Math;

public class Pets {
    // create a new HashMap
    HashMap<Integer, String> names = new HashMap<Integer, String>();

    /* Add Pets
     * 
     */
    public Pets() {
        // add some key-value pairs to the HashMap
        for (int i=0; i<5000; i++) {
            String s = String.valueOf(i);  
            names.put(i, s);
        }
    } 

    /* Tester Method
     * 
     */
    public static void main(String[] args) {
        // intialize Pets
        Pets pets = new Pets();
        // Hashmap lookup
        int rand = (int) (Math.random()*5000);

        long startTime = System.nanoTime();
        for (Integer key : pets.names.keySet()) {
            if (key==rand) {
                break;
            }
        }
        long elapsedTime = System.nanoTime() - startTime;
        System.out.println("Hashmap Lookup");
        System.out.println(elapsedTime);
    }
}

Pets.main(null);
Hashmap Lookup
135600

Binary Search in Hashmap

import java.util.HashMap;
import java.lang.Math;

public class Pets {
    // create a new HashMap
    HashMap<Integer, String> names = new HashMap<Integer, String>();

    /* Add Pets
     * 
     */
    public Pets() {
        // add some key-value pairs to the HashMap
        for (int i=0; i<5000; i++) {
            String s = String.valueOf(i);  
            names.put(i, s);
        }
    } 

    /* Tester Method
     * 
     */
    public static void main(String[] args) {
        // intialize Pets
        Pets pets = new Pets();
        // Hashmap lookup
        int rand = (int) (Math.random()*5000);
        int mid = 2500;
        int min = 0;
        int max = 5000;
        long startTime = System.nanoTime();

        for (int i=0; i<20; i++) {
            if (rand == mid) {
                break;
            }
            if (rand > mid) {
                mid = (max+mid)/2;
            }
            if (rand < mid) {
                mid= (min+mid)/2;
            }
        }

        long elapsedTime = System.nanoTime() - startTime;
        System.out.println("Binary Sort");
        System.out.println(elapsedTime);
    }
}

Pets.main(null);
Binary Sort
3100

Just by looking at one output, we can clearly see that binary sort is way more efficient. This is because it needs to iterate way less times to find the random number.