Skip to content
Avinandan Bose edited this page Mar 26, 2023 · 1 revision

JavaGeneric

This is all about Java Generics

Java includes support for writing generic classes and methods that can operate on variety of data types while often avoiding the need for explicit casts. The generics framework allow us to define a class in terms of a set of formal type parameters, which can then be used as the declared type for variables , parameters, and return values within the class definition. Those formal type parameters are later specified when using the generic class as a type elsewhere in a program. Generics are a feature of Java that allows us to create classes and methods that work with different types of data.Generics are a way to make our code more flexible and reusable.

Diamond Operator: In Java Generics introduced " < > " Diamond Operator which is a Java 7 feature , to simplify the use of generics when creating an object. Along with it , introduced Type Erasure technique, as discussed below.

Note: As language Java is based on OOP based , java.lang.Object is the root of the class hierarchy, and we can create any object of Object class and every class we create or predefined gets instantiated under Object class , hence Object class is Super class of every class.

Similarly Byte(java.lang.Byte), String (java.lang.String), Integer(java.lang.Integer) , Float(java.lang.Float), Double(java.lang.Short) etc. are wrapper classes comes from Java's Lang package as they not only wraps , put a cast over a value and change its type but also perform some specific functions - gives a pure OOP concept .

On the above example , String , Integer , Double are such wrapper classes that puts a narrow explicit cast : Object to String/Integer/Double . The above example is the Representation a generic pair using a classic style in which code become used to with such explicit casts .

illegal; Compiler Error
-------------------------
String stock = p1.getFirst();
String stock2 = p2.getFirst();
Double price = p1.getSecond();
Integer price2 = p2.getSecond();

Hence using Java Generic Framework we can remove such explicit casts. In the framework we have to use a pair class using formal type of parameters to represent the two relevant types in our composition. An implementation using this framework is given in the Code below:

class Pair <A,B> {} : Here A and B are formal type parameters.
Pair<Integer, String> p1 :  Here Integer , String are Actual Parameters.

So, we can write :

p1 = new Pair<Integer, String>(1, "apple");
1.  Type 1 Representation of Java Generic Framework:
Pair<Integer, String> p1 ;
p1 = new Pair<Integer, String>(1, "apple"); //Generic Type Parameters are explicitly specified between Angle Bracket.

2. Also, We Can subsequestly instantiate the generic class using the following syntax:

Pair<Integer, String> p3 ;
p3 = new Pair<>(3, "orange"); //Rely on type interference.

3. Classic type of Instantiation of Object:

Pair<Integer, String> p2 ;
p2 = new Pair(2, "pear"); // Classic Type

1. Type Inference


p3 = new Pair<>(3, "orange");

After the new operator , we provide the name of the generic class , then an empty set of angle brackets(known as "diamond") and finally the parameters to the constructor. An instance of the generic class is created , with the actual types for the formal type parameters determined based upon the original declaration of the variable to which it is assigned . This process is known as Type Inference and was introduced to the generics framework in JAVA SE 7 .

2. Generic Type Parameters Are Explicitly Specified Between Angle Brackets During Instantiation.


p3 = new Pair<Integer, String>(3, "orange");

The above style existed prior to JAVA SE 7 , in which the generic type parameters are explicitly specified between angle brackets during instantiation.

3. Classic Style.


p3 = new Pai(3, "orange");

However , it is important that one of the above styles used . If angle brackets are entirely omitted as shown above, this reverts to the classic style , with Object automatically used for all generic type parameters and resulting in a compiler warning to a variable with more specific types.

Screenshot (177)

Automatic-Unboxing of Wrapper Type To Primitive Data Type

 int price = p1.getFirst();
 double price2 = p2.getSecond();

Here automatic unboxing occurs , where Integer and Double wrapper type , convert to their primitive value of int , double primitive data type.

Generics and Arrays

Generic Methods

class A2<T>{
 T data;

 A2(T item){

     data = item;

 }

 int view(){

     return(Integer)data;

 }

 public static void main(String[] args) {
     A2<Integer> p1;
     p1 = new A2<>( 2);

     System.out.println(p1.view());

 }

}


  • Generic Methods- Eg-12
  • class A2<T>{
        T data;
    
        A2(T item){
    
            data = item;
    
        }
    
        T view(){
    
            return data;
    
        }
    
        public static void main(String[] args) {
            A2<Integer> p1;
            p1 = new A2<>( 2);
    
            System.out.println(p1.view());
    
        }
    
    }
    
    
    

  • Generic Methods- Eg-13
  • class A2<S,T>{
        T data1;
        S data2;
    
        A2(T item1, S item2){
    
            data1 = item1;
            data2 = item2;
    
        }
    
        T view1(){
    
            return data1;
    
        }
    
        S view2(){
    
            return data2;
    
        }
    
        public static void main(String[] args) {
            A2<Integer, Integer> p1;
            p1 = new A2<>(  10, 2);
    
            System.out.println(p1.view1());
            System.out.println(p1.view2());
    
        }
    
    }
    
    
    

  • Generic Methods- Eg-14
  • class A2<S,T>{
        T data1;
        S data2;
    
        A2(T item1, S item2){
    
            data1 = item1;
            data2 = item2;
    
        }
    
        T view1(){
    
            return data1;
    
        }
    
        S view2(){
    
            return data2;
    
        }
    
        public static void main(String[] args) {
           A2<Integer, String> p1;
            p1 = new A2<>(   "Hello",2);
    
            System.out.println(p1.view1());
            System.out.println(p1.view2());
    
        }
    
    }
    
    
    

  • Generic Methods- Eg-15
  • class A2{
    
    
        public static <A> void view(A item) {
            System.out.println("The value is: " + item);
    
        }
    
        public static <B> String view2(B item) {
            return (String) item;
    
        }
    
    
    
        public static void main(String[] args) {
    
    
            view(2);
            System.out.println(view2("Hello"));
    
        }
    
    }
    

    Another :

    class A2<T>{
    
        <A> T view(T data, A item) {
             System.out.print(item);
            return data;
    
        }
    
    
    
        public static void main(String[] args) {
    
            A2<Integer> a2 = new A2<>();
    
            System.out.println(a2.view(12, "Hello"));
    
        }
    
    }
    

    Note :

    class A2<T>{
    
       <T> T view(T data, T item) {
             System.out.print(item);
            return data;
    
        }
    }
    
    
    Here T represents the Type T of Generic Method but not of Parameterized Class.
         
    Hence:
         
    class A2<T>{
    
        <T> T view(T data, T item) {
             System.out.print(item);
            return data;
    
        }
    
    
    
        public static void main(String[] args) {
    
            A2<Integer> a2 = new A2<>();
    
            System.out.println(a2.view("A", "Hello"));
    
        }
    
    }
          
          

    But if :

          
      class A2<T>{
    
      <A> T view(T data, A item) {
    
            return item;
    
      }
        
     :Will generate error as return type now needs generic class's T type as:
        
     :T view() i.e. return Generric class T type,: 
     :though Generic method have an extended type A which has scope to the Method only.:  
          

  • Generic Methods- Eg-16
  • Generic Methods- Eg-17
  • Generic Methods- Eg-18
  • Generic Methods AND Generic Arrays

    Generic Type Overloading (Static Polymorphism )

    • 1. Type 1 [Using Class Generics]
      • 1.a. Difference in the number of parameters passed with methods having the same name .
       
     Note:
      
      : As the Class is Generic :
      :b ) Overloading by Difference in Sequence or Order of Parameter (Is Not Possible ):
      
      Eg: 
      class A<T,K>{
      
      void swap(T a, K b ){
      
      }
      
       void swap(K b, T b ){
      
      }
      //Is Not Possible
      
      :c ) Overloading by Difference in datatypes of parameters passed in methods (Is Not Possible ):
      
       Eg: 
      class A<T,K>{
      
      void swap(T a, T b ){
      
      }
      
       void swap(K a, K b ){
      
      }
      
      //Is Not Possible
      

  • 2. Type 2 [Using static and nonstatic Generics Method]
  • Generic Class Hierarchies

    Generics 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 super-class or be a subclass. The key difference between generic and non-generic is that in a generic hierarchy, any type arguments needed by a generic super-class must be passed up the hierarchy by all subclasses. This is similar to the way that constructor arguments must be passed up a hierarchy.

      1. Using a Generic Superclass

        
        class Gen<T> {
           T obj;
        
           Gen(T o) {
               obj = o;
           }
        
           T getObj() {
               return obj;
           }
        
        }
        
        public class GenHierarchy1 <T> extends Gen<T> {
           GenHierarchy1(T o) {
               super(o);
           }
        
           public static void main(String args[]) {
               GenHierarchy1<String> x = new GenHierarchy1<String>("Generics Test");
               System.out.println(x.getObj());
           }
        
        
        

        
        class GenHierarchy1 <T> extends Gen<T>
        
        
        

        Here type 'T' is specified by Gen2 and is also passed to Gen in the extends clause.

        This means that whatever type is passed to Gen2 will also be passed to Gen.

        
        
         GenHierarchy1<String> x = new GenHierarchy1<String>("Generics Test");
        
        

        It passes String as type parameter to Gen. Thus the obj inside the Gen portion of Gen2 will be of type Integer.

        
        class Gen<T> {
            T obj;
        
            Gen(T o) {
                obj = o;
            }
        
            T getObj() {
                return obj;
            }
        
        }
        
        public class GenHierarchy2 extends Gen<String> {
            <T> GenHierarchy2(T o) {
                super((String)o);
            }
        
            public static void main(String args[]) {
                GenHierarchy2 x = new GenHierarchy2("Generics Test");
                System.out.println(x.getObj());
            }
            
        }
        
        
        

        Notice also that GenHierarchy2 does not use the type parameter T except to support the Gen superclass. Thus, even if a subclass of a generic superclass would otherwise not need to be generic, it still must specify the type parameter(s) required by its generic superclass. If type "T" is not specified then we have to specify the type of class Gen whether Integer, String etc. during Inheritance and then compiler will ask to have a constructor of Sub Class as super Generic class have a constructor and the SubClass's constructor will have a cast of Type "< T >" as shown above.

        A subclass is free to add its own type parameters, if needed.

        
        class Gen<T> {
            T obj;
        
            Gen(T o) {
                obj = o;
            }
        
            T getObj() {
                return obj;
            }
        
        }
        
        public class GenHierarchy3<V> extends Gen<V> {
        
            GenHierarchy3(V o) {
                super(o);
                
            }
        
            public static void main(String args[]) {
                GenHierarchy3<String> x = new GenHierarchy3<String>("Generics Test");
                System.out.println(x.getObj());
            }
            
        }
        
        
        

        Here if Sub Class have single Type parameter and if it changes , then the same type must be put to the "Type" to the Parent class.

        
        class Gen<T> {
            T obj;
        
            Gen(T o) {
                obj = o;
            }
        
            T getObj() {
                return obj;
            }
        
        }
        
         public class GenHierarchy4<V,T> extends Gen<T> {
            V ob2;
        
            GenHierarchy4(T o, V o2) {
                super(o);
                ob2 = o2;
                
            }
        
            V getOb2() {
                return ob2;
            }
        
            public static void main(String args[]) {
                GenHierarchy4<Integer, String> x = 
        		new GenHierarchy4<>("Generics Test", 88);
                System.out.println(x.getObj());
                System.out.println(x.getOb2());
            }
            
        }
        
        
        
        

        Here, T is the type passed to Gen, and V is the type that is specific to GenHierarchy4. V is used to declare an object called ob2, and as a return type for the method getob2( ). In main( ), a Gen2 object is created in which type parameter T is String, and type parameter V is Integer.

      2. A Generic Subclass

        
        class NonGen {... }
        
        class Gen<T> extends NonGen { ... }
        
        class HierDemo {
        
         public static void main(String args[]) {
            
            Gen<String> x = new Gen<String>();
           
        }
        
        
        

        In the program, notice how Gen inherits NonGen in the following declaration:

        
        class Gen<T> extends NonGen {...}
        
        

        Because NonGen is not generic, no type argument is specified. Thus, even though Gen declares the type parameter T, it is not needed by (nor can it be used by) NonGen. Thus, NonGen is inherited by Gen in the normal way. No special conditions apply.

      3. Run-Time Comparisons within a Generic Hierarchy

        
        class Gen3<T>{ ... }
           
        class Gen4<T> extends Gen3<T>{ ... }
           
        class HierDemo{ 
        
           Gen3<Integer> iOb = new Gen3<Integer>();
               Gen4<Integer> iOb2 = new Gen4<Integer>();
               Gen4<String> strOb2 = new Gen4<String>();
           
           if(iOb2 instanceof Gen4<Integer>){...}
        
               if (iOb2 instanceof Gen3<Integer>) {...}
        
               if(strOb2 instanceof Gen4<String>){...}
        
               if (strOb2 instanceof Gen3<String>) {...}
        
               if (iOb instanceof Gen3<Integer>) {...}
        
               if (iOb instanceof Gen4<Integer>) {...}
                  
           
           }
        
        

        Explanation: The program performs these instanceof tests on the type of Objects through instanceof which shows RunTime Comparisons between Objects with a specified type.

      4. Casting

        We can cast one instance of a generic class into another only if the two are otherwise compatible and their type arguments are the same. For example, assuming the foregoing program, this cast is legal:

        
        ((Gen<Integer>) iOb2 ).obj = 100; //Legal
        ((Gen<String>) strOb2).obj = "Generics Test 2";// Legal
        ((Gen<Integer>) iOb).obj = 1000;// Legal
        
        

        iOb2 includes an instance of Gen < Integer > , strOb2 includes an instance of Gen < String > and iOb is an instance of Gen < Integer > . But, this cast:

        
        ((Gen<Long>) iOb2).obj = 101; Illegal
        
        

        is not legal because iOb2 is not an instance of Gen < Long >.

      5. Overriding Methods in a Generic Class

        
        class Gen9<T> {
            T ob;
        
            Gen9(T o) {
                ob = o;
            }
        
            T getob() {
                System.out.print("Gen9's getob(): ");
                return ob;
            }
        }
        
        class Gen10<T> extends Gen9<T> {
            Gen10(T o) {
                super(o);
            }
        
            // Override getob()
            T getob() {
                System.out.print("Gen10's getob(): ");
                return ob;
            }
        }
        
        class OverrideDemo {
            public static void main(String args[]) {
                Gen9<Integer> iOb = new Gen9<Integer>(88);
                Gen10<Integer> iOb2 = new Gen10<Integer>(99);
                Gen10<String> strOb2 = new Gen10<String>("Generics Test");
        
                System.out.println(iOb.getob());
                System.out.println(iOb2.getob());
                System.out.println(strOb2.getob());
            }
        }
        
        Output
        -----------
        
        Gen9's getob(): 88
        Gen10's getob(): 99
        Gen10's getob(): Generics Test
        
        

        As the output confirms, the overridden version of getob( ) is called for objects of type Gen10, but the superclass version is called for objects of type Gen9.

      6. Type Inference with Generics

        Here Inference : means Compiler decision making ability to decide where to put the Context based on its Type. Hence its known as Type Inference . That is :

        
        class MyClass<T, V> {
            T ob1;
            V ob2;
        
            MyClass(T o1, V o2) {
                ob1 = o1;
                ob2 = o2;
            }
            
            public static void main(String[] args) { 
            
            MyClass<Integer, String> a = new MyClass<Integer, String>(100, "Generics");
            
            }
            
         }
         
         
        
        

        Here MyClass < Integer , String > a = new MyClass < Integer , String > ( 100 , " Generics " ) ; here ,Compiler decides that 100 is for Integer and " Generics " for String . As we know " < > " is known as Diamond Operator and Syntax it goes like:

        
        class-name<type-arg-list> var-name = 
        	new class-name<>(cons-arg-list);
        	
        Where,
        
        cons→Constructor.
        arg→Argument.
         
        
        

        Consider the example:

        
        boolean isEqual(MyClass<T, V> o) {
                if (ob1 == o.ob1 && ob2 == o.ob2) {
                    return true;
                } else {
                    return false;
                }
        
               
        
            }
            
            MyClass<Integer, String> a = new MyClass<Integer, String>(100, "Generics");
        
                if (a.isEqual(new MyClass<Integer, String>(100, "Generics"))) {
                    System.out.println("Equal");
                } else {
                    System.out.println("Not Equal");
                }
         
        
        

        In this case , the type arguments for the arguments passed to isSame() can be Inferred from the parameter's type

        Note: Some part of Type Inference is discussed above.

    Bounded Generic Types

      
      interface A{
      }
      class B imlpements A{
      }
      class Ex<T extends B & A>{
      Ex<B> ex = new Ex<>();
      }
      
      :Here B is Class:
      :Here A is Interface:
      :And Class B implements Interface A:
      :And Type for Generic Class Ex is the Class which implements the Interface:
      :i.e. Ex<B> ex = new Ex<>():

  • 3. Next it can extend two interfaces instead of a class and interface.
  • 4. And in that case the Type 'T'of Generic Class will represent the class implementing the two interfaces.
  • interface A {}
    interface B {}
    Class C implements A , B{}
    class Ex<T extends A & B>{
    Ex<C> ex = new Ex<>();
    }
    
    :Here B is Interface:
    :Here A is Interface:
    :And Class C implements Interface A and Interface B:
    :And Type for Generic Class Ex is the Class which implements the Interface:
    :i.e. Ex<C> ex = new Ex<>():
    

  • 5. Note Now , we have the overview of Multiple Upper Bound i.e. Generic Class 's Type say 'T' extends the Class that implements Interfaces and then all the Interfaces Or Only the Interfaces joined by '&' . The type 'T' of Generic class is the class that implements the interfaces .

    Rules:What if for Abstract Methods?

    • Now for Abstract Methods the Class which extends Abstract Class and implement Interface becomes type T of Generic Class.But there is a difference that is we cannot use Generic type to extends the Class which extends Abstract Class if Abstract Class is extended by Generic Type 'T'. That we can only have one class and 'n' number of interfaces.
     
     interface A{}
     interface B{}
     abstract class C{}
     class D extends extends C implements A,B{}
     class Ex<T extends C &  A & B>{
      Ex<D>ex = new Ex<>();
     }
    
    :OR:
    
     interface A{}
     interface B{}
     abstract class C{}
     class D extends extends C implements A,B{}
     class Ex<T extends D &  A & B>{
      Ex<D>ex = new Ex<>();
     }
    
    :i.e. T either extends D or abstract Class C:

    Implementation of Multiple Upper Bound based on Rules for predefined classes and interfaces of Java

    java.lang.Number → Number is abstract class of java.lang package.
    java.lang.Comparable<T> → Comparable<T> is interface of java.lang package.
    

  • T extends Number & Serializable
  • java.lang.Number → Number is abstract class of java.lang package.
    java.io.Serializable → Serializable is interface of java.io package.
    

  • T extends Thread & Runnable
  • java.lang.Thread → Thread is a class of java.lang package ,
    which implements interface Runnabe.
    
    java.lang.Runnable → Runnable is an interface of java.lang package ,
    implemented by Thread class.
    

  • T extends HashMap& Map
  • java.util.HashMap → HashMap is a Class which is in java.util package.
    Hash Map implements Map interface.
    
    java.util.Map → Map is an interface of java.util package ,
    implemented by HashMap class.
    

  • T extends ArrayList & List
  • java.util.ArrayList → ArrayList is a Class which is in java.util package.
    ArrayList implements List interface.
    
    java.util.List → List is an interface of java.util package ,
    implemented by ArrayList class.
    

  • T extends HashSet & Set
  • java.util.HashSet → HashSet is a Class which is in java.util package.
    HashSet implements Set interface.
    
    java.util.Set → Set is an interface of java.util package ,
    implemented by HashSet class.
    

  • T extends CharSequence & Comparable
  • java.lang.CharSequence → CharSequence is an interface in java.lang package.
    
    java.lang.Comparable<T> → Comparable is an interface in java.lang package.
    

  • T extends Object & Comparable
  • java.lang.Object → Object is an interface in java.lang package.
    Class Object is the root of the class hierarchy. 
    Every class has Object as a superclass. 
    
    java.lang.Comparable<T> → Comparable is an interface in java.lang package.
    

    Similarly,

  • T extends AbstractList < Number > & List < Number >
  • T extends AbstractSet < Number > & Set < Number >
  • T extends Set < Number > & NavigableSet < Number >
  • T extends AbstractMap < String ,Number > & Map < String ,Number >
  • T extends HashMap < String, Number > & Map < String, Number >
  • T extends NavigableMap < String, Number > & Map < String, Number >
  • 1.B. Upper Bound Generic Type Methods

    Syntax:
         
    public static <Type extends Class/Interface> returnType funcName(parameter){
        //code
    }
    

  • Upper Bound Generic Type Methods →Eg-2 (public static)[T extends Comparable]
  • Upper Bound Generic Type Methods →Eg-3 (creation of object)[ T extends Number]
  • Syntax:
         
     <Type extends Class/Interface> returnType funcName(parameter){
        //code
    }
    public static void main(String[] args)
    {
    class_name<Type> var/obj_name = class_name<>();
    :Or if not generic class:  
    class_name var/obj_name = class_name();
    }
    

  • Upper Bound Generic Type Methods →Eg-4 (creation of object)[T extends Comparable]
  • Note: Its better to continue with static method rather than of creation of object.

    Other Examples- Static Upper Bound Generic Methods

  • Upper Bound Generic Type Methods →Eg-6 [T extends Runnable]
  • Upper Bound Generic Type Methods →Eg-7 [T extends Thread]
  • Upper Bound Generic Type Methods →Eg-8 [T extends List]
  • Upper Bound Generic Type Methods →Eg-9 [T extends Map]
  • Upper Bound Generic Type Methods →Eg-10 [T extends CharSequence]
  • Upper Bound Generic Type Methods →Eg-11 [T extends Object]
  • Upper Bound Generic Type Methods →Eg-12 [T extends Set]
  • Other Examples- With Creation of Object ,Upper Bound Generic Methods And Generic Classes

    1.C. Multiple Upper Bound Generic Type Methods

    WildCards

    • 1. In generic code, the question mark (?), called the wildcard, represents an unknown type.
    • 2. The wildcard is never used as a type argument for a generic method invocation, a generic class instance creation, or a supertype.
    • 3.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.
    • 4.Unlike arrays, different instantiations of a generic type are not compatible with each other, not even explicitly. This incompatibility may be softened by the wildcard if ? is used as an actual type parameter.
    • Division of WildCards

     
     graph TD;
    
        Wildcards-->| | UpperBoundedWildcards;
        Wildcards-->| | LowerBoundedWildcards;
        Wildcards-->| | UnBoundedWildcards;
       
    
    
    Loading

    1.A. UpperBoundedWildcards

    • To declare a upper bounded Wildcard parameter, list the ?, followed by the extends keyword, followed by its upper bound.
    • Upper Bounded Wildcard

    • Upper Bounded Wildcards →Eg-1
    interface A {
    
    }
    
    class Example<T> implements A {
    
    }
    
    public class WildCards<T extends Example<? extends A>>{
    
    public static void main(String[] args) {
    
        WildCards<Example<A>> obj = new WildCards<>();
    }
    
    }
    
    :Note→ As Generic Type Example class implements interface A:
    :It is taken as Type to create object:
    
    

    Now,

    interface A {
    
    }
    
    interface B<T> {
    
    }
    
    :OR:
    
    interface B<T>extends A {
    
    }
    
    class Example<T> implements A,B<T> {
    
    }
    
    public class WildCards<T extends Example<? extends A> & & B<? extends A>>{
    
    
    }
    
    }
    
    :Will generate error as Example implements B<T>:
    :It will be considered as - B<? extends A> in both the cases:
    :B<? extends A> & B<? extends A> cannot exists in same time:
    
    

    Now, if interface B is not generic , then it can be possible:

  • Upper Bounded Wildcards →Eg-2
  • interface A {
    
    }
    
    interface B {
    }
    
    class Example<T> implements A,B {
    
    }
    
    public class WildCards<T extends Example<? extends A> & B>{
    
    public static void main(String[] args) {
    
        WildCards<Example<A>> obj = new WildCards<>();
    }
    
    }
    
    :Reason→As both the interfaces now considered as specific and different from each other:

  • Upper Bounded Wildcards →Eg-3
  • interface A {
    
    }
    
    interface B<T> {
    }
    
    :OR:
    
    interface B<T> extends A {
    }
    
    class Example<T> implements  B<T> {
    
    }
    
    public class WildCards2 <T extends Example<? extends B<? extends A>>>  {
        
        
            public static void main(String[] args) {
        
                WildCards2<Example<B<A>>> obj = new WildCards2<>();
                
        
            }
        
    }
    
    :The above is inclusive nature of Wildcard in Generic Types:
    :During generating object of Class ,: 
    :The type of Class should be the Class that implements the Interfaces:

  • Upper Bounded Wildcards →Eg-4
  • interface A {
    
    }
    
    interface B<T extends A> {
    
    }
    
    class Example<T> implements B<A> {
    
    }
    class WildCards3 <T extends Example<? extends B<? extends A>>>{
    
    public static void main(String[] args) {
    
            WildCards3<Example<B<A>>> obj = new WildCards3<>();
            
            obj.add(10, 20);
    
        }
    
    }
    
    

  • Upper Bounded Wildcards →Eg-5
  • interface A {
    
    }
    
    interface B<T> {
    
    }
    
    interface C<T> {
    
    }
    
    class D<T> implements B<A>, C<A>{
    }
    
    class WildCards4<T extends B<? extends A> & C<? extends A>> {
    
    public static void main(String[] args) {
    
            WildCards4<D<A>> obj = new WildCards4<>();
            
            }
    
    
    }
    
    

  • Upper Bounded Wildcards →Eg-6
  • interface A {
    
    }
    
    interface B<T extends A> {
    
    }
    
    interface C<T extends A> {
    
    }
    
    class D<T extends A> implements B<A>, C<A> {
    }
    
    
    class WildCards5 <T extends B<? extends A> & C<? extends A>> {
    
    public static void main(String[] args) {
    
           WildCards5<D<A>> obj = new WildCards5<>();
    
    }
    
    
    }
    
    

  • Upper Bounded Wildcards →Eg-7
  • interface A{
    
    }
    
    interface B<T>{
    
    }
    
    class WildCards <T extends B<? extends A>> {
    
    public static void main(String[] args) {
    
        WildCards<B<A>> obj = new WildCards<>();
    
      }
    
    }
    
    

  • Upper Bounded Wildcards →Eg-8
  • class A{
    
    }
    
    interface B<T>{
    
    }
    
    
    class WildCards <T extends B<? extends A>> {
    
    public static void main(String[] args) {
    
          WildCards<B<A>> obj = new WildCards<>();
    
        }
    
    }
    
    

    Hence here are the rules :

  • 1. if the class extends a generic interface.
  • 
    interface A <T>{
    
    }
    
    
    class Example<T> implements A<T> {
    
    }
    
    

    Then any class having upper bound wildcard cannot have both the class and the interface, As the upper bound wildcard of interface represent the same as upper bound wildcard of the class and both cannot co exists . As Java does Type Erasure for type safety, what it does after compilation, all generic types are erased and the class implements interface and the super interface looks same in bytecode.

    
    class WildCards <T extends Example<? extends T> & A<? extends T>> {
        
    }
    
    :As→Example<? extends T> = A<? extends T>:
    :And A<? extends T> & A<? extends T> cannot co-exists.:
    
    

    Screenshot (231)

    Screenshot (232)

  • 2. The generic class that implements interfaces becomes the Type which helps to create the object of the classes.
  • 
    interface A {
    
    }
    
    interface B<T> {
    
    }
    
    
    class Example<T> implements A , B<T> {
    
        //Creation of Object\'s
    
        Example<String> e1 = new Example<String>();
        Example<Integer> e2 = new Example<Integer>();
        Example<Example<String>> e3 = new Example<Example<String>>();
    
    }
    
    public class WildCards6 <T extends B<? extends A>> {
    
        public static void main(String[] args){
    
    	//Same Objects can be created in class WildCard6 java class
    
      	Example<String> e1 = new Example<String>();
        	Example<Integer> e2 = new Example<Integer>();
        	Example<Example<String>> e3 = new Example<Example<String>>();
    
    
            // Creation of Object\'s of WildCards6
    
            WildCards6<Example<A>> obj = new WildCards6<>();
        }
        
    }
    
    

  • 3. if interface is not generic and class is generic which implements the interface then the generic class which have upper bound wild card can have the interface too .
  • 
    interface A {
    
    }
    
    interface B{
    
    }
    class Example<T> implements A,B {
    
        //Creation of Object\'s
        Example<String> e1 = new Example<String>();
        Example<Integer> e2 = new Example<Integer>();
        Example<Example<String>> e3 = new Example<Example<String>>();
    
    }
    
     class WildCards6 <T extends Example<? extends A> & B >{
    
        public static void main(String[] args){
            
            // Creation of Object\'s
            WildCards6<Example<A>> obj = new WildCards6<>();
        }
        
    }
    
    

    But if the interface is generic , then the generic class which implements the generic interface cannot be present at same time with the interface i.e. rule 1.

  • 4. In Upper Bound Wild Card : ? extends Interface / Class . And that Interface / Class must exist. Or will throw Error.
  • 
    interface A{
    
    }
    
    interface B<T>{
    
    }
    
    class WildCards <T extends B<? extends A>>{
    
    public static void main(String[] args) {
    
    WildCards<B<A>> obj = new WildCards<>();
    
    
         }
    
    
    }
    
    
    OR
    
    class A{
    
    }
    
    interface B<T>{
    
    }
    
    class WildCards <T extends B<? extends A>>{
    
    public static void main(String[] args) {
    
    WildCards<B<A>> obj = new WildCards<>();
    
    
       }
    
    
    }
    
    
    
    

  • 5. Rather than using the generic Class which implements the generic interfaces, single generic interfaces can also be taken as Upper Bound WildCard. And can be used as types to generate object of the Class. Also we can use the generic class(if present) which implement the interface/s.
  • 
    interface A{
    
    }
    
    interface B<T>{
    
    }
    
    class WildCards <T extends B<? extends A>>{
    
    public static void main(String[] args) {
    
    WildCards<B<A>> obj = new WildCards<>();
    
         }
    
    
    }
    
    OR
    
    interface A{
    
    }
    
    interface B<T>{
    
    }
    
    class E<T> implements B<T>{
    
    }
    
    class WildCards <T extends B<? extends A>>{
    
    public static void main(String[] args) {
    
    WildCards<E<A>> obj = new WildCards<>();
    
         }
    
    
    }
    
    
    
    

    But if two Upper Bound WildCard joined by '&' [AND] i.e. constituting Multiple Upper Bound then the Generic Class implement both the interfaces must be used as 'Type' to create object i.e. rule no.2 is must .

  • 6. In Upper Bound WildCard if WildCard extends a generic interface it creates another Inner Upper Bound Wild Card .
  • 
    interface A{
    
    }
    
    interface B<T>{
    
    }
    
    interface E<T> {
    
    }
    
    class WildCards <T extends E<? extends B<? extends A>>>{
    
    public static void main(String[] args) {
    
    WildCards<E<B<A>>> obj = new WildCards<>();
    
         }
    
    
    }
    
    
    

    If a generic class implements generic interfaces. Then the inner upper bound can be used with the generic class as Type to create object.

    
    interface A{
    
    }
    
    interface B<T>{
    
    }
    
    interface E<T> {
    
    }
    
    class D<T> implements E<T>,B<T>, A{
    
    }
    
    class WildCards <T extends E<? extends B<? extends A>>>
    
    {
    
    public static void main(String[] args) {
    
    WildCards<D<B<A>>> obj = new WildCards<>();
    
         }
    
    
    }
    
    OR
    
    
    class WildCards <T extends D<? extends B<? extends A>>>
    
    //Here D is Class
    
    {
    
    public static void main(String[] args) {
    
    WildCards<D<B<A>>> obj = new WildCards<>();
    
         }
    
    
    }
    
    
    
    

    Now if there is Multiple Upper Bound present , it acts the same as above i.e. the inner upper bound can be used with the generic class as Type to create object.

    
    interface A {
    
    }
    
    interface B<T> {
    
    }
    
    interface E<T> {
    
    }
    
    interface F<T> {
    
    }
    
    class D<T> implements E<T>,B<T>, F<T>,A{
    
    }
    
    class WildCards<T extends E<? extends B<? extends A>> 
    & F<? extends B<? extends A>>> {
    
    public static void main(String[] args) { 
    
            WildCards<D<B<A>>> obj = new WildCards<>();
    
           }
    
    }
    
    
    

    But the generic class which implements the interfaces if put as multiple upper bound , then rule no.1 is implemented i.e. Generic Class which implemented the generic interface and the generic interface cannot co-exists in multiple upper bound wildcards.

    
    interface A {
    
    }
    
    interface B<T> {
    
    }
    
    interface E<T> {
    
    }
    
    interface F<T> {
    
    }
    
    class D<T> implements E<T>,B<T>, F<T>,A{
    
    }
    
    Then
    
    class WildCards<T extends D<? extends B<? extends A>> 
    &F<? extends B<? extends A>>> {}
    
    cannot occur that is:
    
    
    D<? extends B<? extends A>>  = F<? extends B<? extends A>>
    
    And both cannot co-exists.
    
    

  • 7. Unlike Multiple Upper Bound we cannot use '&' operator inside Upper Bound Wild Card.
  • 
    class A{
    
    }
    
    class A1<T> {
        
    }
    
    interface B1{
    
    
    }
    
    interface C1{
    
    
    }
    
    class WildCards<T  extends A1<? extends A & B1 & C1>> 
    
    = NOT ALLOWED
    
    
    
    

  • 8. Suppose there are two classes and one of them is generic , then it is possible to create an Upper Bound Wild Card and its object.
  • 
    class A1 {
        
    }
    
    
    class B1<T>  {
        
    }
    
    class WildCards<T  extends B1<? extends A1>>{
    
    public static void main(String[] args) { 
    
    WildCards<B1<A1>> obj = new WildCards<>();
    
    }
    
    }
    
    
    

    Or an Interface and a generic class , then also it gives permission to create Upper Bound WildCards and its Object .

    
    interface A1 {
        
    }
    
    
    class B1<T>  {
        
    }
    
    class WildCards<T  extends B1<? extends A1>>{
    
    public static void main(String[] args) { 
    
    WildCards<B1<A1>> obj = new WildCards<>();
    
    }
    
    }
    
    
    

    But when there are more than one interface, then we need a Class which implement those interfaces so that we can create its object using the Class as Type during multiple upper bound is applied else Type remains unknown and in such cases we use Unbounded Wild Cards .

    
    interface A1 {
        
    }
    
    interface C1 <T>{
    
    
    }
    
    
    class B1<T> {
    
    }
    
    class WildCards<T  extends B1<? extends A1> & C<? extends A1>>{
    
    public static void main(String[] args) {
    
    WildCards<TypeUnkown> obj = new WildCards<>();
    
    //As TypeUnkown we cannot create Object 
    //hence here we use unbounded wild card.
    
    }
    
    }
    
    OR
    
    interface A1 {
        
    }
    
    interface B1<T>{
    
    }
    
    interface C1 <T>{
    
    
    }
    
    
    class WildCards<T  extends B1<? extends A1> & C<? extends A1>>{
    
    WildCards<TypeUnkown> obj = new WildCards<>();
    
    //As TypeUnkown we cannot create Object 
    //hence here we use unbounded wild card.
    
    }
    
    }
    
    

  • 9. Whether its Abstract Class or Class procedure remains same for Abstract Class and Class.
  • 
    Eg: 
    
    interface A{
    
    }
    
    interface B<T>{
    
    }
    
    interface C<T>{
    
    }
    abstract class A1<T> implements A,B<T>, C<T> {
    
    }
    
    class WildCards8<T  extends A1<? extends A>> {
    
    }
    
    public static void main(String[] args) {
    
    WildCards<A1<A>> obj = new WildCards<>();
    
    }
    
    }
    
    
    

  • 10. If a generic interface extends another generic interface , then another generic class implements both the interfaces as Upper Bound Wild Card . Then the super interface become equal to the sub interface which extends the super interface and erase the sub interface and impose error. Like rule no.1 and this type of erasure called as: Type Erasure. where after compilation, all generic types are erased, both interfaces ending looking the same in the bytecode.
  • 
    interface A{
    
    }
    
    interface B<E> extends A{
    	
    }
    
    interface C<E> extends B<E>{
    	
    }
    
    class D implements C<A>{
    	
    	
    }
    
    class WildCards<T extends C<? extends D> & B<? extends D>>{
    	
    }
    
    Then :
    
    C<? extends D> = B<? extends D>
    
     B<? extends D> and B<? extends D> cannot co-exist
    
    
    

    Screenshot (226) Screenshot (227)

  • 11. Iterating Rule 1 , 9 and 10 , if generic Abstract Class implements Generic Interface . Where super interface and abstract class after compilation looks same in bytecode causing Type Erasure.
  • 
    interface A{}
    
    interface A1<T>{}
    
    
    abstract class B<T> implements A1<T>{}
    
     class WildCardss<T extends B<? extends A> & A1<? extends A>> {}
    
    Then :
    
    B<? extends D> = A1<? extends A>
    
    A1<? extends A> and A1<? extends A> cannot co-exist
    
    
    

    Screenshot (228)

    Screenshot (229)

    Example of Implementation of Some of the PreBuilt Interfaces and Classes in Upper Bound WildCards.

      
      T extends List<? extends ArrayList<E>>
      
      Where E is a Class , in the Example.
      
      

    java.util.List → is an Interface
    
    java.util.ArrayList → A class which implements List Interface.
    
    Therefore,
    
    class A  {}
    
    :Now, Class A can be an Interface:
    :i.e. Interface A:
    
    class upperBoundWCEg1<T extends List<? extends ArrayList<A>>>{
    
    public static void main(String[] args) {
    
    
    upperBoundWCEg1<ArrayList<ArrayList<A>>> obj =
     new upperBoundWCEg1<>();
     
        }
    
    }
    
    
    

    
    T extends List<? extends ArrayList<E>>
    
    Where E is an Interface , in the Example.
    
    

    Note:

    Screenshot (233)

    Implementation of Rule : 11 , Where,
     
    AbstractList is an Abstract Class which implements List interface,
     
    cannot co-exist with List in upper bound wild card.
    

    
    T extends Set<E>
    
    Further:
    
    T extends Set<? extends TreeSet<E>>
    
    Where E is an Interface , in the Example.
    
    

    Note:

    Screenshot (234)

    java.util.Set → Is an Interface
    java.util.NavigableSet → Is an Interface
    
    :And TreeSet is the class which implements NavigableSet:
    :NavigableSet extends SortedSet interface:
    :And SortedSet extends Set interface:
    
    :Implementaion of Rule 10 and As :
    :NavigableSet extends SortedSet interface:
    :And SortedSet extends Set interface:
    :Hence Set and NavigableSet cannot co-exist:
    :In Upper Bound Wild Card:
    

    
    T extends Map<K,V>
    
    Further:
    
    T extends Map<? extends TreeMap<E,F> , 
    ? extends TreeMap<E,F>>
    
    Where E and F are Interfaces , in the Example.
    
    And TreeMap is a Class which implements Map interface,
    through SortedMap interface.
    
    TreeMap also implements Navigable Map.
    
    

    
    T extends Map<K,V> 
    
    Further:
    
    T extends Map<? extends TreeMap<K,V> ,
    ? extends TreeMap<K,V> >
    
    Further:
    
    T extends Map<? extends TreeMap<? extends I,? extends J> ,
    ? extends TreeMap<? extends I,? extends J>>
    
    Where I and J are Interfaces , in the Example.
    
    And TreeMap is a Class which implements Map interface,
    through SortedMap interface.
    
    TreeMap also implements Navigable Map.
    
    

    Note :
    
    If ,
    
    T extends Map<? extends TreeMap<? extends I,? extends J> , 
    ? extends TreeMap<? extends I,? extends J>> & 
    NavigableMap<? extends TreeMap<? extends I,? extends J >,
    ? extends TreeMap<? extends I,? extends J > >
    
    As  Interface NavigableMap<K,V> extends Interface SortedMap<K,V>
    And Interface  SortedMap<K,V> extends Interface Map<K,V>
    
    Hence it will throw error as Both cannot co-exist as per Rule 10.
    
    

    Screenshot (235)

    Screenshot (236)

    
    T extends List<? extends Number>
    
    
    

    
    T extends Set<? extends Number>
    
    
    

    
    T extends Map<? extends String, ? extends Number>
    
    
    

    Implementing Upper Bound WildCard as returnType of Method

      Note : What Does < T extends List < ? extends Number > > returnType FuncName(T t) mean?

      A parameter "t" of type T, where T is a generic type that extends a List of elements that are subtypes of Number. This means that the parameter "t" could be a List of any type of Number, such as Integer, Double, Float, etc. That is : List < Integer > , List < Double > , List < Float > etc. is the bound for List < ? extends Number > .

      Note : What Does < T extends List < ? extends String > > returnType FuncName(T t) mean?

      This means that the first parameter, "t", is a List of elements that are subtypes of String. That is : List < String > .

      Note :Now its Same for Float, Double ,..., etc. That is for: List < ? extends Float > , List < ? extends Double > , ... etc.

      Note : What Does < T extends Set < ? extends Number > > returnType FuncName(T t) mean?

      This is a type parameter for a generic class or method in Java. The type parameter "T" is bounded by the interface "Set" and it is further specified that the type of elements in this Set must be a subclass of "Number". In other words, this type parameter "T" represents a Set of elements, where the elements must be of type Number or a subclass of Number.A subclass of "Number" in Java is a class that extends the "Number" class. The "Number" class is a superclass for several subclasses in Java such as "Integer", "Double", "Float", "Long", etc. Here 't' is the parameter of the method. It is of type T, which is a Set of elements that are a subclass of "Number". That is : Set < Integer > , Set < Double > , Set < Float > etc. is the bound for Set < ? extends Number > .

      Similarly for Maps:

      Note : What Does < T extends Map < ? extends String, ? extends Number > > returnType FuncName(T t) mean?

      This is a type parameter for a generic class or method in Java. The type parameter "T" is bounded by the interface "Map" and it is further specified that the type of elements in this Map i.e. it's Keys must be sub types of String class and Values must be a subclass of "Number" . In other words, this type parameter "T" represents a Map of elements constitute of Keys and Values, where the Keys must be of type String or a subclass of String and Values must be of type Number or a subclass of Number.A subclass of "Number" in Java is a class that extends the "Number" class. The "Number" class is a superclass for several subclasses in Java such as "Integer", "Double", "Float", "Long", etc. Here 't' is the parameter of the method. It is of type T, which is a Map of elements. That is : Map < String, Integer > , Map < String, Float > etc. is the bound for Map < ? extends String, ? extends Number > .

    Implementing Upper Bound WildCard as Parameter of Method

    Implementing Upper Bound WildCard as Object/Variable

      Note: We know List < Type/Object > can create List which can be editable and alterable i.e. we can add and remove during runtime. Similarly for Set < Type/Object > , Map < Key , Value > but when we say List < ? extends Type > , Set < ? extends Type > and Map < ? extends Key , ? extends Value > i.e. their type information are not available at runtime i.e. not reifiable, hence not alterable and editable. Therefore we cannot add , remove , any element during runtime. Hence, we can use Set.of() and Map.of() to create unmodifiable Set and Map and Arrays.asList() to convert an array into a List object , as they take constant elements according to their types in a fixed compile time .

    Implementing Upper Bound WildCard in Class and Method

    Note: Rule 1 , 10, 11 of Upper Bound WildCard i.e. based on TypeErasure may get bypassed by some IDE like IntelliJ IDEA and some online editor like ONLINE GDB COMPILER and may get compiled successfully, the above rules are set based on IDE : VS CODE and ECLIPSE IDE .

    Some Generics allowed in IDE like IntelliJ IDEA to Run

    • 1. T extends Set < ? extends Number > & NavigableSet < ? extends Number > -Eg 1
    • 2. T extends Map extends String, ? extends Number > & NavigableMap < ? extends String, ? extends Number > -Eg 2
    • i.e.

      interface A1{ }
      
      interface B1<T> { }
      interface C1<T> extends B1<T>{ }
      class D1<T> implements C1<T>,A1{ }
      public class Example4 <T extends B1<? extends A1> & C1<? extends A1>>{
      
          public static void main(String[] args) {
      
              Example4<D1<A1>> d = new Example4<>();
      
          }
      }
      
      OR
      
      class/abstract class A1{ }
      [i.e. Class or Abstract Class]
      
      interface B1<T> {}
      interface C1<T> extends B1<T>{ }
      class D1<T> extends A1 implements C1<T> { }
      public class Example4 <T extends B1<? extends A1> & C1<? extends A1>>{
      
          public static void main(String[] args) {
      
              Example4<D1<A1>> d = new Example4<>();
      
          }
      }
      
      
      
      
      

      Note: If anyone see that NavigableSet extends Set interface , hence NavigableSet implements all the functions of Set , hence in VSCODE compiler compiles and while converting into bytecode erases the Type and it shows NavigableSet is put two times hence the implementation T extends Set < ? extends Number > & NavigableSet < ? extends Number > = T extends NavigableSet < ? extends Number > . This is same for Map and NavigableMap .

    1.B. Lower Bound And Lower Bounded Wildcards

      Description : In the Java programming language, a lower bound is a type that specifies that a type parameter must be either the specified type or a supertype of the specified type. Lower bounds are specified using the super keyword followed by the type that you want to use as the lower bound.

      'Super' keyword is used to refer and invoke parent class's properties . But unlike upper bound 'extend' , we cannot just call 'Super' after Type 'T' i.e. `T super AnyClass/Interface` . It generally comes with the `?` wildcard i.e. what we called `Lower Bound Wild Card`.

        Lower Bound Wild Card

          Explanation : To explain about Lower Bound Wild Card , lets take example of Upper Bound Wild Card : < T extends List < ? extends Number > >, here , T extends List falls under Upper Bound where `Type is bound to List` and we know ArrayList class extends List, hence say ,the Type is ArrayList then Object of Type `T` can access all of its predefined methods .

          
          <T extends List <Number>> void test (T type, Integer a){
          
          type.add(a); //Here `type` is Object of T 
          
          
          } 
          
          //Say T is ArrayList which `Extends` List
          //formal object reference 'type`will be ,
          //replaced by the Object of ArrayList
          //That is:
          
          ArrayList<Integer> al =  new ArrayList<>();
          
          test(al,1);
          
          //That is al.add(1) 
          
          

          Now, for < T extends List < ? extends Number > > , < ? extends Number > ,is known as Upper Bound WildCard . Due to the use of `?` Wild Card , Compiler could not recognise which subclass of Number will be there for `?` during run time , hence type information remain unavailable i.e. not reifiable ,hence we cannot alter without knowing its type. Hence:

          
          <T extends List <? extends Number>> void test (T type, Integer a){
          
          type.add(a); //Cannot takes place it will throw Error 
          
          type.remove(a); //Cannot takes place it will throw Error 
          
          
          } 
          
          
          

          And we know that : ArrayList < Integer > , ArrayList < Float >, ArrayList < Double > ...etc. are all bound to < T extends List < ? extends Number > > , since Integer extends Number , Float extends Number, Double extends Number ...etc . Here Integer, Float , Double ...etc. are all sub-classes or child-classes of Number abstract class and after the addition and removal i.e. alteration of elements , the objects of ArrayList < Integer > , ArrayList < Float >, ArrayList < Double > ...etc. passed to the method as actual parameter .As it takes constant elements according to their types in a fixed compile time .

          
          <T extends List <? extends Number>> void test (T type){} 
          
          ArrayList<Integer> intList = new ArrayList<>();
          intList.add(1);
          intList.add(2);
          intList.add(3);
          intList.remove(1);
          
          test(intList);
          
          

          Now, Lower Bound Wild Card i.e. < T extends List < ? super Number > > , super keyword will fetch all elements that contains inside the class Number. Abstract Class Number have abstract methods which returns byte, double, float, int, long, and short. Moreover the abstract class Number is the superclass/parent class of platform classes(Wrapper Classes :Integer, Float, Byte,Double,Long and Short) representing numeric values that are convertible to the primitive types byte, double, float, int, long, and short. Hence now `Compiler` knows what type of information needed i.e. Number which implies Integer, Float, Byte,Double,Long and Short or any of its supertypes, such as Object[java.lang.Object] and Serializable[java.io.Serializable] . As we know Object class is superclass/ parent class of all classes and abstract class Number implements java.io.Serializable interface. Hence now it is reifiable : `Now information available at runtime. And can be alterable at runtime.` .

          
          <T extends List <? super Number>> void test (T type){
          t.add(1);
          t.add(2);
          t.add(3);
          t.remove(1);
          } 
          
          //Now it is possible to alter during runtime .
          
          ArrayList<Number>list = new ArrayList<>();
          ArrayList<Object> list1 = new ArrayList<>();
          ArrayList<Serializable> list2 = new ArrayList<>();
          
          test(list);
          test(list1);
          test(list2);
          
          

          Hence ArrayList < Number > ,ArrayList < Object >, and ArrayList < Serializable > are lower bound to < T extends List < ? super Number > > where Number, Object and Serializable are super types of Number. And Lower Bound WildCard is reifiable .

        Rules :

          Note: The rules are similar to the Upper Bound WildCard.

          • 1. If a generic class implements generic interface then during multiple upper bound including lower bound wild card implemented , cannot co-exist , it causes type erasure - same like Upper Bound .
          • 
            interface A{
            
            }
            
            interface A1<T>{
            
            }
            
            class B<T> implements A1<T>,A{
            
            }
            
            public class JavaWild1 <T extends B<? super A> & A1< ? super A>>{}
            
            :As→B<? super A> = A1< ? super A>:
            :And A1< ? super A> & A1< ? super A> cannot co-exists.:
            
            

          • 2. Instantiation.
            • 2.a. Based only on Interfaces .
            • 
              interface A {
              
              }
              
              interface C extends A{
              
              }
              
              interface A1<T>{
              
              }
              
               class JavaWild1 <T extends A1<? super C>>{
              
                  public static void main(String[] args) {
                      JavaWild1<A1<C>> jw1 = new JavaWild1<>();
                      JavaWild1<A1<A>> jw2 = new JavaWild1<>();
                  }
              }
              
              Note→ A is Super interface of C.
              
              Hence , both C and A are used as Type to create Object.
              
              

            • 2.b. Based only on Classes .
            • 
              class A{
              
              }
              
              class B extends A{
              
              }
              
              class C<T> extends B {
              
              }
              public class JavaWild2<T extends C<? super B>> {
                  public static void main(String[] args) {
                      JavaWild2<C<B>> jw1 = new JavaWild2<>();
                      JavaWild2<C<A>> jw2 = new JavaWild2<>();
                  }
                  
              }
              
              Note→ A is Super Class of B.
              
              Hence , both B and A are used as Type to create Object.
              
              

            • 2.c. Based on Classes & Interfaces .
            • 
              interface A{}
              
              interface B extends A{}
              
              
              class C<T> implements B{}
              
              public class JavaWild3<T extends C<? super B>>{
              
                  public static void main(String[] args) {
                      JavaWild3<C<B>> jw1 = new JavaWild3<>();
                      JavaWild3<C<A>> jw2 = new JavaWild3<>();
                  }
              }
              
              Note→ A is Super Interface of B.
              
              Hence , both B and A are used as Type to create Object.
              
              

          • 3. Instantiation[Multiple Upper Bound].
            • 3.a. Instantiation based on Multiple Upper Bound -1 .
            • 
              interface A {}
              
              interface C extends A {}
              
              interface B extends C {}
              
              interface A1<T> {}
              
              interface C1<T> {}
              
              class A2<T> implements A1<T>,C1<T> {}
              
              public class JavaWild4 <T extends A1<? super B> 
              			& C1<? super B>> {
              
                  public static void main(String[] args) {
                      JavaWild4<A2<A>> jw1 = new JavaWild4<>();
                      JavaWild4<A2<B>> jw2 = new JavaWild4<>();
                      JavaWild4<A2<C>> jw3 = new JavaWild4<>();
                      
                  }
                  
              }
              
              Note→ A is Super Interface of B.
              And, C is Super Interface of B.
              And, Including B itself.
              Hence , both B,C and A are used as Type to create Object.
              
              
              
              

            • 3.b. Instantiation based on Multiple Upper Bound -2.
            • 
              interface A {}
              
              interface C extends A {}
              
              interface B extends C {}
              
              interface A1<T> {}
              
              interface C1<T> {}
              
              class A2<T> implements A1<T>, C1<T> {}
              
              public class JavaWild5 <T extends A1<? super C> 
              			& C1<? super B>>{
              			
                  public static void main(String[] args) {
                      JavaWild5<A2<A>> jw1 = new JavaWild5<>();
                      JavaWild5<A2<C>> jw3 = new JavaWild5<>();
                  }
                  
              }
              
              Note→ A is Super Interface of B.
              And, C is Super Interface of B.
              And, But B is not super interface of C.
              
              Hence B Cannot be taken as Type.
              
              

          • 4. Instantiation based on Inner Lower Bound .
            • 4.a. Instantiation based on Inner Lower Bound -1 .
            • 
              interface A {}
              
              interface C extends A {}
              
              interface B extends C {}
              
              interface A1<T> extends B {}
              
              interface C1<T>  {}
              
              class A2<T> implements C1<T>{}
              
              public class JavaWild6 <T extends C1<? super A1<? super B>>> {
              
                  public static void main(String[] args) {
                      JavaWild6<A2<A>> jw1 = new JavaWild6<>();
                      JavaWild6<A2<B>> jw2 = new JavaWild6<>();
                      JavaWild6<A2<C>> jw3 = new JavaWild6<>();
                      JavaWild6<A2<A1>> jw4 = new JavaWild6<>();
                      JavaWild6<C1<A>> jw5 = new JavaWild6<>();
                      JavaWild6<C1<B>> jw6 = new JavaWild6<>();
                      JavaWild6<C1<C>> jw7 = new JavaWild6<>();
                      JavaWild6<C1<A1>> jw8 = new JavaWild6<>();
                      
                  }
                  
              }
              
              Note →  Super types of A1 is: B , C, A and A1.
              To implement A1 as type i.e. A1 we cannot implement as Generic , 
              i.e. Cannot be Parameterized , which will generate error,
              i.e. A1<A>, A1<B> cannot be used, where A,B is super of B.
              Rather than we implement it as RAW type and will ,
              generate Warning of saying Parameterized.
              
              As,
              JavaWild6<A2<A1>> jw4 = new JavaWild6<>();
              JavaWild6<C1<A1>> jw8 = new JavaWild6<>();
              
              

            • 4.b. Instantiation based on Inner Lower Bound -2 .
            • 
              interface A {}
              
              interface C extends A {}
              
              interface B extends C {}
              
              interface A1<T> extends B {}
              
              interface A2<T> extends A1<T> {}
              
              interface C1<T> {}
              
              class A3<T> implements C1<T>, A2<T> {}
              
              public class JavaWild8 <T extends C1<? super A1<? super B>> & 
              				A2<? super A1<? super C>>>{
              
                  public static void main(String[] args) {
                      JavaWild8<A3<A>> jw1 = new JavaWild8<>();
                      JavaWild8<A3<B>> jw2 = new JavaWild8<>();
                      JavaWild8<A3<C>> jw3 = new JavaWild8<>();
                      JavaWild8<A3<A1>> jw4 = new JavaWild8<>();
                      
                  }
                  
              }
              
              

          • 5. Instantiation based on Inner Lower Bound and Inner Upper Bound.
            • 5.a. Instantiation based on Inner Lower Bound and Inner Upper Bound -1 .
            • 
              interface A {}
              
              interface C extends A {}
              
              interface B extends C {}
              
              interface A1<T> extends B {}
              
              interface A2<T> extends A1<T> {}
              
              interface C1<T> {}
              
              class A3<T> implements C1<T> {}
              
              public class JavaWild10<T extends C1<? extends A1<? super B>>> 
              
              {
              
                      public static void main(String[] args) {
              
                          JavaWild10<C1<A1<B>>> jw1 = new JavaWild10<>();
                          JavaWild10<C1<A1<C>>> jw2 = new JavaWild10<>();
                          JavaWild10<C1<A1<A>>> jw3 = new JavaWild10<>();
                          
                          JavaWild10<A3<A1<A>>> jw4 = new JavaWild10<>();
                          JavaWild10<A3<A1<B>>> jw5 = new JavaWild10<>();
                          JavaWild10<A3<A1<C>>> jw6 = new JavaWild10<>();
                  
                      }
                  
              }
              
              
              

            • 5.b. Instantiation based on Inner Lower Bound and Inner Upper Bound -2 .
            • 
              interface C extends A {}
              
              interface B extends C {}
              
              interface A1<T> extends B {}
              
              interface A2<T> extends A1<T> {}
              
              interface C1<T> {}
              
              class A3<T> implements C1<T> {
              
              }
              
              public class JavaWild10<T extends C1<? super A1<? extends C>>>
              {
                  
                      public static void main(String[] args) {
                          JavaWild10<A3<A>> jw1 = new JavaWild10<>();
                          JavaWild10<A3<B>> jw2 = new JavaWild10<>();
                          JavaWild10<A3<C>> jw3 = new JavaWild10<>();
                          JavaWild10<A3<A1>> jw4 = new JavaWild10<>();
              
              
                          JavaWild10<C1<A>> jw5 = new JavaWild10<>();
                          JavaWild10<C1<B>> jw6 = new JavaWild10<>();
                          JavaWild10<C1<C>> jw7 = new JavaWild10<>();
                          JavaWild10<C1<A1>> jw8 = new JavaWild10<>();
                  
                      }
                  
              }
              
              

            • 5.c. Instantiation based on Inner Lower Bound and Inner Upper Bound -3 .
            • 
              interface A {}
              
              interface C extends A {}
              
              interface B extends C {}
              
              interface A1<T> extends B {}
              
              interface A2<T> extends A1<T> {}
              
              interface C1<T> {}
              
              class A3<T> implements C1<T>, A2<T> {}
              
              public class JavaWild9<T extends C1<? extends A1<? super B>> 
              			& A2<? extends A1<? super C>>> 
              {
              
                  public static void main(String[] args) {
                      JavaWild9<A3<A1<A>>> jw1 = new JavaWild9<>();
                      
              
                  }
              
              }
              
              
              

            • 5.d. Instantiation based on Inner Lower Bound and Inner Upper Bound -4 .
            • 
              interface A {}
              
              interface C extends A {}
              
              interface B extends C {}
              
              interface A1<T> extends B {}
              
              interface A2<T> extends A1<T> {}
              
              interface C1<T> {}
              
              class A3<T> implements C1<T>, A2<T> {
              
                  }
              
              public class JavaWild9<T extends C1<? super A1<? extends B>> 
              			& A2<? super A1<? extends C>>> {
              
                  public void add () {
                      System.out.println("int");
                  }
              
                  public static void main(String[] args) {
                      JavaWild9<A3<A>> jw1 = new JavaWild9<>();
                      JavaWild9<A3<B>> jw2 = new JavaWild9<>();
                      JavaWild9<A3<C>> jw3 = new JavaWild9<>();
                      JavaWild9<A3<A1>> jw4 = new JavaWild9<>();       
                   
                  }	
              }
              
              
              

          • 6. Other Type Erasures.
            • 6.a Other Type Erasures-1.
            • 
              interface A {}
              
              interface B<T> extends A {}
              
              interface C<T> extends B<T> {}
              
              class D implements C<A> {}
              
              public class JavaWild11<T extends C<? super D> & B<? super D>>{
              }
              
              

              Note: Generic interface C extends B , hence super or parent is B , during compilation the byte code and erase the sub interface and impose error for type safety which also known as Type Erasure.

            • 6.b Other Type Erasures-2. [Same Rule for Abstract Class ]
            • 
              interface A {}
              
              interface B<T> extends A {}
              
              interface C<T> extends B<T> {}
              
              abstract class D<T> implements C<T>{} 
              
              class JavaWild11<T extends D<? super A> & C<? super A>>{
              }
              
              class JavaWild12<T extends D<? super A> & B<? super A>>{
              }
              
              [Explanation:]
              
              D<? super A> = C<? super A>
              C<? super A> = C<? super A> cannot co-exist.
              
              
              D<? super A> = B<? super A>
              B<? super A> = B<? super A> cannot co-exist.
              
              

              Note: Abstract Class and Class 's rules are same .

      Example of Implementation of Some of the PreBuilt Interfaces and Classes in Lower Bound WildCards.

      
      T extends List<? super ArrayList<E>>
      
      Where E is a Class , in the Example.
      
      Here in the Example:
      
      ArrayList<AbstractList<E>>,ArrayList<ArrayList<E>>,
      ArrayList<Object> are bound to 
      T extends List<? extends ArrayList<E>>
      
      And AbstractList, Object , and ArrayList
      are super types of ArrayList .
      
      

      
      T extends List<? super ArrayList<E>>
      
      Where E is an Interface , in the Example.
      
      Here in the Example:
      
      ArrayList<AbstractList<E>>,ArrayList<ArrayList<E>>,
      ArrayList<Object> are bound to 
      T extends List<? extends ArrayList<E>>
      
      And AbstractList, Object , and ArrayList
      are super types of ArrayList .
      
      

      
      T extends Set<? super TreeSet<E>>
      
      Where E is an Interface , in the Example.
      
      Here in the Example:
      
      TreeSet<TreeSet<E>>,TreeSet<Object>,
      TreeSet<Set<E>> , TreeSet<AbstractSet<E>>,
      TreeSet<NavigableSet<E>> and TreeSet<SortedSet<E>>
      are bound to T extends Set<? super TreeSet<E>>
      
      And TreeSet, Object , Set , AbstractSet,
      AbstractSet, NavigableSet and SortedSet
      are super types of TreeSet . 
      

      
      T extends Map<? super TreeMap<E, F>, ? super TreeMap<E, F>>
      
      Where E and F are  Interfaces , in the Example.
      
      Here in the Example:
      
      TreeMap<TreeMap<E, F>, TreeMap<E, F>> ,
      TreeMap<Object, Object> ,
      TreeMap<Map<E, F>, Map<E, F>> ,
      TreeMap<SortedMap<E, F>, SortedMap<E, F>> ,
      TreeMap<NavigableMap<E, F>, NavigableMap<E, F>> and
      TreeMap<AbstractMap<E, F>, AbstractMap<E, F>> 
      are bound to :
      T extends Map<? super TreeMap<E, F>, ? super TreeMap<E, F>>
      
      And TreeMap, Object , Map, SortedMap,
      NavigableMap, and AbstractMap
      are super types of TreeMap . 
      

      
      T extends List<? super Number>
      
      Where Number is a pre-defined abstract class in Java.
      
      Here in the Example:
      
      ArrayList<Number>,
      ArrayList<Object> ,
      ArrayList<Serializable> ,
      are bound to :
      T extends List<? super Number>
      
      And Number, Object, Serializable
      are super types of Number class . 
      

    Implementing Lower Bound WildCard as returnType of Method

      Note : What Does < T extends List < ? super Number > > returnType FuncName(T t) mean?

      This means that this method takes in a generic argument of type T, where T must extend a List of elements that are either of type Number or a superclass of Number. The super classes of Numbers are Object and Serializable. In other words, the method accepts a list of objects that are either of type Number or a superclass of Number. Here 't' is the parameter of the method. It is of type T, which is a List of elements that is a Number or superclass of "Number".

    public static < T extends List < ? super Number > > T test(T t, Integer a) {
    
    for (Object n : t) {
                System.out.println(n);
            }
    }
    
    Here what type of element we have input is unknown ,
    till we run the program i.e. in runtime , hence object
    of an Object class is passed.
    

    Similarly,

    Explanation:The method takes a generic type parameter "T" that extends List of some type that is a superclass of String and it takes a parameter which is an object of type "T" . "T extends List " which means the "Type T" can take subclasses of List such as ArrayList.

    
    Here String,Object,Serializable,Comparable<String> and CharSequence 
    Are Super Types of String.
    
    Where as  ArrayList<String>, ArrayList<Object>, ArrayList<Serializable>,
    ArrayList<Comparable<String>>, and ArrayList<CharSequence> are bound to
    T extends List < ? super String > > .
    
    And ArrayList is subclass/childclass of List.
    Which satisfies T extends List .
    
    

    Explanation: It defines a generic method that takes a parameter of a type T, which must be a subtype of Set and can contain any superclass of Number. The method returns the same type T.In other words, the method can accept any type of Set, such as HashSet < Serializable >, HashSet < Number > and HashSet < Object > i.e. It accept a Number or its supertype. The method returns the same type T that was passed in as a parameter.

    
    Here Serializable,Number,Serializable,and Object are Super Types of Number.
    
    Where as  HashSet<Serializable>, HashSet<Number> and HashSet<Object> 
    are bound to T extends Set < ? super Number >  .
    
    And HashSet is subclass/childclass of Set.
    Which satisfies T extends Set.
    
    

    Explanation: The generic method that takes a single argument of a type T, which must be a subtype(Sub Classes) of Map with keys of type String or a supertype of String, and values of type Number or a subtype of Number. The method returns the same type T that was passed in as an argument.

    
    We already know:
     
    Serializable,Number,Serializable,and Object are Super Types of Number.
    
    String,Object,Serializable,Comparable<String> and CharSequence 
    Are Super Types of String.
    
    Where as , HashMap<String, Number>, HashMap<String, Serializable>
    ...etc. are bound to T extends Map<? super String, ? super Number> .
    
    And HashMap is subclass/childclass of Map.
    Which satisfies T extends Map.
    
    

    Implementing Lower Bound WildCard as Parameter of Method

      Note : What Does ' void funcName ( List < ? super Number > num ) ' mean?

      Explanation: `void` indicates that the function does not return any value."funcName" is the name of the function. "List" is a generic interface in Java that represents an ordered collection of elements. " < ? super Number > " is a bounded wildcard type that specifies that the list can contain elements of any type that is a superclass of the Number class. This means that the list can contain objects of type Number or any of its subclasses (such as Integer, Double, etc.). As we know it takes Number and its super types of abtsract Number class i.e. Serializable and Object.

      Note : What Does ' List < ? super Number > funcName ( List < ? super Number > num ) ' mean?

      Explanation: The method signature " List < ? super Number > funcName ( List < ? super Number > num)" declares a method named "funcName" that takes in a list of elements that are either of type Number or a superclass of Number, and returns a List of elements that are also either of type Number or a superclass of Number. The keyword "super" is used to denote a lower bound on the type parameter, which means that the list can contain elements of the specified type or any of its superclasses.

      Similarly, it goes same for Set , Map ... etc.

    Implementing Lower Bound WildCard as Variable

    Implementing Lower Bound WildCard in Class and Method

    Some Generics allowed in IDE like IntelliJ IDEA to Run

      • T extends Map < ? super String, ? super Number > & NavigableMap < ? super String, ? super Number > -Eg
      • i.e.

        interface A2{ }
        
        interface B2<T> {}
        interface C2<T> extends B2<T>{ }
        class D2<T> implements C2<T>,A2{ }
        
        public class Example6 <T extends B2<? super A2> & C2<? super A2>>{
        
                public static void main(String[] args) {
        
                    Example6<D2<A2>> d = new Example6<>();
        
                }
        }
        
        OR
        
        class/abstract class A2{ }
        [i.e. Class or Abstract Class]
        
        interface B2<T> {}
        interface C2<T> extends B2<T>{ }
        class D2<T> extends A2 implements C2<T> { }
        
        public class Example6 <T extends B2<? super A2> & C2<? super A2>>{
        
                public static void main(String[] args) {
        
                    Example6<D2<A2>> d = new Example6<>();
        
                }
        }
        
        
        

        Note: If anyone see that NavigableMap extends Map interface , hence NavigableMap implements all the functions of Map , hence in VSCODE compiler compiles and while converting into bytecode erases the Type and it shows NavigableMap is put two times hence the implementation T extends Map < ? super String, ? super Number > & NavigableMap < ? super String, ? super Number > = T extends NavigableMap < ? super String, ? super Number > . And it put a difference between two IDE between IntelliJ and VSCODE.

    UnBounded Wild Card

      Unbounded WildCard: Unbounded means here not bounded by any Upper Bound "extends" or Lower Bound "super" . Then the Type is only bounded by WildCard "?" which enables all types of reference to be passed in parameter. It accepts everything dynamically to be passed in type during runtime . Hence it is known as "Unbounded WildCards" .

      Note : When we donot know what would be the type to pass in parameter , suppose there are two interfaces but there is no class that implement the interfaces and both the interfaces are bounded by a Generic class, hence the only option remains to create object of that class is using "< ? >" unbounded wild card.

      
      interface A{}
      
      interface B<T>{}
      
      interface C<T>{}
      
       class WildCards< T extends B<? extends A> & C<? super A>> {
      
          public static void main(String[] args) {
             
              WildCards<?> a = new WildCards<>();
      
          }
          
      }
      	
      Here we see there is no class exist ,
      that implement interfaces B and C.
      Hence "Wild Card" is must here.
      
      

      1. Unbound Wild Card Over Class

        
        import java.util.ArrayList;
        import java.util.List;
        
        public class WildCards<T extends List<?>> {
        
            public static void main(String[] args) {
        
                WildCards<ArrayList<Integer>> a = new WildCards<>();
                WildCards<Float>> b = new WildCards<>();
               	WildCards<ArrayList<Double>> c = new WildCards<>();
                WildCards<ArrayList<Comparable<Number>>> c1 = new WildCards<>();
                // .....etc.
        	
            }
        
        }
        
        Here we see as List<?> is unbounded it take Integer,
        Float, Double , Comparable etc. any types as its bound while 
        creating object.
        
        

        2. When no class implements two Interfaces in a Multiple Upper Bound

        
        import java.util.Set;
        import java.util.List;
        
        public class WildCards<T extends List<Number> & Set<Number>> {
        
            public static void main(String[] args) {
        
                WildCards<?> a = new  WildCards<>();
                WildCards<?> b = new  WildCards<>();
                WildCards<?> c = new  WildCards<>();
                // .....etc.
        	
            }
        
        }
        
        Here there is no class no class that implements,
        both interface of List and Set. Hence we need to
        pass "UnBound WildCard" as Type as shown above to
        create Object.
        
        

        3. When a class and interface is used in Multiple Upper Bound and no class exist that inherit both the class and interface.

        
        import java.util.Set;
        import java.util.ArrayList;
        
        public class WildCards<T extends ArrayList<Number> & Set<Number>> {
        
            public static void main(String[] args) {
        
                WildCards<?> a = new  WildCards<>();
                WildCards<?> b = new  WildCards<>();
                WildCards<?> c = new  WildCards<>();
                // .....etc.
        	
            }
        
        }
        
        Here there are no class exist that extends ArrayList and implements Set 
        interface  hence UnBounded Wild Card is used. 
        
        

        Hence, UnBounded WildCards are use for the above reasons in application.

    Type Erasure

      Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to:

      • 1.Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
      • 2.Insert type casts if necessary to preserve type safety.
      • 3.Generate bridge methods to preserve polymorphism in extended generic types.
      • Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.

        1. Erasure of Generic Types

          
          
          class Node<T> {
          
              T data;
              Node<T> next;
          
              public Node(T data) {
                  this.data = data;
                  this.next = null;
              }
          
          public static void main(String[] args) {
                  Node<String> n1 = new Node<String>("Hello");
                  Node<String> n2 = new Node<String>("World");
                  n1.next = n2;
                  System.out.println(n1.data);
                  System.out.println(n1.next.data);
              }
          
          }
          
          

          Note here T is replaces with String by compiler during Compilation. Hence erasing Generic type 'T' with desired Class.

          
          ////During Compilation
          
          class Node<String> {
          
              String data;
              Node<String> next;
          
              public Node(String data) {
                  this.data = data;
                  this.next = null;
              }
          
          ///
          
          

        2. Erasure of Generic Methods.

          
          class GenericBase {
          
              public <T> T get(T t) { 
              
              	return t; 
              
              }
          
              public static void main(String[] args) {
                  GenericBase gb = new GenericBase();
                  System.out.println(gb.get(10));
              }
          
          }
          
          

          Note here `T` replaces with Integer by compiler during Compilation and it erase the type 'T' with Integer . As above class is not generic , hence we have to put a cast < T > before starting the method. The type erasure process is shown below :

          
          ////During Compilation
          
          public <Integer> Integer get(Integer t) {
                  return t;
              }
          
          ///
          
          

          Similarly,

          
          class Shape {...}
          
          class Circle extends Shape {...}
          
          class Rectangle extends Shape {...}
          
          class Program {
          
              public static <T extends Shape> void draw(T shape) {
                  System.out.println("Drawing a " + shape.getClass().getSimpleName());
          
              }
          
              public static void main(String[] args) {
                  Circle circle = new Circle();
                  Rectangle rectangle = new Rectangle();
          
                  draw(circle);
                  draw(rectangle);
              }
          
          }
          
          

          The Java compiler replaces T with Shape:

          
          public static void draw(Shape shape) { /* ... */ }
          
          

        3. Effects of Type Erasure and Bridge Methods.

        • Java compiler creates a synthetic method known as Bridge Method when compiling a class or interface that extends a parameterized class or interface as part of type erassure.
        • 
          class Gener<T>{
              T obj;
              Gener(T o){
                  obj = o;
              }
              T getObj(){
                  return obj;
              }
          }
          
          class Gener2 extends Gener<String>{
              Gener2(String  o){
                  super(o);
              }
          
              String getObj(){
                  System.out.println("Gener2's getObj()");
                  return obj;
              }
          }
          
          class BridgeDemo {
          
              public static void main(String args[]){
                  Gener2 strOb2 = new Gener2("Generics Test");
          
                  String str = strOb2.getObj();
          
                  System.out.println(str);
              }
          
          }
          
          
          

          Compiling process is as shown below:

          class Gener2 extends Gener<java.lang.String>{
          
          //Type Erasure 
          Gener2(java.lang.String){
            super(o)
          }
          
          //Bridge Method[Java Preserves It]
          java.lang.Object getObj(){
          
           	return (java.lang.String) ob;
          
          }
          
          //Type Erasure 
          java.lang.String getObj(){
          
          	return (java.lang.String) ob;
          
          }
          
          
          }
          
          

          Here after Type Erasure , "java.lang.Object getObj();" is acceptable method . Here what happens is that "Due to Type Erasure , The Perfectly Acceptable form is : Object getob{...}, To handle this problem, the compiler generates a bridge method [Here is java.lang.Object getObj()] with the preceding signature that calls the String version "java.lang.String getObj()".The Bridge Method Is Used To Preserve The Type Safety Of Generic Types And The Polymorphism Of Generic Types After Type Erasure."

          Again,

          
          class Node1<T> {
          
               T data;
          
               Node1(T data) {
                  this.data = data;
              }
          
               void setData(T data) {
                  System.out.println("Node.setData");
                  this.data = data;
              }
          }
          
          class MyNode extends Node1<Integer> {
               MyNode(Integer data) {
                  super(data);
              }
          
               void setData(Integer data) {
                  System.out.println("MyNode.setData");
                  super.setData(data);
              }
          }
          
          class BridgeMethods {
          
              public static void main(String[] args) {
                  Node1<Integer> node = new MyNode(5);
                  node.setData(4);
              }
          }
          
          

          Type Erasure and Bridge Method

          
          //Replacing Type with Object Class
          class Node1 {
          
               Object data;
          
               Node1(Object data) {
                  this.data = data;
              }
          
               void setData(Object data) {
                  System.out.println("Node.setData");
                  this.data = data;
              }
          }
          
          
          class MyNode extends Node1<Integer> {
          
               MyNode(java.lang.Integer data) {
                  super(data);
              }
          
          //Bridge Method[Preserved]
               void setData(java.lang.Object data) {
                  setData((java.lang.Integer)data);
              }
          
          //Type Erasure 
          public void setData(Integer data) {
                  System.out.println("MyNode.setData");
                  super.setData(data);
              }
          
          }
          
          

          The bridge method MyNode.setData(object) delegates(passes) to the original MyNode.setData(Integer) method.

      Non-Reifiable Types and Heap Pollution

        A. Non Reifiable and Reifiable Types

        A Reifiable type is a type whose type information is fully available at runtime. Non-reifiable types are types where information has been removed at compile-time by type erasure — invocations of generic types that are not defined as unbounded wildcards. A non-reifiable type does not have all of its information available at runtime. Reifiable and Non-Reifiable types are already discussed earlier.

        B. Heap Pollution

        Heap pollution occurs when a variable of a parameterized type refers to an object that is not of that parameterized type. This situation occurs if the program performed some operation that gives rise to an unchecked warning at compile-time. An unchecked warning is generated if, either at compile-time (within the limits of the compile-time type checking rules) or at runtime, the correctness of an operation involving a parameterized type (for example, a cast or method call) cannot be verified. For example, heap pollution occurs when mixing raw types and parameterized types, or when performing unchecked casts.

        For Example

        import java.util.ArrayList;
        import java.util.List;
        
        public class HeapPollution {
        
        public static void main(String[] args) {
        
        List<String> stringList = new ArrayList<>();
        List<Integer> integerList = new ArrayList<>();
        integerList.add(42);
        stringList = (List<String>) (List) integerList; 
        System.out.println(stringList.get(0));
        }
        }
        
        

        In the above code, a List named stringList and a List named integerList are created. Then, the integerList is added to stringList, but to make the assignment work, the integerList is cast to a raw type List and then cast to List.This is heap pollution because the stringList was intended to hold only String objects, but now it contains Integer objects as well. This can lead to unexpected behavior and errors when the stringList is accessed and the code expects to find only String objects in it.

        
        // Heap pollution occurs when adding integerList to stringList
        // because stringList is expected to hold only String objects
        stringList = (List<String>) (List) integerList; // This causes heap pollution
        
        

        And,

        
         System.out.println(stringList.get(0)); 
        // This causes ClassCastException to be thrown
        
        

        Handling Heap Pollution

        To understand handling Heap Pollution, we have to understand : VarArgs of Java

      • Heap Pollution- Eg -1
      • 
        import java.util.ArrayList;
        import java.util.List;
        
        public class HeapPollution {
           
            public static void merge(List<String>... stringList) {
               
                Object[] arr = stringList;
                List<Integer> temp = new ArrayList<Integer>();
                temp.add(420);
                arr[0] = temp;
        
                String firstEle = stringList[0].get(0);//ClassCastException
                System.out.println(firstEle);
                
            }
        
            
            public static void main(String args[]) {
                List<String> list1 = new ArrayList<>();
                List<String> list2 = new ArrayList<>();
                List<String> list3 = new ArrayList<>();
                list1.add("My Name");
                list2.add("is");
                list3.add("Avinandan");
        
                merge(list1, list2, list3);
            }
        }
        
        
        

        Here , In the above program it throws warning Type safety: Potential heap pollution via varargs parameter stringList at List < String > ... stringList and stringList[0].get(0) tries to access the first element of the first list in the input using the get method, but this results in a ClassCastException because the first list has been replaced with an ArrayList and no longer contains strings i.e. [ ArrayList < String > ] during runtime .Hence after compilation firstEle becomes Integer type and change occurs during runtime causing ClassCastException.

        Solution: To remove Warning we put the annotation @SafeVarargs and as the change occurs during runtime , we just change the type of "firstEle" to java.lang.Object to avoid "Class Cast Exception" as shown Below:

      • Heap Pollution- Eg -2
      • 
        
        @SafeVarargs
            public static void merge(List<String>... stringList) {
        
        	Object[] arr = stringList;
                List<Integer> temp = new ArrayList<Integer>();
                temp.add(420);
                arr[0] = temp;
        
                Object firstEle = stringList[0].get(0);
                System.out.println(firstEle);
        }
        
        
        

      Restrictions in Java Generics

      • 1.Cannot Instantiate Generic Types with Primitive Types
        • class Example<T> {
              T ob1;
              void setOb1(T ob1) {
                  this.ob1 = ob1;
              }
          
              public static void main(String[] args){
                  Example<int> obj = new Example<>();//Cannot Instantiate
              }
          
          }
          
          

      • 2.Cannot Create Instances of Type Parameters
        • class Example<T> {
              Example() {
                  T = new T(); ///Cannot Instantiate
              }
          
          }
          
          

      • 3.Cannot Declare Static Fields Whose Types are Type Parameters or Restrictions of Static Members
        • class Example<T> {
          
              //Wrong no static variable of type T
             static T ob;
          
          }
          
          

          Similarly,

          class Example<T> {
          
           //Wrong no static method can use T
             static T get() {
              return null;
            }
          
          }
          
          

          Reason: A class's static field is a class-level variable shared by all non-static objects of the class. Hence, static fields of type parameters are not allowed.

      • 4.Cannot Use Casts or instanceof with Parameterized Types
        • 4.a.InstanceOf

          import java.util.ArrayList;
          import java.util.List;
          
          class Example{
              public static <E> void rtti(List<E> list) {
              
              //Illegal use of instanceof
              ------------------------------
                  if (list instanceof ArrayList<Integer>) {
                      System.out.println("ArrayList<Integer>");
                  }
              }
          }
          
          

          As Type E can be ArrayList < Integer > , ArrayList < String > etc. hence making E specific for ArrayList will throw compile-time error. To make it run successfully we must use "?" unbound as parameter:

          import java.util.ArrayList;
          import java.util.List;
          
          class Example{
              public static  void rtti(List<?> list) {
                  if (list instanceof ArrayList<?>) {
                      System.out.println(list.getClass());
                  }
              }
              public static void main(String[] args) {
                  List<Integer> list = new ArrayList<>();
                  rtti(list);
              }
          }
          
          

          4.b.Cast

          import java.util.ArrayList;
          import java.util.List;
          
          class Example{
              public static void main(String[] args) {
                  List<Integer> list = new ArrayList<>();
          	
          	//Invalid Cast
                  List<Number>  ln = (List<Number>) list;
              }
          }
          
          

          Cannot cast ' java.util.List < java.lang.Integer > ' to ' java.util.List < java.lang.Number > ' as it cannot be converted. The valid cast would be:

          import java.util.ArrayList;
          import java.util.List;
          
          class Example{
              public static void main(String[] args) {
                  List<Integer> list = new ArrayList<>();
                  List<Integer>  ln = (List<Integer>) list;
          	 ln.add(1);
                  System.out.println(ln);
              }
          }
          
          

          Also,

          import java.util.ArrayList;
          import java.util.List;
          
          class Example{
              public static void main(String[] args) {
                  List<Integer> list = new ArrayList<>();
                  List<Integer>  ln = (ArrayList<Integer>) list;
                  ln.add(1);
                  System.out.println(ln);
              }
          }
          
          

          And:

          import java.util.ArrayList;
          import java.util.List;
          
          class Example{
              public static void main(String[] args) {
                  List<Integer> list = new ArrayList<>();
                  ArrayList<Integer>  ln = (ArrayList<Integer>) list;
                  ln.add(1);
                  System.out.println(ln);
              }
          }
          
          

          Reason : In the above examples the compiler knows that a type parameter is always valid and allows the cast.

      • 5.Cannot Create Arrays of Parameterized Types
        • Some examples of above rule:

          Eg -1
          
          import java.util.ArrayList;
          
          class Example{
              public static void main(String[] args) {
              
              //Cannot Create Arrays of Parameterized Types
              
                  ArrayList<String>[] arrayOfLists = new ArrayList<String>[10];
          
              }
          }
          
          

          Eg -2
          
          class Example <T>{
              public static void main(String[] args){
              
              //Cannot Create Arrays of Parameterized Types
                  Example<Integer>[] e = new Example<Integer>[10];
              }
          
          }
          
          

          Eg -3
          
          class Example <T>{
              public static void main(String[] args){
              
              //Cannot Create Arrays of Parameterized Types
                  Example<Integer>[] e = new Example<Integer>[10];
              }
          
          }
          
          

          Eg -4
          
          class Example <T>{
          
              T vals[];
               public void arr(){
               
                   //Cannot create a generic array of T
                   //Also,Cannot be instantiated
          	 
                   vals = new T[10];
               }
          }
          
          

          Reason: The reason is that there is no way for the compiler to know what type of array to actually create .

          So , the solution is using "?" wildcard as shown below :

          Solution Eg-1
          
          class Example <T>{
              public void print(){
                  System.out.println("Example");
              }
              public static void main(String[] args) {
                  Example<?>[] e = new Example<?>[10];
                  e[0] = new Example<String>();
                  e[1] = new Example<Integer>();
                  e[2] = new Example<Double>();
                  e[0].print();
                  e[1].print();
                  e[2].print();
              }
          
          }
          
          

          And in solution2:

          Solution Eg-2
          
          class Example<T > {
          
          T vals[];
          
          Example( T[] nums) {
          
          vals = nums;
          
             }
          
          }
          public static void main(String[]args){
          Integer n[] = {1,2,3,4,5};
          Example<Integer> iob = new Example<Integer>( n);
          
          }
          
          

          Here it is OK to assign reference to existent array.

          Now :

          import java.util.ArrayList;
          
          class Example {
          
              public void print(){
                  System.out.println("Example");
              }
          
              public static void main(String[]args){
                  ArrayList<? >[] al1 =  new ArrayList<? >[10];
                  al1[0] = new ArrayList<String>();
          	
          	//Compiler doesnot recognize the type .
                  al1[0].add("Hello"); //Error
          	
              }
          
          }
          
          
          

          import java.util.ArrayList;
          
          class Example {
          
              public void print(){
                  System.out.println("Example");
              }
          
              public static void main(String[]args){
                  ArrayList<? extends String >[] al1 = 
          		(ArrayList<? extends String>[]) new ArrayList<? >[10];
          		
                  al1[0] = new ArrayList<String>();
          	
          	//Compiler doesnot recognize the type .
                  al1[0].add("Hello");//Error
          
              }
          
          }
          
          
          

          Hence the solution here is :

          Solution Eg-3
          
          import java.util.ArrayList;
          
          class Example {
          
              public void print(){
                  System.out.println("Example");
              }
          
              public static void main(String[]args){
                  ArrayList<? super String>[] al1 = 
          		(ArrayList<? super String>[]) new ArrayList<?>[10];
                  al1[0] = new ArrayList<String>();
          	
          	
                  al1[0].add("Hello"); //OK
          	
                  System.out.println(al1[0]);
          
              }
          
          }
          

          Note: Here we are using the lower bound and cast to recognize the compiler the "Type" and the approach becomes complicated , hence :

          Example<?>[] e = new Example<?>[10];
          
          

          i.e. Solution 1 is considered as better approach , as type checking is enforced.

      • 6. Cannot Create, Catch, or Throw Objects of Parameterized Types
        • 1. Here are the Compile Time Errors:

          //Compile Time Error
          class Example<T>  extends Exception{ .....}
          

          
          //Compile Time Error
          class Example<T>  extends Throwable{ .....}
          
          

          2. And, cannot Catch Type Parameters:

          import java.util.ArrayList;
          class Example<T extends Exception> {
          
                  public  T get(ArrayList<T> t) {
                      try {
                         System.out.println("try");
                          return t.get(0);
                      } 
          	    
          	    catch (T e) //Error
          	    
          	    {
                          return (T) e;
                      }
                  }
          
          
          }
          
          

          But "Throws" clause with type parameter is allowed:

          import java.util.ArrayList;
          class Example<T extends Exception> {
          
                  public  T get(ArrayList<T> t) throws T {
                      return t.get(0);
                  }
          
                  public static void main(String[] args) {
                      Example<Exception> e = new Example<>();
                      ArrayList<Exception> al = new ArrayList<>();
                      al.add(new Exception("Exception Thrown"));
                      try {
                          System.out.println(e.get(al));
                      } catch (Exception exception) {
                          exception.printStackTrace();
                      }
                  }
          
          
          }
          
          

      • 7. A class cannot have two overloaded methods that will have the same signature after type erasure.
        • 
          import java.util.Set;
          
          public  class Example {
          
          //Compilation Error
              public void print(Set<Integer> strSet) { }
              public void print(Set<Integer> intSet) { }
          }
          
          

          Also,

          public  class Example<T> {
          
          //Compilation Error
              public void print(T a) { }
              public void print(T b) { }
          }
          
          

          The overloads would all share the same classfile representation and will generate a compile-time error.

      Wild Card Capture , Sub Types , Generic Anonymous Class and Generic Inner Class.

      • 1. Type Capture in Java Generics
        • In Java Generics, capture is the process of taking a type variable in a generic method or class, and introducing a new type parameter that captures the specific type that is inferred for that type variable in a given context.

          In other words, when the Java compiler encounters a generic method or class with a type variable, it needs to determine the actual type that will be used for that variable when the method or class is called. This process is called type inference. In some cases, the type inference algorithm can be ambiguous or unpredictable, which can lead to compile errors or unexpected runtime behavior.

          To resolve this issue, the compiler creates a new type parameter that "captures" the actual type that is inferred for the type variable in the current context. This new type parameter is called a capture, and it ensures that the generic method or class can work correctly with the specific type that is inferred for the type variable.

          Captures are used internally by the Java compiler and are not directly visible to the programmer.

           Such as we have :
          
          class Example{
           public static <T extends Comparable<T>> T[] max(T[] a, T[] b) {
                 for (int i = 0; i < a.length; i++) {
                     if (a[i].compareTo(b[i]) > 0) {
                         return a;
                     }
                 }
                   return b;
              }
          public static void main(String[] args) {
                  Integer[] arr1 = {1, 2, 3};
                  Integer[] arr2 = {4, 5, 6};
                  Integer[] maxArr = max(arr1, arr2);
                  for (Integer i : maxArr) {
                      System.out.println(i);
                  }
          
          
              }
          	
          }
          	
          Now while compilation , 
          Compiler can create its own "Capture" the above method as:
          
          public static <T extends Comparable<T>> T[] maxHelper (T[] a, T[] b) {
                 for (int i = 0; i < a.length; i++) {
                     if (a[i].compareTo(b[i]) > 0) {
                         return a;
                     }
                 }
                   return b;
              }
          
          i.e.
          And check that the type provided by the User is correct and then compile ,
          And it is not visible to Programmer as Compiler creates it internally i.e.
          	
          ---------	
          class Example{
           public static <T extends Comparable<T>> T[] max(T[] a, T[] b) {
                 for (int i = 0; i < a.length; i++) {
                     if (a[i].compareTo(b[i]) > 0) {
                         return a;
                     }
                 }
                   return b;
              }
          public static void main(String[] args) {
                  Integer[] arr1 = {1, 2, 3};
                  Integer[] arr2 = {4, 5, 6};
                  Integer[] maxArr = max(arr1, arr2);
                  for (Integer i : maxArr) {
                      System.out.println(i);
                  }
          
          
              }
          
          //During Compilation(Compiler creates an Capture) for type check
          public static <T extends Comparable<T>> T[] maxHelper (T[] a, T[] b) {
                 for (int i = 0; i < a.length; i++) {
                     if (a[i].compareTo(b[i]) > 0) {
                         return a;
                     }
                 }
                   return b;
              }
          //Known to compiler Only, not visible to Programmer.
          		
          }
          	
          
          	
          

      • 2. Wild Card Capture in Java Generics and Helper Method
        • Wild Card Capture is a mechanism in Java Generics that allows a wildcard expression to be captured by a type parameter, in a similar way to how a type variable can be captured by a type parameter using capture conversion.

          In Java Generics, a wildcard expression is a type argument that uses the wildcard character ("?") to represent an unknown type. Wildcards are commonly used in generic methods and classes to provide flexibility and enable more general-purpose code.

          However, when a wildcard is used in a context where a type parameter is expected, the Java compiler needs to perform capture conversion to infer the actual type that should be used for the wildcard. This process can be complex and may lead to unexpected behavior if not handled correctly.

          To avoid this issue, the Java compiler can create a new type parameter that "captures" the wildcard expression, similar to how capture conversion works for type variables. This new type parameter can then be used in place of the wildcard expression, ensuring that the generic method or class works correctly with the specific type that is inferred for the wildcard.

          Wild Card Capture is an internal mechanism used by the Java compiler and is not directly visible to the programmer.

          
          Like :
          
          public class Example {
          	public static <T>void printList(List<T> list) {
          	    for (T o : list) {
          	        System.out.println(o);
          	    }
          	}
          	
          	public static void main(String[] args) {
          		List<Integer> numbers = Arrays.asList(1, 2, 3);
          		printList(numbers);
          
              }
              -----------------------------
              //Compiler create a Capture
              ----------------------------------
              public static void printList(List<Integer> list) {
          	    for (Integer o : list) {
          	        System.out.println(o);
          	    }
          	}
               ----------------------------------
               //Only known to Compiler
               --------------------------------
               
            Similarly:
           
           public class Example {
          	public static <T>void printList(List<?> list) {
          	    for (Object o : list) {
          	        System.out.println(o);
          	    }
          	}
          	
          	public static void main(String[] args) {
          		List<Integer> numbers = Arrays.asList(1, 2, 3);
          		printList(numbers);
          
                  }
          
              }
              -----------------------------
              //Compiler create a Capture
              ------------------------------
              public static void printList(List<Integer> list) {
          	    for (Integer o : list) {
          	        System.out.println(o);
          	    }
          	}
               -----------------------------
               //Only known to Compiler
              -----------------------------
              **************************************
              **************************************
              *Also  for Multiple Upper Bound Type:*
              **************************************
              **************************************
              
              public class Example {
          	public static <T>void printList(List<? extends Number> list) {
          	    for (Object o : list) {
          	        System.out.println(o);
          	    }
          	}
          	
          	public static void main(String[] args) {
          		List<Integer> numbers = Arrays.asList(1, 2, 3);
          		printList(numbers);
          
              	}
          
              }
              ---------------------------
              //Compiler create a Capture
              --------------------------
              public static void printList(List<Integer extends Number> list) {
          	    for (Integer o : list) {
          	        System.out.println(o);
          	    }
          	}
                ---------------------------
               //Only known to Compiler
               ---------------------------------
              **************************************
              **************************************
              *    Also  for Lower Bound Type:    *
              **************************************
              **************************************
              
              public class Example {
          	public static <T>void printList(List<? super Number> list) {
          	    for (Object o : list) {
          	        System.out.println(o);
          	    }
          	}
          	
          	public static void main(String[] args) {
          		List<Number> numbers = Arrays.asList(1, 2, 3);
          		printList(numbers);
          
              	}
          
              }
              --------------------------
              //Compiler create a Capture
              ------------------------------
              public static void printList(List<Number super Number> list) {
          	    for (Number o : list) {
          	        System.out.println(o);
          	    }
          	}
              ---------------------------
              //Only known to Compiler
              ------------------------------
          

          Helper Methods: The methods which compiler creates helps to identify Types , are called Helper Methods , and whenever the Types doesnot mathes it throws "Capture of #"error.

      • 3. Raw Types And Sub Types
        • Raw Types: Raw types in Java Generics are a way to use a generic class or method without specifying its type parameters. Raw types are created by omitting the type parameter(s) from the generic class or method name.

          import java.util.ArrayList;
          import java.util.List;
          
          public class Example <T extends List<Number>>{
          	public static <T>void printList(List<? super Number> list) {
          	    for (Object o : list) {
          	        System.out.println(o);
          	    }
          	}
          	
          	public static void main(String[] args) {
          		Example eg = new Example();
          		List<Number> li = new ArrayList<>();
          		li.add(1);
          		li.add(2);
          		eg.printList(li);
          		
          
              }
              
          }
          
          

          Here "Example eg = new Example();" is in raw type i.e. doesnot mention "Type" in " < > " operator.

          Sub Types: We can subtype a generic class or interface by extending or implementing it. The relationship between the type parameters of one class or interface and the type parameters of another are determined by the extends and implements clauses.

          
          import java.util.ArrayList;
          import java.util.List;
          
          interface b2<T> extends List<T> { }
          
          public class Example3<T extends b2<? extends Number>> {
          
              T get(T t) {
                  t.forEach(System.out::println);
                  return t;
              }
          
              public static void main(String[] args) {
                  Example3<b2<Number>> e = new Example3<>();
                  Example3<b2<Integer>> e1 = new Example3<>();
                  List<Number> al = new ArrayList<>();
                  al.add(1);
                  al.add(2);
                  al.add(3);
                  System.out.println(e.get((b2<Number>) al));
                  
                  List<Integer> al1 = new ArrayList<>();
                  al1.add(1);
                  al1.add(2);
                  al1.add(3);
          
          
                  System.out.println(e1.get((b2<Integer>) al1));
          
              }
          }
          
          

          Hence:

          interface b2<T> extends List<T> { }
          
          Then:
          
          class Example<T extends b2<? extends Number>>{
          
           public static void main(String[] args) {
           
           Example3<b2<Number>> e = new Example3<>();
           Example3<b2<Integer>> e1 = new Example3<>();
           Example3<b2<FLoat>> e1 = new Example3<>();
           Example3<b2<Double>> e1 = new Example3<>();
           
           	}
          
          }
          
          i.e.,
          
          List<Number> --> b2<Number>
          List<Number> --> b2<Integer>
          List<Number> --> b2<FLoat>
          List<Number> --> b2<Double>
          
          

      • 3. Generic Anonymous Class
        • Generic Anonymous Class: Like Anonymous CLass as those classes are nested class which have no name , hence Anonymous.Whatever the class returns stored in a variable created through that Class i.e. object of that class.As they are nested inside a class , also known as Anonymous Inner Class .As they can be Parameterized, hence Generic Anonymous Class.And they override the super class/abstract class/Interface's method, while inheriting it.

          ++++++++
          +Eg: 1 +
          ++++++++
          
          interface A<T> {
              T get(T t);
          }
          
          
          public class Example {
              //Anynomous Generic Class
              A<String> a = new A<String>() {
                  @Override
                  public String get(String s) {
                      System.out.println(s);
                      return s;
                  }
              };
          public static void main(String[] args) {
              Example e = new Example();
              e.a.get("Hello");
              }
          
          
          }
          
          

          Similarly,

          ++++++++
          +Eg: 2 +
          ++++++++
          
          abstract class A2<T extends Number>{
              public abstract T m(T a);
          }
          
          class Example3{
              public static void main(String[] args) {
                  A2<Number> a = new A2< Number>() {
                      @Override
                      public Number m(Number a) {
                          return a;
                      }
                  };
          
                  System.out.println(a.m(1));
                  System.out.println(a.m(1.0));
                  System.out.println(a.m(1.0f));
          
          
                  A2<Integer> a1 = new A2< Integer>() {
                      @Override
                      public Integer m(Integer a) {
                          return a;
                      }
                  };
          
                  System.out.println(a1.m(1));
                  System.out.println(a1.m(2));
          
          
              }
          }
          
          

          i.e.,

          
          abstract class A2<T extends Number>{
              public abstract T m(T a);
          }
          
          class Example3{
              public static void main(String[] args) {
                  A2<Number> a = new A2< Number>() {
                      @Override
                      public Number m(Number a) {
                          return a;
                      }
                  };
          
          
                  A2<Integer> a1 = new A2< Integer>() {
                      @Override
                      public Integer m(Integer a) {
                          return a;
                      }
                  };
          
                 A2<Double> a2 = new A2< Double>() {
                      @Override
                      public Double m(Double a) {
                          return a;
                      }
                  };
          	
          	A2<Float> a3 = new A2< Float>() {
                      @Override
                      public Float m(Float a) {
                          return a;
                      }
                  };
          	
          	//....etc.
          
          
              }
          }
          
          

          Similarly,Using Wild Card in Upper Bound :

          ++++++++
          + Eg-3 +
          ++++++++
          
          import java.util.ArrayList;
          import java.util.List;
          
          abstract class A2<T extends List<? extends Number>>{
              public abstract T m(T a);
          }
          
          class Example3{
              public static void main(String[] args) {
                  A2<List<Number>> a = new A2< List<Number>>() {
                      @Override
                      public List<Number> m(List<Number> a) {
                          return a;
                      }
                  };
          
                  List<Number> al = new ArrayList<>();
                  al.add(1);
                  al.add(2.0f);
                  al.add(3.33D);
          
                  System.out.println(a.m(al));
          
          
          
                  A2<List<Integer>> a1 = new A2< List<Integer>>() {
                      @Override
                      public List<Integer> m(List<Integer> a) {
                          return a;
                      }
                  };
                  List<Integer> al1 = new ArrayList<>();
          
                  al1.add(1);
                  al1.add(2);
                  al1.add(3);
          
                  System.out.println(a1.m(al1));
          
                  //Same for Double, Float,
                  // Long, Short, Byte,
                  //Which are extended by Number
          
          
          
              }
          }
          
          
          

          Similarly,in Multiple Upper Bound :

          ++++++++
          + Eg-4 +
          ++++++++
          
          import java.util.AbstractList;
          import java.util.ArrayList;
          import java.util.List;
          
          abstract class A2<T extends AbstractList<Number>&List<Number>>{
              public abstract T m(T a);
          }
          
          class Example3{
              public static void main(String[] args) {
                  A2<ArrayList<Number>> a = new A2< ArrayList<Number>>() {
                      @Override
                      public ArrayList<Number> m(ArrayList<Number> a) {
                          return a;
                      }
                  };
          
                  ArrayList<Number> al = new ArrayList<>();
                  al.add(1);
                  al.add(2.0f);
                  al.add(3.33D);
          
                  System.out.println(a.m(al));
          
          
          
              }
          }
          
          
          

          Similarly,using Lower Bound :

          ++++++++
          + Eg-5 +
          ++++++++
          
          import java.util.ArrayList;
          import java.util.List;
          
           class A2<T extends List<? super Number>>{
              public  T m(T a){
                  return a;
              };
          }
          
          class Example3{
              public static void main(String[] args) {
                  A2<ArrayList<Number>> a = new A2< ArrayList<Number>>() {
                      @Override
                      public ArrayList<Number> m(ArrayList<Number> a) {
                          return a;
                      }
                  };
          
                  ArrayList<Number> al = new ArrayList<>();
                  al.add(1);
                  al.add(2.0f);
                  al.add(3.33D);
          
                  System.out.println(a.m(al));
          
          A2<ArrayList<Object>> a1 = new A2< ArrayList<Object>>() {
                      @Override
                      public ArrayList<Object> m(ArrayList<Object> a) {
                          return a;
                      }
                  };
          
                  ArrayList<Object> al1 = new ArrayList<>();
                  al1.add(1);
                  al1.add(2.0f);
                  al1.add(3.33D);
          
                  System.out.println(a1.m(al1));
          
                  A2<ArrayList<Serializable>> a2 = new A2< ArrayList<Serializable>>() {
                      @Override
                      public ArrayList<Serializable> m(ArrayList<Serializable> a) {
                          return a;
                      }
                  };
          
                  ArrayList<Serializable> al2 = new ArrayList<>();
                  al2.add(1);
                  al2.add(2.0f);
                  al2.add(3.33D);
          
                  System.out.println(a2.m(al2));
          
          //Object and Serializable are super types of Number class
          
          
              }
          }
          
          
          

          Other Examples:

    interface A<T> {
        public T get(T a);
    }
    
    interface B<T> {
        public T get(T b);
    }
    
    class C<T> implements A<T>, B<T> {
        public T get(T c) {
            return c;
        }
    }
    
    class Example<T extends A<? extends Number> & B<? extends Number>>{
        public static void main(String[] args) {
            A<Number> a = new A<Number>(){
                @Override
                public Number get(Number a) {
                    return a;
                }
            };
    
            System.out.println(a.get(1));
    
            B<Integer> b = new B<Integer>(){
                @Override
                public Integer get(Integer b) {
                    return b;
                }
            };
    
            System.out.println(b.get(2));
    
            
        }
    
        // Same for Double, Float,
        // Long, Short, Byte,
        // Which are extended by Number
         
    
    
    
    }
    

  • 2. Multiple Upper Bound Wild Card with Lower Bound
  • import java.io.Serializable;
    
    interface A1<T> {
        public T get(T a);
    }
    
    interface B1<T> {
        public T get(T b);
    }
    
    class C1<T> implements A1<T>, B1<T> {
        public T get(T c) {
            return c;
        }
    }
    
    
    public class Example<T extends A1<? super Number> & B1<? super Number>> {
    
        public static void main(String[] args) {
            A1<Number> a = new A1<Number>(){
                @Override
                public Number get(Number a) {
                    return a;
                }
            };
    
            System.out.println(a.get(1));
    
            B1<Object> b = new B1<Object>(){
                @Override
                public Object get(Object b) {
                    return b;
                }
            };
    
            System.out.println(b.get(2));
    
    
            B1<Serializable> b2 = new B1<Serializable>() {
                @Override
                public Serializable get(Serializable b) {
                    return b;
                }
            };
    
            System.out.println(b2.get(3));
    
        }
        
        
        
    }
    
    

    Hence, everything depends upon what Type the Super Interface / Class have .

    
    
    class/ abstract class/Interface Eg <TYPE >{
        
        Method();
    }
    
    class Ex{
    
    Eg <type> e = Eg<type>{
    
    	@Ovveride
    	Method();
    
       }
    
    }
    
    //type depends upon Type 
    
    

    ***Until JDK 9 arrived , the diamond operator and generic class is possible only with normal classes , i.e. if we run any program with anonymous class and type parameter in JDK 7 it will throw error , hence Java developers extended the feature of the diamond operator in JDK 9 by allowing the diamond operator to be used with anonymous inner classes too.***

  • 4. Generic Inner Class
  • ------x-------



    Implementation of Lambda Expression, Functional Interface Method References AND ConstructorReference in Java Generics

    Rule:
    	
    Here is a rule , constructor reference depends upon the 
    Functional Interface and calls the single function in the interface, 
    the Type parameter of Interface should match with the,
    Class's type parameter.	
    i.e.,
    
    interface ConstRef1<T extends Number>
    {
     MyFunc1<T>func();
    }
    class MyFunc1<T extends Number>
    {
     MyFunc();
    }
    
    Hence, <T extends Number> should exist in both.

  • Constructor Reference with Upper Bound WildCard →Eg-3
  • Rule:
    	
    Note: In wild card upper bound if class Type is,
    extended to a class say Number then interface Type
    should be those classes which extends the Type Class
    (Sub Classes)or remain Same.
    
    //Same
    interface ConstRef1<T extends List < ? extends Number>>
    {
     MyFunc1<T>func();
    }
    class MyFunc1<T extends List < ? extends Number>>
    {
     MyFunc();
    }
    
    :OR:
    
    //Using Sub Class in Interface
    interface ConstRef1<T extends List <Integer>>
    {
     MyFunc1<T>func();
    }
    class MyFunc1<T extends List < ? extends Number>>
    {
     MyFunc();
    }	

  • Constructor Reference with Lower Bound WildCard →Eg-4
  • Rule:
    	
    Note: In wild card upper bound if class Type is,
    Super to a class say Number then interface Type
    should be of Super classes or remain Same.
    
    //Same
    interface ConstRef1<T extends List < ? super Number>>
    {
     MyFunc1<T>func();
    }
    class MyFunc1<T extends List < ? super Number>>
    {
     MyFunc();
    }
    
    :OR:
    
    //Using Sub Class in Interface
    interface ConstRef1<T extends List <Serializable>
    {
     MyFunc1<T>func();
    }
    class MyFunc1<T extends List < ? extends Number>>
    {
     MyFunc();
    }
    
    Note: Object,Serializable are super Classes of Number
    including Number itself.

    Other Examples:

  • Constructor Reference with Upper Bound WildCard Having More Than One Interface and One Class →Eg-5
  • Constructor Reference with Multiple Upper Bound Having More Than One Interface and One Class →Eg-6
  • Constructor Reference with Multiple Upper Bound WildCard Having More Than One Interface and One Class →Eg-7
  • Constructor Reference with Multiple Upper Bound WildCard with Lower Bound Having More Than One Interface and One Class →Eg-8
  • Implementation of Set in Java Generics

    Implementation of Map in Java Generics

    Clone this wiki locally