stary sky purple cliff with trees, and java symbol ontop featured image

Generics

Table of Contents

So what are Generics in Java?

Java Generic methods and generic classes enable programmers to specify, with a single method declaration, a set of related methods, or with a single class declaration, a set of related classes, respectively.

Generics allow you to build Classes and methods to handle any type of data. The use of Generics eliminates the need of making copies of the same classes and methods for each type of data.

What are Generic Classes?

A generic Class looks like a non-generic class, except the class name is followed by a generic type parameter. You can have one or more generic type parameters separated by commas. For example, public class Box<T> {} vs public class Box {} .

Now, the benefit of a generic class is that you don’t have to specify a type, meaning that a Generic class can make generic objects that accepts multiple types, and a type can be specified on the object, instead of the class. This saves you from making multiple classes of different type that does the same thing.

TL;DR: Generic Classes allow you to use 1 class for handling multiple types, instead of a class for each type. And Generic classes make Generic Objects, which can be specified a type if needed.

Example:

public class App {
    public static void main(String args[]) {
/**
* Using the Box class constructor, objects of type Box are made. This includes integerBox, stringBox, and floatBox.
*
* To tell the compiler that these generic objects can only work with a specific type, you use <type>.
* In the below example, <Integer><String><Float> are used to tell the compiler that the objects
* can only use Integer, String, and Float types respectively.
* 
* Now, the benefit of a generic class is that you don't have to specify a type, meaning that a Generic class can 
* make objects that accepts multiple types.
*/
        Box<Integer> integerBox = new Box<Integer>(10);
        Box<String> stringBox = new Box<String>("Hello World!");
        Box<Float> floatBox = new Box<Float>(10.f);

        System.out.println("Integer Value: " +  integerBox.get());
        System.out.println("String Value: " + stringBox.get());
        System.out.println("Float Value: " + floatBox.get());
    }
}

// Generic Class
class Box<T> { // Token T has not yet been defined. It will be defined when we instantiate the Box class. Object will only be able to work with the T type.
    //Instance variable
    private T x;

    //Parameterized constructor
    public Box(T x) {
        this.x = x;
    }

    //Returns the casted value by the methods signature
    public T get() {
        return x;
    }
}

Output:

generic-class-results

What are Generic Methods?

Generic methods allow you to eliminate redundant overloaded methods. So overloaded methods pretty much does the same thing, regardless of the type of data. Without Generics, you would need to make a copy of the overloaded method for each type. But you can reduce that to 1 overloaded method for all specified types with Generics, since the method is going to do the same thing regardless of type.

So instead of having say 10 overloaded method for each type of data, with Generics you can just have 1 method that processes all the data types.

TL;DR: Generic Methods allows you to use 1 method to handle multiple data types, instead of one method for each type of data.

Example without Generic Method:

Let’s start by making two arrays. We’ll have an Integer array called iray, and a Character array called cray.

Let’s make printMe Method outside of the main method.

import java.util.*;
public class Bucky {
    public static void main(String[] args) {
        Integer[] iray = {1,2,3,4};
        Character[] cray = {'b','u','c','y'};
        printMe(iray);
        printMe(cray);
    }

    // So what this is going to do is loop through your integer array and print out each element one by one.
    public static void printMe(Integer[] i){
        for (Integer x: i)
            System.out.printf("%s ", x); // ...(format, args); >>> ...(String, x); x is argument
        System.out.println();
    }
    public static void printMe(Character[] i){
        for (Character x: i)
            System.out.printf("%s ", x); // ...(format, args); >>> ...(String, x); x is argument
        System.out.println();
    }
}

Output:

"C:\Program Files (x86)\Java\jdk1.8.0_91\bin\java" -Didea.launcher.port=7532 "-Didea.launcher.bin.path=C:\Program Files (x86)\JetBrains\IntelliJ IDEA 2016.1.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\charsets.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\deploy.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\access-bridge-32.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\cldrdata.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\dnsns.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\jaccess.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\jfxrt.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\localedata.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\nashorn.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\sunec.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\sunjce_provider.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\sunmscapi.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\sunpkcs11.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\ext\zipfs.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\javaws.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\jce.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\jfr.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\jfxswt.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\jsse.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\management-agent.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\plugin.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\resources.jar;C:\Program Files (x86)\Java\jdk1.8.0_91\jre\lib\rt.jar;C:\Users\rock\IdeaProjects\practice2\out\production\practice2;C:\Program Files (x86)\JetBrains\IntelliJ IDEA 2016.1.3\lib\idea_rt.jar" com.intellij.rt.execution.application.AppMain Bucky 1 2 3 4 b u c y Process finished with exit code 0

Example with Generics Method:

<T> is a type parameter. Now java will look at what is given <T> as a “generic” form of data.

Make sure to have <T> always before the return type. In this case, before void.

So instead of Integer or Character, you replace them with Generic type T.

import java.util.*;
public class Bucky {
    public static void main(String[] args) {
        Integer[] iray = {1,2,3,4};
        Character[] cray = {'b','u','c','y'};
        printMe(iray);
        printMe(cray);
    }

    // The Generic Method
    public static <T> void printMe(T[] x){
        for(T b : x)
            System.out.printf("%s ", b);
        System.out.println();
    }
}

Same Output.

Using a Generic Return Type in a Generic Method

So in this code, we will be returning the maximum of type in the array. For example, out of Integers 2,3,4 in the Array 4 will be returned.

But because we are using multiple different types of data, it won’t be sufficient to use the greater than or less than signs. Instead, you need to use the compareTo method so that you can compare any kind of data.

The compareTo method is found in the Comparable class.

<T extends Comparable<T>> means only objects that inherit from the Comparable class can be used in this method.

The T that comes after <T extends Comparable<T>> is the return type; in other words T means that you can only return the type of T data.

import java.util.*;
public class Bucky {
    public static void main(String[] args) {
        System.out.println(max(23,42,1));
        System.out.println(max("apples","tots","chicken"));

    }

    // Generic type of data a, generic type of data b, generic type of data c.
    // So we are passing in 3 variables a, b, c. So this method has to return the maximum of those variables
    public static <T extends Comparable<T>> T max (T a, T b, T c) {
        T m = a; // Let's start out by assuming variable a is the greatest. m stands for maximum

        // compareTo returns either -1, 0, 1. So in this case, if b > a = 1 | if b < a = -1 | if b = a = 0
        if (b.compareTo(a) > 0)
            m = b;

        if(c.compareTo(m) > 0)
            m = c;

        return m;
    }
}

Output:

overload-method-output-array2

What's Your Opinion?