What are generics in Java, and what is their use? Are you also contemplating the same? Look no further as we attempt to explain what generics in Java along with examples. Below are the topics we will be discussing in this blog. So, let’s get started, shall we?
- Introduction
- Generic Methods
- Generic Constructors
- Bounded Type Parameters
- Generic Class
- Generic Interfaces
- Raw Types and Legacy Code
- Bounded Wildcards
- Generic Restrictions
- Erasure, Ambiguity Errors, And Bridge Methods
- Conclusion
- Frequently Asked Questions
Introduction
The word generics means parameterized types. Parameterized types are essential because they enable us to create databases, interfaces, and methods through which the type of data 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 work with objects. And their type differs based on their type arguments.
The generics in java programming were 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 essential to understand that Java can create generalized interfaces, classes, and methods operating with references to the object type. The object will be the superclass of all other classes; this object reference can refer to any object.
Generics in java added the type of safety lacking and streamlined the process since it is no longer necessary to explicitly employ casts to translate between object and the data that is operated on.
Thus, generics expand 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 is the generic class generics, and the second is the 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 that 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 non-generic class called genmeth and a generic method within the same class demo (). The generic method shows if an object is a member of an array, which can also be used with any object and array as long as that array contains objects 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
Constructors can be generic even if the constructed 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 generic type parameter, a subclass of Number. A constructor can be called with any numeric type, which includes integer, float, or double. Though the constructor is not a generic class, its constructor is generic.
Bounded Type Parameters
Any class type can replace the type parameters for many purposes, and sometimes limiting what is passed to a type parameter is helpful. 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, or float. Thus, we should generically specify the type of numbers 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, which defines the float value method. This method is available for all numeric wrapper classes. The problem is that the compiler does not know that we intend to create state 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 with 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 an extended keyword clause when specifying the type parameter as shown below:
<X extends superclass>
This specifies that X can only be replaced by a superclass or subclass of the superclass. Superclass defines an inclusive upper limit.
We can fix the class using an upper bound by specifying a 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
A number bounds type x. The compiler knows that all objects of type X can have double values since a number declares its method.
Generic Class
The general form or the syntax for declaring a generic class is shown below:
Class class-name <type-arg-list> { //……
And the 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 a generic class can be. Thus, a generic class can act as both a superclass and a subclass. The main difference between the generic and non-generic classes is that in a generic hierarchy, any type of argument needed by a superclass must be passed to the hierarchy of subclasses, similar to how a hierarchy passes up constructor arguments.
Let us see an example that uses both a 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, and it should specify the parameters required by its generic superclass; The subclass is free to add its type parameters.
There are also runtime comparisons in a generic hierarchy, i.e., instances that determines whether an object is an instance of a class. It returns true if the object is a specified type or can be cast to that specified type. This can be applied to objects of generic classes. One class instance 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 method.
Generic Interfaces
Generic interfaces are additionally the same as generic classes and generic methods, and these are specified just like generic classes and declared the same as 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. Millions of pre-generics legacy codes must remain functional and compatible with generics. Pre-generics code should be able to work with generics, and generic code must work with pre-generic code. To handle the transitions of generics, java allows a generic class that can be used without any arguments, and thus it creates a raw type for the class. This Raw type is compatible with legacy code which does not know 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 as a reference to any 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
Adding generics to java caused a significant change to the collection framework since the entire collections framework must be re-engineered. All collections are now generic, and many of these methods which operate on collections take generic type parameters. The addition of generics affected every part of the collections, and Generics added that one type of feature, which 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 essential when creating a generic type that will operate on a class hierarchy. To understand this, let us see an example of 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 are superclasses of a subclass are the acceptable arguments. This is 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
The 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 declared by the enclosing class. We cancan’tclare static members that use a type parameter declared by the enclosing class, and we can declare static generic methods, which define their type parameters.
- Generic array restrictions
There are mainly two critical 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 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 separate generic declarations to resolve to the same erased type, which causes a conflict. Often, the solution to ambiguity involves restricting 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 are not available for use. One last point we should consider about bridge points is their return type. This would cause an error in our source code and does not cause a problem handled correctly by the JVM.
Advantages
- More vigorous 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 creation 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 you can gain some valuable insights from the same. Check out Great Learning Academy’s Online Course on Java Programming and upskill today to learn more about such concepts.
Frequently Asked Questions
Generics allow types to be parameters when defining classes, interfaces, and methods. Type parameters allow the reuse of the same code with multiple inputs, somewhat like the more well-known formal parameters used in method declarations.
A generic class essentially indicates that its components or operations can be generalized by substituting any other type for the example T parameter, such as an integer, character, string, double, or another user-defined type.
A generic class or interface that is specified across types is referred to as a generic type. In essence, generic types enable code reuse by enabling the development of general, generic classes (or methods) that function with various kinds.
The term “generic code” refers to the code, including any subroutines, that Broderbund, its affiliates, or third parties utilize in other products or for other reasons that are now included in the Product.
The responsibility for type safety is now on the compiler due to generics. Since the right data type is guaranteed at compile time, developing code to test for it is not necessary. Type casting is not required, hence there is less chance of run-time errors.