Generics in Java and Their Implementation

Generics in Java

In Java programming, language generics are introduced in J2SE 5 for dealing with type-safe objects. It detects bugs at the compile time by which code is made stable. Any object type is allowed to be stored in the collection before the generic introduction. Now after the generic introduction in the Java programming language, programmers are forced to store particular object types.

Advantages of Java Generics

Three main advantages of using generics are given below:

1. Type-Safety

Generics allow to store of only a single object type. Therefore, different object types are not allowed to be stored in generics.

Any type of object is allowed to be stored without generics.

// declaring a list with the name dataList

List dataList= new ArrayList();    

// adding integer into the dataList 

dataList.add(10);  

// adding string data into the dataList 

dataList.add("10"); 

With generics, we need to tell the object type we want to store.

Declaring a list with the name dataList

List<Integer> dataList= new ArrayList();   

Adding integer into the dataList 

dataList.add(10);  

Adding string data into the dataList 

dataList.add("10"); // but this statement gives compile-time error  

2. No Need for Type Casting 

Object type casting is not required with generics.

It is required to do casting before a generic introduction.

declaring a list with the name dataList

List dataList= new ArrayList();    

adding an element to the dataList 

dataList.add("hello");    

typecasting    

String s = (String) dataList.get(0);

There is no need for object type casting after generics.

// declaring a list with the name dataList

List<String> dataList= new ArrayList<String>();    

// adding an element to the dataList 

dataList.add("hello");   

//typecasting is not required 

String s = dataList.get(0);

3. Checking at Compile Time

Issues will not occur at run time as it is checked at the compile time. And according to good programming strategies, problem handling done at compile time is far better than handling done at run time. 

// declaring a list with the name dataList

List<String> dataList = new ArrayList<String>();    

// adding an element into the dataList 

dataList .add("hello");    

// try to add an integer in the dataList but this statement will give compile-time error

dataList .add(32);

Syntax:

Generic collection can be used as :

ClassOrInterface<Type>    

Example:

An example of how generics are used in java is given below:

ArrayList<String>  

Example Program of Java Generics

The ArrayList class is used in this example. But in place of the ArrayList class, any class of collection framework can be used, like Comparator, HashMap, TreeSet, HashSet, LinkedList, ArrayList, etc.

// importing packages 

import java.util.*;

// creating a class with the name GenericsExample

class GenericsExample {

    // main method

    public static void main(String args[]) {

        // declaring a list with the name dataList to store String elements

        ArrayList < String > dataList = new ArrayList < String > ();

        // adding an element into the dataList 

        dataList.add("hina");

        // adding an element into the dataList 

        dataList.add("rina");

        // if we try to add an integer into the dataList then it will give a compile-time error

        //dataList.add(32); //compile time error  

        // accessing element from dataList 

        String s = dataList.get(1); //no need of type casting

        // printing an element of the list

        System.out.println("element is: " + s);

        // for iterating over the dataList elements

        Iterator < String > itr = dataList.iterator();

        // iterating and printing the elements of the list

        while (itr.hasNext()) {

            System.out.println(itr.next());

        }

    }

}

Output

element is: rina

hina

rina

Java Generics Example Using Map

In this, we are using a map to demonstrate the generic example. The map allows the data storage in the form of key-value pairs. 

// importing packages 

import java.util.*;

// creating a class with the name GenericsExample

class GenericsExample {

    // main method

    public static void main(String args[]) {

        // declaring a map for storing keys of Integer type with String values

        Map < Integer, String > dataMap = new HashMap < Integer, String > ();

        // adding some key value into the dataMap

        dataMap.put(3, "seema");

        dataMap.put(1, "hina");

        dataMap.put(4, "rina");

        // using dataMap.entrySet() 

        Set < Map.Entry < Integer, String >> set = dataMap.entrySet();

        // creating an iterator for iterating over the dataMap

        Iterator < Map.Entry < Integer, String >> itr = set.iterator();

        // iterating for printing every key-value pair of map

        while (itr.hasNext()) {

            // type casting is not required

            Map.Entry e = itr.next();

            System.out.println(e.getKey() + " " + e.getValue());

        }



    }

}

Output:

Generic Class

A generic class is a class that can refer to any type. And here, for creating a particular type of generic class, we are using the parameter of T type. 

The declaration of a generic class is much similar to the declaration of a non-generic class, except that the type parameter section is written after the name of the class. Parameters of one or more than one type are allowed in the type parameter section. 

A generic class declaration looks like a non-generic class declaration, except that the class name is followed by a type parameter section. As one or more parameters are accepted, so Parameterized types or parameterized classes are some other names for it. 

And the example is given below to demonstrate the use and creation of generic classes.

Generic Class Creation

class GenericClassExample < T > {

    T object;

    void addElement(T object) {

        this.object = object;

    }

    T get() {

        return object;

    }

}

Here T type represents that it can refer to any type, such as Employee, String, and Integer. The type specified by you for the class is used for data storage and retrieval. 

Generic Class Implementation

Let us see an example for a better understanding of generic class usage

// creating a class with the name GenericExample

class GenericExample {

    // main method

    public static void main(String args[]) {

        // using the generic class created in the above example with the Integer type

        GenericClassExample < Integer > m = new GenericClassExample < Integer > ();

        // calling addElement for the m

        m.addElement(6);

        // if we try to call addElement with the string type element then it will give a compile-time error

        //m.addElement("hina"); //Compile time error  

        System.out.println(m.get());

    }

}

Output

Generic Method

Similar to the generic class, generic methods can also be created. And any type of argument can be accepted by the generic method. The declaration of the generic method is just similar to that of the generic type, but the scope of the type parameter is limited to the method where its declaration has been done. Generic methods are allowed to be both static and non-static. 

Let us understand the generic method of Java with an example. Here is an example of printing the elements of an array. Here E is used for representing elements.

// creating a class with the name GenericExample

public class GenericExample {

    // creating a generic method for printing the elements of an array

    public static < E > void printElements(E[] elements) {

        // iterating over elements for printing elements of an array

        for (E curElement: elements) {

            System.out.println(curElement);

        }

        System.out.println();

    }

    // main method

    public static void main(String args[]) {

        // declaring an array having Integer type elements

        Integer[] arrayOfIntegerElements = {

            10,

            20,

            30,

            40,

            50

        };

        // declaring an array having character-type elements


        Character[] arrayOfCharacterElements = {

            'J',

            'A',

            'V',

            'A',

            'T',

            'P',

            'O',

            'I',

            'N',

            'T'

        };


        System.out.println("Printing an elements of an Integer Array");

        // calling generic method printElements for integer array 

        printElements(arrayOfIntegerElements);

        System.out.println("Printing an elements of an Character Array");


        // calling generic method printElements for character array 

        printElements(arrayOfCharacterElements);

    }

}

Output:

Printing an elements of an Integer Array

10

20

30

40

50



Printing an elements of an Character Array

J

A

V

A

T

P

O

I

N

T

Wildcard in Java Generics

Wildcard elements in generics are represented by the question mark (?) symbol. And any type is represented by it. If <? extends Number> is written by us, then this means any Number such as (double, float, and Integer) child class. Now the number class method can be called from any of the child classes. Wildcards can be used as a type of local variable, return type, field, or Parameter. But the wildcards can not be used as type arguments for the invocation of the generic method or the creation of an instance of generic.

Let us understand the wildcards in Java generics with the help of the example below given:

// importing packages

import java.util.*;

// creating an abstract class with the name Animal

abstract class Animal {

    // creating an abstract method with the name eat

    abstract void eat();

}

// creating a class with the name Cat which inherits the Animal class

class Cat extends Animal {

    void eat() {

        System.out.println("Cat can eat");

    }

}

// creating a class with the name Dog which inherits the Animal class

class Dog extends Animal {

    void eat() {

        System.out.println("Dog can eat");

    }

}

// creating a class for testing the wildcards of java generics

class GenericsExample {

    //creating a method by which only Animal child classes are accepted 

    public static void animalEat(List << ? extends Animal > lists) {

        for (Animal a: lists) {

            //Animal class method calling by the instance of the child class

            a.eat();

        }

    }

    // main method

    public static void main(String args[]) {

        // creating a list of type Cat

        List < Cat > list = new ArrayList < Cat > ();

        list.add(new Cat());

        list.add(new Cat());

        list.add(new Cat());

        // creating a list of type Dog

        List < Dog > list1 = new ArrayList < Dog > ();

        list1.add(new Dog());

        list1.add(new Dog());

        // calling animalEat for list

        animalEat(list);

        // calling animalEat for list1

        animalEat(list1);

    }

}

Output:

Cat can eat

Cat can eat

Cat can eat

Dog can eat

Dog can eat

Upper Bounded Wildcards

The main objective of using upper-bounded wildcards is to reduce the variable restrictions. An unknown type is restricted by it to be a particular type or subtype of a particular type. Upper Bounded Wildcards are used by writing a question mark symbol, then extending the keyword if there is a class and implementing a keyword for the interface, and then the upper bound is written.

Syntax of Upper Bound Wildcard

 ? extends Type. 

Example of Upper Bound Wildcard

Let us understand the Upper Bound Wildcard with an example. Here upper bound wildcards are used by us for List<Double> and List<Integer> method writing.

// importing packages

import java.util.ArrayList;

// creating a class with the name UpperBoundWildcardExample 

public class UpperBoundWildcardExample {

    // creating a method by using upper bounded wildcards

    private static Double sum(ArrayList << ? extends Number > list) {



        double add = 0.0;



        for (Number n: list) {

            add = add + n.doubleValue();

        }



        return add;

    }

    // main method

    public static void main(String[] args) {

        // creating a list of integer type

        ArrayList < Integer > list1 = new ArrayList < Integer > ();

        // adding elements to the list1

        list1.add(30);

        list1.add(40);

        // calling sum method for printing sum

        System.out.println("Sum is= " + sum(list1));

        // creating a list of double type

        ArrayList < Double > list2 = new ArrayList < Double > ();

        list2.add(10.0);

        list2.add(20.0);

        // calling sum method for printing sum

        System.out.println("Sum is= " + sum(list2));





    }



}

Output:

Sum is= 70.0

Sum is= 30.0

Unbounded Wildcards

Unknown type list is specified by the unbounded wildcards like List<?>.

Example of Unbounded Wildcards

// importing packages

import java.util.Arrays;

import java.util.List;

// creating a class with the name UnboundedWildcardExample 

public class UnboundedWildcardExample {

    // creating a method displayElements by using Unbounded Wildcard

    public static void displayElements(List << ? > list) {



        for (Object n: list) {

            System.out.println(n);

        }



    }



    // main method

    public static void main(String[] args) {

        // creating a list of type integer

        List < Integer > list1 = Arrays.asList(6, 7, 8);

        System.out.println("printing the values of integer list");

        // calling displayElements for list1  

        displayElements(list1);

        // creating a list of type string

        List < String > list2 = Arrays.asList("six", "seven", "eight");

        System.out.println("printing the values of string list");

        // calling displayElements for list2

        displayElements(list2);

    }

}

Output:

printing the values of integer list

6

7

8

printing the values of string list

six

seven

eight

Lower Bounded Wildcards

Lower Bounded Wildcards are used to restrict the unknown type to be a particular type or the supertype of the particular type. Lower Bounded Wildcards are used by writing a question mark symbol followed by the keyword super, then writing the lower bound.

Syntax of Lower Bound Wildcard

 ? super Type. 

Example of Lower Bound Wildcard

// importing packages

import java.util.*;

// creating a class with the name LowerBoundWildcardExample 

public class LowerBoundWildcardExample {

    // creating a method by using upper bounded wildcards

    private static void displayElements(List << ? super Integer > list) {



        for (Object n: list) {

            System.out.println(n);

        }

    }



    // main method

    public static void main(String[] args) {

        // creating a list of type integer

        List < Integer > list1 = Arrays.asList(6, 7, 8);

        System.out.println("printing the values of integer list");

        // calling displayElements for list1  

        displayElements(list1);

        // creating a list of type string

        List < Number > list2 = Arrays.asList(8.0, 9.8, 7.6);

        System.out.println("printing the values of string list");

        // calling displayElements for list2

        displayElements(list2);

    }

}

Output: 

printing the values of integer list

6

7

8

printing the values of string list

8.0

9.8

7.6

Conclusion

  • After the generic introduction in the Java programming language, programmers are forced to store particular object types.
  • Type safety, No need for type casting, and Checking at compile time are Three main advantages of using generics.
  • A generic class is a class that can refer to any type.
  • Similar to the generic class, generic methods can also be created. And any type of argument can be accepted by the generic method. 
  • Wildcard elements in generics are represented by the question mark (?) symbol.
  • Upper Bounded, Lower Bounded, and Unbounded are three types of wildcards in Java generic.

Source link