0% found this document useful (0 votes)
4 views

Module-4

Uploaded by

hayath
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views

Module-4

Uploaded by

hayath
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 19

Module-4

Generics Concept: General Form of a Generic Class, Bounded Types, Generic Class
Hierarchy, Generic Interfaces, Restrictions in Generics. Introduction to Lambda expression,
Block Lambda Expressions, Generic Functional Interfaces. Passing lambda expressions as
arguments - Lambda expressions and exceptions, Lambda expressions and variable capture.

Introduction to Generics
Introduced by JDK 5, generics changed Java in two important ways. First, it added a new
syntactical element to the language. Second, it caused changes to many of the classes and
methods in the core API. Through the use of generics, it is possible to create classes,
interfaces, and methods that will work in a type-safe manner with various kinds of data. With
generics, you can define an algorithm once, independently of any specific type of data, and
then apply that algorithm to a wide variety of data types without any additional effort.
The term generics means parameterized types. Parameterized types are important because
they enable you to create classes, interfaces, and methods in which the type of data upon
which they operate is specified as a parameter. A class, interface, or method that operates on
a parameterized type is called generic.
Generics added the type safety that was lacking. With generics, all casts are automatic and
implicit. Thus, generics expanded your ability to reuse code and let you do so safely and
easily. When declaring an instance of a generic type, the type argument passed to the type
parameter must be a class type. You cannot use a primitive type, such as int or char.

Advantages of Generics in Java


1. We can write a method/class/interface once and use it for any type we want.

2. We can hold only a single type of object in generics. It doesn’t allow to store other
objects.

3. Individual Type Casting is not needed.


Example:
Before Generics
List l= new ArrayList();
l.add("India");
String s = (String) l.get(0); // typecasting

After Generics, typecasting of the object is not required


List<String> l = new ArrayList<String>();
l.add("hello");
String s = l.get(0);

4. By using generics, we can implement algorithms that work on different types of


objects and at the same, they are type-safe too.

5. It is checked at compile time so the problem will not occur at runtime. The good
programming strategy says it is far better to handle the problem at compile time than
runtime.
Example:
List<String> list = new ArrayList<String>();
list.add("hello");
list.add(32); //Compile Time Error

For an instance of Generic class


BaseType <Type> object = new BaseType <Type>();

The General Form of a Generic Class


A generic class is a class that can hold any kind of object. To create a Generic class we have
to specify generic type <T> after the class name. The syntax is given below.
class class-name<T>
{
// …members
}
Note: We can specify n number of generic types with a class as follows:
class ClassName<T1, T2, T3,…..>
{
}
Generic class
A Generic class simply means that the items or functions in that class can be generalized
with the parameter (example T) to specify that we can add any type as a parameter in place
of T like Integer, Character, String, Double or any other user-defined type.

Example: Single type parameter


class Solution<T>
{
T data;
public static T getData(){
return data;
}
}

Example: Multiple type parameters


public class Pair<K, V> {

private K key;
private V value;

public Pair(K key, V value) {


this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}

Here in this example, we can use the object or instance of this class as many times with
different Parameters as T type. If we want the data to be of int type, the T can be replaced
with Integer, and similarly for String, Character, Float, or any user-defined type. The
declaration of a generic class is almost the same as that of a non-generic class except the
class name is followed by a type parameter section. The type parameter section of a generic
class can have one or more type parameters separated by commas.

Generic Method in Java


A generic method is a method that can take any kind of parameter. To create the generic
method we have to specify the generic type <T> before the return of the method. The syntax
to use a generic method is given below.
<T> returnType methodName(Parameters)
{
//statements
}
We can write a single generic method declaration that can be called with arguments of
different types. Based on the types of arguments passed to the generic method, the compiler
handles each method call appropriately.
Following are the rules to define Generic Methods

 All generic method declarations have a type parameter section indicated by angle
brackets <> that precedes the method’s return type.
 Each type parameter section can contain one or more type parameters separated by
commas. A type parameter or a type variable is an identifier that specifies a generic
type name.
 The type parameters can be used to declare the return type which is known as actual
type arguments.
 A generic method’s body is declared like that of any non-generic method. The point to
be noted is that type parameter can represent only reference types, and not primitive
types (like int, double, and char).

Bounded Types in Java


Java is a versatile and powerful programming language known for its strong type system.
One of the key features that enhances type safety and promotes code reusability is bounded
types. Bounded types allow developers to impose constraints on the types that can be used as
generic parameters in classes, interfaces, and methods. By defining these constraints,
developers can ensure that certain operations are supported and prevent errors at compile-
time.

A bounded type parameter is a generic type that is restricted to a specific subset of types. It
can be defined using the ‘extends’ keyword followed by the upper bound. The upper bound
can be a class or an interface, and it indicates that the generic type must be a subclass of the
specified class or implement the specified interface.
The syntax for defining a bounded type parameter is as follows:

class MyClass<T extends MyClassType> {


// ...
}
In the above example, T is the type parameter, and MyClassType is the upper bound. It
means that any type T used as a parameter must either be MyClassType or its subclass.
Bounded Type Examples:
To better understand bounded types, let's consider a few examples. Suppose we have a class
hierarchy representing different types of shapes:

class Shape {
// ...
}
class Circle extends Shape {
// ...
}
class Rectangle extends Shape {
// ...
}
Now, let's define a generic class called ShapeContainer that can hold objects of various
shapes:

class ShapeContainer<T extends Shape> {


private T shape;
public void addShape(T shape) {
this.shape = shape;
}
public T getShape() {
return shape;
}
}
In this example, the ShapeContainer class has a bounded type parameter T with an upper
bound of Shape. It ensures that only shapes or their subclasses can be used as the generic
argument.

By using bounded types, we can enforce compile-time type safety and ensure that only shape-
related operations can be performed on objects stored in ShapeContainer. For instance, if we
try to add a string or any other unrelated type, the compiler will generate an error.
Bounded Type with Multiple Bounds
Java also supports bounded types with multiple bounds, where a type parameter must satisfy
multiple constraints. Multiple bounds are specified using the & symbol, followed by the
interface or class names.

Consider the following example of a generic method that compares two objects:
class BoundedTypeExample {
public static <T extends Comparable<T> & MyInterface> int compare(T obj1, T obj2) {
return obj1.compareTo(obj2);
}
}
In this example, the generic method compare has a bounded type parameter T with two
bounds: Comparable<T> and MyInterface. It ensures that the type parameter T must
implement the Comparable interface and extend the MyInterface interface.

By using bounded types with multiple bounds, we can leverage the capabilities of multiple
interfaces or classes to provide more flexible and powerful functionality.

BoundedTypeExample.java

// Shape hierarchy
abstract class Shape {
public abstract void draw();
}
class Circle extends Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
class Rectangle extends Shape {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}
// Generic class with bounded type parameter
class ShapeContainer<T extends Shape> {
private T shape;
public void addShape(T shape) {
this.shape = shape;
}
public T getShape() {
return shape;
}
}
// Main class
public class BoundedTypeExample {
public static void main(String[] args) {
// Create a ShapeContainer for Circles
ShapeContainer<Circle> circleContainer = new ShapeContainer<>();
Circle circle = new Circle();
circleContainer.addShape(circle);
// Get the shape from the container
Circle retrievedCircle = circleContainer.getShape();
retrievedCircle.draw();
// Create a ShapeContainer for Rectangles
ShapeContainer<Rectangle> rectangleContainer = new ShapeContainer<>();
Rectangle rectangle = new Rectangle();
rectangleContainer.addShape(rectangle);
// Get the shape from the container
Rectangle retrievedRectangle = rectangleContainer.getShape();
retrievedRectangle.draw();
// Try to add a String to the ShapeContainer (results in a compile-time error)
// ShapeContainer<String> stringContainer = new ShapeContainer<>(); // Compile-time e
rror
// stringContainer.addShape("Invalid Shape"); // Compile-time error
}
}
Generic Class Hierarchy
Generic classes can be part of a class hierarchy in just the same way as a non-generic class.
Thus, a generic class can act as a superclass or be a subclass. The key difference between
generic and non-generic hierarchies is that in a generic hierarchy, any type arguments needed
by a generic superclass must be passed up the hierarchy by all subclasses. This is similar to
the way that constructor arguments must be passed up a hierarchy.
Hierarchical classifications are allowed by Inheritance. Superclass is a class that is
inherited. The subclass is a class that does inherit. It inherits all members defined by super-
class and adds its own, unique elements. These uses extends as a keyword to do so.
Sometimes generic class acts like super-class or subclass. In Generic Hierarchy, all sub-
classes move up any of the parameter types that are essential by super-class of generic in
the hierarchy. This is the same as constructor parameters being moved up in a hierarchy.
Example 1: Generic super-class

// Java Program to illustrate generic class hierarchies


// Importing all input output classes
import java.io.*;
// Helper class 1
// Class 1 - Parent class
class Generic1<T>
{
// Member variable of parent class
T obj;
// Constructor of parent class
Generic1(T o1)
{
obj = o1;
}

// Member function of parent class that returns an object


T getobj1()
{
return obj;
}
}

// Helper class 2
// Class 2 - Child class
class Generic2<T, V> extends Generic1<T> {
// Member variable of child class
V obj2;
Generic2(T o1, V o2)
{
// Calling super class using super keyword
super(o1);

obj2 = o2;
}

// Member function of child class


// that returns an object
V getobj2() { return obj2; }
}

// Class 3 - Main class


class GFG {

// Main driver method


public static void main(String[] args)
{
// Creating Generic2 (sub class) object
// Custom inputs as parameters
Generic2<String, Integer> x= new Generic2<String, Integer>("value : ",100);
// Calling method and printing
System.out.println(x.getobj1());
System.out.println(x.getobj2());
}
}
Output
value :
100
Note: A subclass can freely add its own type parameters, if necessary.

Example 2: Non-generic sub-class of generic sub-class


import java.io.*;
// non-generic super-class
class NonGen {
int n;
NonGen(int i) { n = i; }
int getval() { return n; }
}
// generic class sub-class
class Gen<T> extends NonGen {
T obj;
Gen(T a, int i)
{
super(i);
obj = a;
}
T getobj() { return obj; }
}

class GFG {
public static void main(String[] args)
{
Gen<String> w = new Gen<String>("Hello", 2024);
System.out.println(w.getobj() + " " + w.getval());
}
}

Output
Hello 2024
Generic Interfaces
Generic Interfaces in Java are the interfaces that deal with abstract data types. Interface
help in the independent manipulation of java collections from representation details. They
are used to achieving multiple inheritance in java forming hierarchies. They differ from the
java class. These include all abstract methods only, have static and final variables only. The
only reference can be created to interface, not objects. Unlike class, these don’t contain any
constructors, instance variables. This involves the “implements” keyword. These are similar
to generic classes.

We can create a generic interface by specifying the <T> after the interface name. The syntax
is given below.
Interface InterfaceName<T>
{
//members
}

The benefits of Generic Interface are as follows:


1. This is implemented for different data types.
2. It allows putting constraints i.e. bounds on data types for which interface is
implemented.
Syntax:
interface interface-name < type-parameter-list > {//....}
class class-name <type-parameter-list> implements interface-name <type-arguments-list>
{//...}
Implementation: The following example creates an interface ‘MinMax’ which involves
very basic methods such as min(), max() just in order to illustrate as they return
the minimum and maximum values of given objects.
Example
// Java Program to illustrate Generic interfaces

// Importing java input output classes


import java.io.*;

// An interface that extends Comparable


interface MinMax<T extends Comparable<T> > {
// Declaring abstract methods
// Method with no body is abstract method
T min();
T max();
}

// Class 1 - Sub-class
// class extending Comparable and implementing interface
class MyClass<T extends Comparable<T> >
implements MinMax<T> {

// Member variable of 'MyClass' class


T[] values;

// Constructor of 'MyClass' class


MyClass(T[] obj) { values = obj; }

// Now, defining min() and max() methods


// for MimMax interface computation

// Defining abstract min() method


public T min()
{
// 'T' is typename and 'o1' is object_name
T o1 = values[0];

// Iterating via for loop over elements using


// length() method to get access of minimum element
for (int i = 1; i < values.length; i++)
if (values[i].compareTo(o1) < 0)
o1 = values[i];

// Return the minimum element in an array


return o1;
}

// Defining abstract max() method


public T max()
{
// 'T' is typename and 'o1' is object_name
T o1 = values[0];
// Iterating via for loop over elements using
// length() method to get access of minimum element
for (int i = 1; i < values.length; i++)
if (values[i].compareTo(o1) > 0)
o1 = values[i];
// Return the maximum element in an array
return o1;
}
}

// Class 2 - Main class


// Implementation class
class GFG {
// Main driver method
public static void main(String[] args)
{
// Custom entries in an array
Integer arr[] = { 3, 6, 2, 8, 6 };
// Create an object of type as that of above class
// by declaring Integer type objects, and
// passing above array to constructor
MyClass<Integer> obj1 = new MyClass<Integer>(arr);

// Calling min() and max() methods over object, and

// printing the minimum value from array elements


System.out.println("Minimum value: " + obj1.min());

// printing the maximum value from array elements


System.out.println("Maximum value: " + obj1.max());
}
}

Java
Output
Minimum value: 2
Maximum value: 8
Output explanation: Here interface is declared with type parameter T, and its upper bound
is Comparable which is in java.lang. This states how objects are compared based on type of
objects. Above T is declared by MyClass and further passed to MinMax as MinMax needs a
type that implements Comparable and implementing class(MyClass) should have same
bounds.
Restrictions in Generics
There are a few restrictions associated with the usage of generics that are mentioned below:
1. In Java, generic types are compile time entities. The runtime execution is possible only if it
is used along with raw type.

2. Primitive type parameters is not allowed for generic programming.


For example: Stack<int> is not allowed.

3. For the instances of generic class throw and catch instances are not allowed.
For example:
public class Test<T> extends Exception
{
//code // Error: can't extend the Exception class
}
We cannot create generic exception classes and cannot extend Throwable (which is
superior to all exception classes in the exception class hierarchy).
4. Instantiation of generic parameter T is not allowed.
For example:
new T() //Error
new T[10]

5. Arrays of parameterized types are not allowed.


For example:
new Stack<String>[10];//Error

6. Static fields and static methods with type parameters are not allowed.

Detailed notes on Restrictions

1. Type parameters cannot be instantiated


It is not possible to create an instance of a type parameter. The compiler doesn’t know what
type of object to create, their T is simply a placeholder. Below is the example code that
shows the invalid creation of an instance of T which leads to a compilation error.
Example:
// Java Program to demonstrate Generic class creation with the type parameter T.
class GenType<T> {
private T data;

GenType(T data)
{
// parameterized constructor
this.data = data;
}

T getData() { return data; }

// main function
public static void main(String[] args)
{
GenType<Integer> gt = new GenType<>(10);
System.out.println(gt.getData());
}
}
Output
10
2. Restriction on using static members
It is illegal for the static members (variables, methods) to use a type parameter declared by
the enclosing class. We will use the above example in this case to make the variable and
method static, this also leads to compile time error. Let’s see in detail what the compiler
says:
Example:

// Java Program to demonstrate Generic class creation with type parameter T.


class GenType<T> {
// illegal to make a variable as static.
// static T data; //compile-time Error: Cannot make a
// static reference to the non-static type T
T data;

GenType() {}

GenType(T data)
{
// parameterized constructor
this.data = data;
}

T getData() { return data; }

public static void main(String[] args)


{
GenType<String> gt
= new GenType<>("Hello.How are you!!");
System.out.println(gt.getData());
}
}

Output
Hello.How are you!!

3. Generic array Restrictions


There are two important generic restrictions that applied to arrays.
1. We cannot instantiate an array whose element type is a type parameter. There is no way
for the compiler to know what type of array to actually create. However, we can pass a
reference to a type-compatible array as a parameter and assign it to the object created.
This is acceptable because the array passed as a parameter has a known type, which will
be of the same type as T at the time of object creation.
2. We cannot create an array of type-specific generic references. The reason could be the
same as in the above case the compiler doesn’t know what kind of array to create. This
can be resolved by using a wildcard, which is better than using a raw type because at
least some type checking will be done.

Example:
// Java Program to implement Generic array Restrictions
import java.util.Arrays;

public class GenArray<T extends Number> {


T obj;
T arr[];
GenArray(T o, T[] vals)
{
this.obj = o;
System.out.println("value: " + obj);
// Invalid
// arr = new T[10];
// compile-time Error:Cannot
// create a generic array of T
// But, this is allowed because we are assigning the
// reference to the existing array.
arr = vals;
}

T[] getArray() { return arr; }

public static void main(String[] args)


{
Integer[] Array = { 1, 2, 3, 4, 5 };
GenArray<Integer> obj1= new GenArray<Integer>(10, Array);

System.out.println(Arrays.toString(obj1.getArray()));
// illegal to create an array of type-specific generic references.
}
}
Output
value: 10
[1, 2, 3, 4, 5]

4. Primitive data types are not used with the generic types
We will get the compilation error if we use the primitive data types at the time of object
creation. The following code demonstrates the situation:
Example:
// Java Program to implement
// Primitive data types are not
// used with the generic types
import java.io.*;
// Driver Class
class Box<T> {
private T data;
Box(T data) { this.data = data; }
T getData() { return data; }
public static void main(String[] args)
{
Box<Integer> b1 = new Box<Integer>(10);
// use of wrapper classes
Box<String> b2 = new Box<String>("Geek For Geeks");
System.out.println("value: " + b1.getData());
System.out.println("value: " + b2.getData());
}
}
Output
value: 10
value: Geek For Geeks

5. Generic Exception Restriction


We cannot create generic exception classes and cannot extend Throwable (which is
superior to all exception classes in the exception class hierarchy) . We will use the above
example to understand this, although we get an error if we execute this code, give it a try.
Example:
// Java Program to implement
// Generic Exception Restriction
import java.io.*;
// generic class cannot extend throwable
class Box<T> extends Throwable {
private T data;
Box(T data) { this.data = data; }
T getData() { return data; }
// main function
public static void main(String[] args)
{
Box<Integer> b1 = new Box<Integer>(10);
System.out.println("value: " + b1.getData());
}
}

Output:
Error: Could not find or load main class Box
Caused by: java.lang.ClassNotFoundException: Box

INTRODUCTION TO LAMBDA EXPRESSIONS

Lambda expression is a new and important feature of Java which was included in Java SE 8.
A lambda expression is a short block of code which takes in parameters and returns a value.
Lambda expressions are similar to methods, but they do not need a name and they can be
implemented right in the body of a method.

A lambda expression contains three components:

o A parameter list: It can be empty or non-empty as well.


o An arrow symbol(->): It is used to link arguments-list and body of expression.
o Body: It contains expressions and statements for lambda expression.
Expressions are limited. They have to immediately return a value, and they cannot
contain variables, assignments or statements such as if or for. In order to do more
complex operations, a code block can be used with curly braces. If the lambda expression
needs to return a value, then the code block should have a return statement.

Syntax:
The simplest lambda expression contains a single parameter and an expression:
(argument-list) -> {body} (or) parameter -> expression

To use more than one parameter, wrap them in parentheses:


(parameter1, parameter2) -> expression
(parameter1, parameter2) -> { code block }

How to write simple methods as lambda expression

Here is a simple method that displays a message “Hello World”.

public void hello(){ //without lambda expression

System.out.println("Hello World");
}

To convert this method to a lambda expression, remove the access specifier “public”, return
type “void” and method name “hello” and write it as :

() -> {System.out.println("Hello World");}

Here is another method example that adds two numbers and returns their sum :
int add(int x, int y){
return x+y;
}

This method can be converted to lambda expression as :

(int x, int y) -> {return x+y;}

Block Lambda Expressions

The lambda expressions that have a single expression in the body are known an Expression
lambdas. Java also supports second type of lambda expression where the lambda expression
body contains a block of code rather than just a single statement. This type of lambda
expression are called Block lambdas.

Block Lambda contains many operations that work on lambda expressions as it allows the
lambda body to have many statements. This includes variables, loops, conditional
statements like if, else and switch statements, nested blocks, etc. This is created by
enclosing the block of statements in lambda body within braces {}. This can even have a
return statement i.e. return value.

 In the block lambda expression, we must explicitly use a return statement to return a
value.
 A lambda block contains multiple statements.
 It expands the type of operations that can be performed with a lambda expression.
 The multiple statements are enclosed within braces {}.
 In a block lambda expression, a return statement must be explicitly used to return a value.
 The lambda function body can contain expressions over multiple lines if encased in curly
braces.

Creating Block Lambda Expressions


To create a block lambda expression, we can enclose the body within braces as shown below

MathFunctions mathFunc = (n) -> {


//block of code
};
In the below program, we use a block lambda expression to calculate and return the factorial
of a number.

public interface FindFact {


int factorial(int n);
}

The FindFact functional interface declares the factorial method. This method takes an integer
argument and returns an integer value.

public class BlockLambdaDemo {


public static void main(String[] args) {
FindFact mathFunc = (n) -> {
int fact = 1;
for(int i =1;i <= n;i++){
fact = i * fact;
}
return fact;
};
System.out.println("Factorial(5) = " + mathFunc.factorial(5));
}
}
In the above example, we created a block lambda expression and declared a variable fact in it
to store factorial value. Then we added a for loop, that evaluates factorial of a number and
returns it.

The return statement just returns the control from the lambda; it does not cause an enclosing
method to return.
Functional Interfaces
A functional interface is an interface that contains only one abstract method. They can
have only one functionality to exhibit. From Java 8 onwards, lambda expressions can be
used to represent the instance of a functional interface. A functional interface can have any
number of default methods.
In Functional interfaces, there is no need to use the abstract keyword as it is optional to use
the abstract keyword because, by default, the method defined inside the interface is abstract
only. We can also call Lambda expressions as the instance of functional interface .
// Java program to demonstrate lambda expressions to
// implement a user defined functional interface.

@FunctionalInterface
interface Square {
int calculate(int x);
}
class Test {
public static void main(String args[])
{
int a = 5;

// lambda expression to define the calculate method


Square s = (int x) -> x * x;
// parameter passed and return type must be
// same as defined in the prototype
int ans = s.calculate(a);
System.out.println(ans);
}
}
Output
25
Generic functional interfaces
A lambda expression, itself, cannot specify type parameters. Thus, a lambda expression
cannot be generic. However, the functional interface associated with a lambda expression can
be generic. In this case, the target type of the lambda expression is determined, in part, by the
type argument or arguments specified when a functional interface reference is declared.
Syntax
interface SomeFunc {
T func(T t);
}
Example
interface MyGeneric<T> {
T compute(T t);
}
public class LambdaGenericFuncInterfaceTest {
public static void main(String args[]) {
MyGeneric<String> reverse = (str) -> { // Lambda Expression
String result = "";
for(int i = str.length()-1; i >= 0; i--)
result += str.charAt(i);
return result;
};
MyGeneric<Integer> factorial = (Integer n) -> { // Lambda Expression
int result = 1;
for(int i=1; i <= n; i++)
result = i * result;
return result;
};
System.out.println(reverse.compute("Lambda Generic Functional Interface"));
System.out.println(factorial.compute(5));
}
}
Output
ecafretnI lanoitcnuF cireneG adbmaL
120

Building a lambda expression that returns a numeric value


The example demonstrates:

 Declaration of a generic (template) functional interface IValue<T>, operating on


the T type; creating a lambda expression and using it for the numeric type Float. The
lambda expression implements the generic IValue<T> functional interface.
// Generalized (template) functional interface.

// This interface operates on the generic type T.


interface IValue<T> {
T getValue();
}

public class TrainLambda02 {


public static void main(String[] args) {
// Declare a reference to the IValue functional interface that will operate on the Float type
IValue<Float> ref;

// Set a lambda expression with reference to the type Float


ref = () -> 3.1415f; // return the value of float type
// Invoke method getValue(), which returns 3.1415f
float v = ref.getValue(); // v = 3.1415
System.out.println("v = " + v);
}
}
The result of the program
v = 3.1415

Passing lambda expressions as arguments

You might also like