"

Java Generic Classes

Lecture: Generics (11 minutes)

Similarly to generic methods, Java also supports generic classes. In a method, the generic type stood in for the specific type of the parameter(s). In a class, the generic type can be used to stand in for the type of the instance variable(s), return types of methods, and/or parameter types of the methods. Let’s look at some examples.

Defining Generic Classes

Here is a simple example of defining Generic class:

// Use<> to specify parameter type and define generic class

public class GenericClass<T> {
    // Type T reference variable named someField declaration.
    T someField;
    // constructor
    GenericClass(T someField) {
        this.someField = someField;
    }

    public T getSomeField() {
        return this.someField;
    }

    public static void main(String[] args) {
        GenericClass<String> stringSomeField = new GenericClass<String> ("String Value and check constructor param");
        System.out.println(stringSomeField.getSomeField());
        GenericClass<Integer> integerSomeField = new GenericClass<Integer> (23);
        // unboxing of new Integer(23);
        System.out.println(integerSomeField.getSomeField());
    }
}

Generic Interfaces

Let’s revisit the Boxable example from earlier. In the previous versions, the Boxable interface required the class implementing the interface to add and remove Strings. Using generics, we can modify the interface to allow the user to store any object. Note that we no longer have to specify the generic type for each method.

/**
 * Defines the basic behavior of a box
 */

public interface GenericBoxable<E> {
    /**
     * Adds an element to the Box
     *
     * @return true if element successfully added; false otherwise
     */

    public boolean add(E element);
    /**
     * Removes an element from the Box
     *
     * @return the element removed or null if nothing removed
     */

    public E remove(E element);
    /**
     * @return String representation of the contents of the box
     */

    public String peek();
}

Here’s an example of a generic class that implements the generic Boxable interface.

/**
 * Represents a generic shipping crate
 */

public class GenericCrate<E> implements GenericBoxable<E> {
    private ArrayList<E> contents;

    public GenericCrate() {
        contents = new ArrayList<E>();
    }
    /**
     * Removes all elements currently in the Crate and adds them to collection
     *
     * @param collection - where to put items in the crate
     */

    public void addAllTo(Collection<? super E> collection) {
        // Remove all items currently in the Crate and add them to collection
        while (!contents.isEmpty()) {
            E item = contents.remove(0); // Remove an item from the queue.
            collection.add(item); // Add it to the collection.
        }
    }
    /**
     * Adds everything in collection to this crate
     */

    public void addAll(Collection<? extends E> collection) {
        // Add all the items from the collection to the end of the queue
        for (E item: collection)
            add(item);
    }

    public boolean add(E element) {
        return contents.add(element);
    }

    public E remove(E element) {
        if (contents.remove(element)) {
            return element;
        }

        return null;
    }

    public String peek() {
        if (contents.isEmpty()) {
            return "Empty crate";
        }

        return "Crate contains: " + contents.toString();
    }
}

ADT Generic Class

We now have all the pieces required to create a box that’s truly an Abstract Data Type.

/**
* Represents a box that holds one item
*/
public class GenericBox<E> {
    private E item;

    /**
    * Adds element to the box, if it is empty
    *
    * @param element
    * @return true if element was added, false if the box was already full
    */
    public boolean add(E element) {
        if (item != null)
            return false;
        item = element;
        return true;
    }

    /**
    * Removes object from the box
    */
    public E remove() {
        return item;
    }

    @Override
    public String toString() {
        String top = "#".repeat(20) + "\n";
        String emptySide = "###" + " ".repeat(14) + "###" + "\n";

        String side;
        if (item != null)
            side = String.format("### %10s ###\n", item.toString());
        else
            side = emptySide;

        return top + emptySide + side + emptySide + top;
    }
}


Why Generics and Not Object?

At this point, you may be wondering why we bother with all the extra syntactical overhead of using generics when we could just use Object as the default type. There are a few reasons for this.

First, remember that the methods available to call on an object are determined by the reference type. Thus, your class would be limited to only using methods that are available to all Objects. If you wanted to use methods not defined in Object, you would have to type cast the object. But therein lies another problem – to what type would you cast the object since you have no idea what type of object it is?

This leads to another problem for your user. If they were to use your class that only accepts Objects, every time they got an object back, they would have to cast the object back to the type they wanted.

/**
 * Represents a box that holds one item
 *
 */

public class ObjectBox {
    private Object item;
    /**
     * Adds element to the box, if it is empty
     *
     * @param element
     * @return true if element was added, false if the box was already full
     */
    public boolean add(Object element) {
        if (item != null)
            return false;
        item = element;
        return true;
    }
    /** 
     * Removes object from the box
    */
    public Object remove() {
        return item;
    }
    @Override
    public String toString() {
        String top = "#".repeat(20) + "\n";
        String emptySide = "###" + " ".repeat(14) + "###" + "\n";
        String side;
        if(item != null)
            side = String.format("###  %10s  ###\n", item.toString());
        else
            side = emptySide;
        return top + emptySide + side + emptySide + top;
}

import java.util.Color;
public class Main {
    public static void main(String[] args){
        ObjectBox box = new ObjectBox();
        box.add(Color.RED);
        . . .
        Color retrievedColor = (Color)box.remove(); // have to cast Object to Color
    }
}


Using generics provides a cleaner, safer way to allow different types of data to be used. Since you can rely on compiler verification with generics, your programs are less prone to user error and can do more with the objects. The next section describes another benefit of using generic types – you can pinpoint exactly what behaviors you want the generic type to have.

License

Icon for the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License

Computer Science II Copyright © by Various is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License, except where otherwise noted.