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

generics

Uploaded by

neha praveen
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views

generics

Uploaded by

neha praveen
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 14

Bounded Types in Java

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. In this article, we'll delve into
bounded types in Java and explore how they can be leveraged to write more robust and flexible code.
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.
Syntax:

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.
Here's a complete Java program that demonstrates the use of bounded types with input and output:
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 error
// stringContainer.addShape("Invalid Shape"); // Compile-time error
}
}
Output:
Drawing a circle
Drawing a rectangle
Bounded types in Java provide a powerful mechanism to enforce constraints on generic types,
ensuring type safety and promoting code reuse. By specifying upper bounds, developers can restrict
the range of types that can be used as generic parameters, leading to more robust and maintainable
code. Whether it's restricting types within a class hierarchy or imposing constraints from multiple
interfaces, bounded types enable developers to write more expressive and reliable code.
By understanding and utilizing bounded types effectively, Java developers can harness the full
potential of the language's type system and build high-quality software systems.

Wildcards in Java

The wildcard can be used in a variety of situations such as the type of a parameter, field, or local
variable; sometimes as a return type. Unlike arrays, different instantiations of a generic type are not
compatible with each other,
Types of wildcards in Java
1. Upper Bounded Wildcards:
These wildcards can be used when you want to relax the restrictions on a variable. For example, say
you want to write a method that works on List < Integer >, List < Double >, and List < Number >, you
can do this using an upper bounded wildcard.
To declare an upper-bounded wildcard, use the wildcard character (‘?’), followed by the extends
keyword, followed by its upper bound.
public static void add(List<? extends Number> list)
// Java program to demonstrate Upper Bounded Wildcards

import java.util.Arrays;
import java.util.List;

class WildcardDemo {
public static void main(String[] args)
{

// Upper Bounded Integer List


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

// printing the sum of elements in list


System.out.println("Total sum is:" + sum(list1));

// Double list
List<Double> list2 = Arrays.asList(4.1, 5.1, 6.1);

// printing the sum of elements in list


System.out.print("Total sum is:" + sum(list2));
}

private static double sum(List<? extends Number> list)


{
double sum = 0.0;
for (Number i : list) {
sum += i.doubleValue();
}

return sum;
}
}

Output
Total sum is:22.0
Total sum is:15.299999999999999

Explanation:
In the above program, list1 and list2 are objects of the List class. list1 is a collection of Integer and
list2 is a collection of Double. Both of them are being passed to method sum which has a wildcard
that extends Number. This means that list being passed can be of any field or subclass of that field.
Here, Integer and Double are subclasses of class Number.
2. Lower Bounded Wildcards:
It is expressed using the wildcard character (‘?’), followed by the super keyword, followed by its
lower bound: <? super A>.
Syntax: Collectiontype <? super A>

// Java program to demonstrate Lower Bounded Wildcards

import java.util.Arrays;
import java.util.List;

class WildcardDemo {
public static void main(String[] args)
{
// Lower Bounded Integer List
List<Integer> list1 = Arrays.asList(4, 5, 6, 7);

// Integer list object is being passed


printOnlyIntegerClassorSuperClass(list1);

// Number list
List<Number> list2 = Arrays.asList(4, 5, 6, 7);

// Integer list object is being passed


printOnlyIntegerClassorSuperClass(list2);
}

public static void printOnlyIntegerClassorSuperClass(


List<? super Integer> list)
{
System.out.println(list);
}
}
Output
[4, 5, 6, 7]
[4, 5, 6, 7]
Explanation:
Here arguments can be Integer or superclass of Integer(which is Number). The method
printOnlyIntegerClassorSuperClass will only take Integer or its superclass objects. However, if we
pass a list of types Double then we will get a compilation error. It is because only the Integer field or
its superclass can be passed. Double is not the superclass of Integer.
Note: Use extend wildcard when you want to get values out of a structure and super wildcard when
you put values in a structure. Don’t use wildcard when you get and put values in a structure. You can
specify an upper bound for a wildcard, or you can specify a lower bound, but you cannot specify both.
Generic Interface in Java

Similar to generic class, when an interface is defined using generic type parameter, it is called generic
interface in Java.
When a generic interface is implemented by either a generic class or non-generic class, the type
argument must be specified.
The generic interface has main two advantages that are as:
A generic interface can be implemented for different types of data.
It allows placing constraints (i.e. bounds) on the types of data for which interface can be implemented.

The general syntax to write an implementation class that implements the above interface, as follows:
class class-name<T> implements interface-name<T>
{
public void method-name(T t)
{
// Write code for this method as per requirement.
}
}
JAVACopy code
Here, T represents a generic parameter that specifies any data type.

Let’s take an example program where we will create a generic interface and implementation class.
Then the implementation class will be used to display the class name of object passed to the method.
Example 2:
public interface Fruit<T>
{
// This method can accept any type of object.
public void taste(T fruit);
}
public class AnyFruit<T> implements Fruit<T>
{
public void taste(T fruit)
{
// Get the class name of object passed to this method.
String fruitname = fruit.getClass().getName();
System.out.println(fruitname);
}
}
public class Banana {

}
public class Orange {

}
public class GenericClass {
public static void main(String[] args)
{
// Create an object of class Banana and pass it to class AnyFruit.
Banana b = new Banana();
AnyFruit<Banana> fruit1 = new AnyFruit<Banana>();
fruit1.taste(b);
// Create an object of Orange and pass it to class AnyFruit.
Orange o = new Orange();
AnyFruit<Orange> fruit2 = new AnyFruit<Orange>();
fruit2.taste(o);
}
}
JAVACopy code
Output:
genericsProgram.Banana
genericsProgram.Orange

Raw Types in java


A raw type is the name of a generic class or interface without any type arguments. For example, given
the generic Box class:
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
To create a parameterized type of Box<T>, you supply an actual type argument for the formal type
parameter T:
Box<Integer> intBox = new Box<>();
If the actual type argument is omitted, you create a raw type of Box<T>:
Box rawBox = new Box();
Therefore, Box is the raw type of the generic type Box<T>. However, a non-generic class or interface
type is not a raw type.
Raw types show up in legacy code because lots of API classes (such as the Collections classes) were
not generic prior to JDK 5.0. When using raw types, you essentially get pre-generics behavior —
a Box gives you Objects. For backward compatibility, assigning a parameterized type to its raw type
is allowed:
Box<String> stringBox = new Box<>();
Box rawBox = stringBox; // OK
But if you assign a raw type to a parameterized type, you get a warning:
Box rawBox = new Box(); // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox; // warning: unchecked conversion
You also get a warning if you use a raw type to invoke generic methods defined in the corresponding
generic type:
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8); // warning: unchecked invocation to set(T)
The warning shows that raw types bypass generic type checks, deferring the catch of unsafe code to
runtime. Therefore, you should avoid using raw types.
The Type Erasure section has more information on how the Java compiler uses raw types.
Unchecked Error Messages
As mentioned previously, when mixing legacy code with generic code, you may encounter warning
messages similar to the following:
Note: Example.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
This can happen when using an older API that operates on raw types, as shown in the following
example:
public class WarningDemo {
public static void main(String[] args){
Box<Integer> bi;
bi = createBox();
}

static Box createBox(){


return new Box();
}
}
The term "unchecked" means that the compiler does not have enough type information to perform all
type checks necessary to ensure type safety. The "unchecked" warning is disabled, by default, though
the compiler gives a hint. To see all "unchecked" warnings, recompile with -Xlint:unchecked.
Recompiling the previous example with -Xlint:unchecked reveals the following additional
information:
WarningDemo.java:4: warning: [unchecked] unchecked conversion
found : Box
required: Box<java.lang.Integer>
bi = createBox();
^
1 warning
To completely disable unchecked warnings, use the -Xlint:-unchecked flag.
The @SuppressWarnings("unchecked") annotation suppresses unchecked warnings. If you are
unfamiliar with the @SuppressWarnings syntax,
Generic Class Hierarchies in Java

Generic means parameterized types introduced in java5. These help in creating classes, interfaces,
methods, etc. A class or method which works on parameterized type known as “generic class ” or
“generic method”. Generics is a combination of language properties of the definition and use of
Generic types and methods. Collections were used before Generics which holds any type of objects
i.e. non-generic. Using Generics, it has become possible to create a single class, interface, or method
that automatically works with all types of data(Integer, String, Float, etc). It has expanded the ability
to reuse the code safely and easily. Generics also provide type safety (ensuring that an operation is
being performed on the right type of data before executing that operation).
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

// 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
Java

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 o1, int i)
{
super(i);
obj = o1;
}
T getobj() { return obj; }
}

class GFG {
public static void main(String[] args)
{

Gen<String> w = new Gen<String>("Hello", 2021);

System.out.println(w.getobj() + " " + w.getval());


}
}

Output
Hello 2021

You might also like