generics in java
  1. Introduction
  2. Generic Methods
  3. Generic Constructors
  4. Bounded Type Parameters
  5. Generic Class
  6. Generic Interfaces
  7. Raw Types and Legacy Code
  8. Bounded Wildcards
  9. Generic Restrictions
  10. Erasure, Ambiguity Errors And Bridge Methods
  11. Conclusion

Introduction

The word generics means parameterized types. Parameterized types are important because they enable us to create databases, interfaces, and methods through which the type of data on which they operate is given as a parameter. In generics, it is possible to create a single class. A class interface or a method that operates on a parameterized type is called generic, like generic class or generic method, and generics only works with objects. And their type differs based on their type arguments.

The generics in java programming was introduced in J2SE 5 to deal with type-safe objects. It detects the bugs at compile-time and makes the code stable. The java collections framework always supports the generics to specify the type of object to be stored. It is always important to understand that java has given the ability to create generalized interfaces, classes and methods operating with references to the type of object. The object will be the superclass of all other classes; this object reference can refer to any type of object.

Generics in java added the type of safety which was lacking and also streamlined the process since it is no longer necessary to explicitly employ casts to translate between object and the data that is actually operated on.

Thus, generics expands our ability to reuse the code, which is type safety and easy.

A simple generics in java example:

The below program demonstrates two different classes. The first class is the generic class generics, and the second class is generic demo which uses generics.

//A simple generic class. Here S, is a parameter that will be replaced by a //real type when an object of generics is created.
Class generics <S> {
S obj; // declare an object of type S
//pass the constructor a reference to
//an object of type S
Generics (S o) {
Obj=o;
}
//return obj.
S getobj ( ) {
return obj;
}
//show type of S
Void showType ( ) {
System.out.println(“type “ + obj.getClass ( ) .getName ( ) );
Obj.getclass ( ). getname ( ) );
}
}
//demonstrate the generic class.
Class genericsdemo {
//**Public static void main ( String args [] ) {
// create a generics reference for integers.
gen<integer> iobj;
iobj = new generics<integer> (88);
iobj.showtype ( ) ;
int p= iob.getobj ( ) ;
//System.out.println(“value: “ + p);
//System.out.println ( ) ;
generics<String>  strob = new generics<String> (“Test for generics”);
strobj.showType ( );
String str = strobj.getob ( ) ;
 //System.out.println ( “ value : “ + str );
}
}

The output produced is:

Type of S is java.lang.integer 

Value: 88

Type of S is java.lang.integer

Value: Test for generics 

Generic Methods

Generic methods introduce their type of parameters, i.e., static and non-static generic methods are allowed and constructors. The methods in a generic class can use a class type parameter and are, therefore, automatically generic relative to the type parameter. It is also possible to declare a generic method which uses one or more types of parameters on its own. It is also possible to create a method within a non-generic class. Type inference allows invoking a method as an ordinary method without specifying a type between brackets.

The below program declares a class that is non-generic called genmeth and a generic method within the same class demo (). The generic method shows if an object is a member of an array, and this can also be used with any type of object and array as long as that array contains objects that are compatible with the type of the object.

// demonstrating a simple generic method 
Class genmeth {
// determining whether if an object is array.
Static <S, T extends S> boolean demo (S x, T [] y) {
f (int type=1; type<y. length; type++)
if (x. equals (y[type] ) )
return true;
}
//Public static void main ( String args [ ] ) {
//use demo () on integers 
Integer number [ ] = { 1, 2, 3, 4, 5 };
If (demo (2, nums) )
System.out.println(“2 is in nums”);
If (!demo (7, nums) )
System.out.println(“7is in nums”);	
}
}

Output:

2 is in nums

7 is in nums

In the above program the syntax used for creating demo () is: <type-param-list> ret-type meth-name(param-list) { // ….

Also Read: Palindrome in Java

Generic Constructors

It is possible for constructors to be generic even if the construct class is not generic. These constructors at least have one parameter which is of generic type. 

//using a generic constructor 
Class constructor {
Private double val;
<T extends Number> constructor ‘(T arg) {
Val=arg.doubleValue ( );
}
Void showval ( ) {
//System.out.println(“value” + val);
}
}
Class consdemo {
//Public static void main (String args [] ) {
Constructor test= new constructor (1000);
Constructor test1= new constructor (123.5F);
test.showval ();
test1.showval ();
}
}

The output will be:

Value 1000.0

Value 123.5 

In this example, the constructor specifies a parameter of a generic type, which is a subclass of number. A constructor can be called with any numeric type, which includes integer, float or double. Though constructor is not a generic class, its constructor is generic.

Bounded Type Parameters

The type parameters can be replaced by any class type for many purposes, and sometimes it is useful to limit what is passed to a type parameter. Whenever we want to declare a bound type parameter, list the type parameters name followed by extends keyword and upper bound.

Let us assume that we need to create a generic class that contains a method that should return an average of an array of numbers. Then we want to use the class to obtain the average of an array of any type of number, which may be an integer, double, float. Thus, we should specify the type of numbers generically using a type parameter.

//states attempts unsuccessfully to create a generic class that can compute the average.
//the class contains an error
Class states <X>{
X [] nums; nums is an array type;
// pass the constructor reference to type X
States (X [] o) {
nums=0;
}
//return type float in all cases 
float average () {
float sum=0.0;
for (int j=0; j< nums. Length; j++ )
sum += nums[j].floatValue ( ) ; //error //
return sums/nums. Length;
}
}

In the above program, the average () method tries to obtain the float version of each number in the nums array by calling float value since all numeric classes integer float double are subclasses of number, and this defines the float value method. This method is available for all numeric wrapper classes. The problem is that the compiler has no way to know that we intend to create states objects using only numeric types. And when we compile, we get errors reported. To solve this problem, we need to tell the compiler to pass only numeric type values to X. Further. We need to ensure that only numeric types are passed.

To handle these types of situations, java provides us bounded types. When specifying these type parameters, you can create an upper bound that declares the superclass from which all types of arguments must be derived. This is done by using extends keyword clause when specifying the type parameter as shown below:

<X extends superclass>

This specifies that X can only be replaced by superclass or subclass of superclass. Superclass defines an inclusive, upper limit. 

Using an upper bound we can fix about class, by specifying Number as an upper bound as shown below.

// in this the type argument for X must be either a number or a class derived from number.
Class states <X extends Number> {
X[] nums; //array of number or subclass
// pass the constructor a reference to 
// an array of type number or subclass 
float average ( ) {
float sum = 0.0;
for (int type=0; type<nums. Length; type++)
sum += nums[type]. Float value ();
return sum/ nums.Length;
}
}
//demonstrates states
Class bounds {
Public static void main (String args []) {
Integer inums ={1, 2, 3, 4, 5};
States<integer> iobj = new states<integer> (inums);
float v = iob.average ();
System.out.println (“iob average is “ +v);
States<integer> iobj = new states<integer> (inums);
float w = fob.average ();
System.out.println (“fob average is “ +w);
// this wont compile because string is not a subclass of number 
// string strs [] ={ “1”, “2”, “3”, “4”, “5”};
//States<String> strob = new  states<string> (strs);
//float x = strob.average ();
//system.out.println(“ strob average is ” + v );
}
}

Output:

Average is 3.0

Average is 3.3

Type x is bounded by a number. The compiler knows that all objects of type X can have double values since its method is declared by a number.

Generic Class

The general form or the syntax for declaring a generic class is shown below:

Class class-name <type-arg-list> { //……

And syntax for declaring a reference to a generic class is:

Class-name <type-arg-list> var-name= new class-name<type-arg-list>(cons-arg-list);

Generic class hierarchy:

Generic classes can also be a part of the class hierarchy in the same way as a generic class can be. Thus, a generic class can act as both a superclass and also a subclass. The main difference between the generic and non-generic classes is that, in a generic hierarchy any type arguments which are needed by a superclass must be passed to hierarchy of subclasses, which is similar to the way constructor arguments are passed up by a hierarchy.

Let us see an example which uses both superclass and a subclass:

//a simple generic class hierarchy of both superclass and subclass:
Class Generic<X> {
X ob;
Generic (X o) {
Ob=o;
}
//return ob;
X getob () {
Return ob;
}
}
//a subclass of gen it can create its own parameters.
Class Generic2<X> extends Generic <X> {
Generic2  (X o) {
Super(o);
}
}

In this example, we can see that Generic2 does not use the type parameter X except to pass the Generic superclass, otherwise it would not need to be generic. It should specify the parameters required by its generic superclass. Subclass is free to add its own type parameters.

There are also runtime comparisons in a generic hierarchy, i.e., instances of determines whether an object is an instance of a class. It returns true if the object is a specified type or it can be cast to that specified type. This can be applied to objects of generic classes. One instance of a class can be cast to another type if both are compatible, and their type arguments are the same. We can also override a method in a generic class like any other methods.

Generic Interfaces

Generic interfaces are additionally the same as generic classes and generic methods. These are specified just like generic classes. These are declared the same like generic classes. If a class implements a generic interface, then the implementing class does not need to be generic. 

// a generic interface example
interface minimum < x extends comparable <X> > {
X min ();
}
//implementing min function 
Class MyClass<X extends comparable <X>> implements min <X> {
X [] vals;
MyClass ( X[] o )
{
Vals=0;
}
// return the min value in vals
Public X min () {
X v= vals [0];
for (int i=0; i<vals.Length; i++)
if(vals[i].comparisionTo9v0 < 0)
v=vals[i];
return v;
}
}
Class demo {
Public static void main (String args [])
{
Integer inums[]= {3, 6, 9, 7, 8};
Character chs[]= {a, ’g’, ’h’, ’j’, ’w’}	
MyClass<Integer> iob = new MyClass<Integer> (inums);
MyClass<Character> cob = new MyClass<Character> (chs);
System.out.println(“minimum value inums:” + iob.min);
System.out.println(“minimum value chs:” + cob.min);
}
}

The output will be:

Minimum value inums: 3

Minimum value chs: a

Raw Types and Legacy Code

Generics is the addition to java, which is necessary for providing some transition to the path from old, pre-generics code. There are millions of pre-generics legacy code that must remain functional and compatible with generics. Pre-generics code should be able to work with generics, and generic code must be able to do work with pre-generic code. To handle the transitions of generics, java allows a generic class that can be used without any type of arguments, and thus it creates a raw-type for the class. This raw-type is compatible with legacy code which has no knowledge about generics. And there lies the main drawback to using this raw-type is that the type safety of generics is lost. A raw type is not type-safe. Thus, a variable of a raw type can be assigned a reference to any type of object. One final point about raw-type and legacy code is that we should limit the use of raw types to the codes in which we must mix legacy code with the new generic code. Raw types are transitional features that should not be used for new code.

Generics Fundamentally Changed the Collection Framework

The addition of generics to java caused a significant change to the collection framework since the entire collections framework has to be reengineered for it. All collections are now generic, and many of these methods which operate on collections take generic type parameters. The addition of generics affected each and every part of the collections. Generics added that one type of feature which collection was missing nothing but type safety.

Bounded Wildcards

Wildcard arguments can be bounded in the same way that a type parameter can be bounded. A bounded wildcard is always important when we are creating a generic type that will operate on a class hierarchy. To understand this, let us see an example for bounded wildcards.

In general, for establishing an upper bound for a wild card, we use the given below expression:

<? extends superclass> 

This superclass is the name of a class that serves as an upper bound. And we should remember that this is inclusive because the class forming the upper bound is also within the bounds.

We can also specify a lower bound for a wildcard by adding a super clause to a wild card declaration.

<? super subclass>

In these types of cases, only that classes which are super classes of a subclass are the acceptable arguments. This is called an exclusive clause because it will not match the specified class by a subclass.

Generic Restrictions

There are also a few restrictions that we need to keep in mind when we use generics. They always involve creating objects of a type parameter, static members, exceptions, and arrays.

Some restrictions are:

  • Type parameters can’t be instantiated

Instance of a type parameter cannot be created.

For example:

//cannot create an instance of T.
Class gen<T>
T ob;
gen () {
ob = new T; // this is illegal creation.
}
} 

This is an illegal attempt to create an instance of T. The reason is T does not exist at runtime; how can the compiler know what type of object to be created. We should remember that erasure removes all types of parameters during the compilation process.

  • Restrictions on static members

In this restriction, no static members can use a type parameter that is declared by enclosing class. We can’t declare static members that use a type parameter declared by the enclosing class. We can declare static generic methods, which define their own type parameters.

  • Generic array restrictions

There are mainly two important generic restrictions that are applied to arrays. Firstly, we cannot instantiate an array whose base type is always a type parameter. And the second one is that we cannot create an array of type-specific generic references. We can pass a reference to a type-compatible array when an object is created and assign the references. We can also create an array of references to generic if we use a wildcard. And this is considered to be better than using an array of raw types because type checking will still be enforced.

  • Generic exception restriction 

Generic classes cannot extend throwable. This means that we cannot create generic exception classes.

Erasure, Ambiguity Errors And Bridge Methods

Let us look at some topics in generics briefly:

  • Erasure

When the java code is compiled, all generic type information is erased or removed, which means replacing type parameters with their bound type, which is an object if no explicit bound is specified and then applying the appropriate casts for maintaining type compatibility with the types specified with the type arguments.

The compiler enforces this type of compatibility, and this approach to generic means that no type parameters exist at run time. And simply called a source-code mechanism.

  • Ambiguity errors

The inclusion of generics gives rise to a new type of error called ambiguity; this error occurs when erasure causes two seemingly distinct generic declarations for resolving to the same erased type, which causes a conflict. Often, the solution to ambiguity involves the restricting of the code since ambiguity often means that we have a conceptual error in the design.

  • Bridge methods

The compiler needs to add a bridge method to a class to handle situations in which the type erasure of an overriding method in a subclass does not produce the same erasure as a method in the superclass. In this case, a method can be generated, which uses the type erasure of the superclass, and this method calls the method that has the type erasure specified by the subclass. These bridge methods will occur only at the bytecode level, and these are not available for use. There is one last point we should consider about bridge points: their return type. This would cause an error in our source code. It does not cause a problem that is handled correctly by the JVM.

Advantages

  • Stronger type checks at a compile time
  • Elimination of casts
  • Enabling users to implement generic algorithms
  • Type safety
  • Reusability 
  • They convert runtime errors to compile time errors

Conclusion

Generics are the extensions to java since they streamline the creations of type-safety and reusable code. Generic code will be part of the future for all java programmers. This brings us to the end of the blog on generics in Java. We hope that you were able to gain some valuable insights from the same. If you wish to learn more such concepts, check out Great Learning Academy’s Free Online Course on Java Programming and upskill today.

Also Read:
Inheritance in Java and Types of Inheritance in Java

0

LEAVE A REPLY

Please enter your comment!
Please enter your name here

four × 5 =