< Generics >

What is Generics?

  • Generics checks errors at compile time.
  • Generics helps in type safety without copy and paste.
  • Generics allow you to abstract over types. The most common examples are container types, such as those in the Collections hierarchy.
  • It is suitable for the instance of a class

Generic Examples


                        

Generic Class Demo


                        

Advantage of Java Generics

  1. Type-safety : We can hold only a single type of objects in generics. It doesn’t allow to store other objects
  2. Type casting is not required: There is no need to typecast the object.
  3. Compile-Time Checking: It is checked at compile time so problem will not occur at runtime. The good programming strategy says it is far better to handle the problem at compile time than runtime
  4. Github code sample Generic

                    

Type Safe

  • Generic Type safe
  • 
                            

    Generic Method

  • Generic methods
  • 
                            

    Generic Interface

  • Generic Interface
  • 
                            

    Generic Type Interface

    • Generic type can be defined for a class or interface.
    • Generic cannot be used for static instance
    • Only generic classes can implement generic interfaces. Normal classes can’t implement generic interfaces.It must mention the type For example, above generic interface can be implemented as,code Generic Interface

    
                        

    Summary

    CodeValid?Reason
    class GenericClass<T> implements GenericInterface<T>YesGeneric class implements generic interface with the same type parameter.
    class NormalClass implements GenericInterface<T>NoNon-generic class cannot use generic type parameter T.
    class NormalClass implements GenericInterface<Integer>YesNon-generic class specifies a concrete type (Integer).
    class class0 implements Iinterface<Integer>YesNon-generic class implements generic interface with a concrete type.
    class class5<T> extends class4<T> implements Iinterface<Integer>NoInconsistent type usage (generic T vs. concrete Integer).

    Type Inference

    • The Java generics features were updated in Java 7. From Java 7 the Java compiler can infer the type of the collection instantiated from the variable the collection is assigned to.
    • Notice how the generic type of the ArrayList has been left out. Instead is only the <> written. This is also sometimes referred to as the diamond operator. When you just write a diamond operator as generic type, the Java compiler will assume that the class instantiated is to have the same type as the variable it is assigned to. In the example below, that means String because the List variable has String set as its type.
    • Github code Generic Inference

    1. The compiler looks at the left-hand side of the assignment (the variable declaration) to determine the type.

    2. It then applies that type to the right-hand side (the object instantiation) where the diamond operator is used.

    • The left-hand side is List<String> list, so the compiler knows the type is String.

    • The right-hand side is new ArrayList<>(), so the compiler infers that the ArrayList should also be of type String.

    Generic Method

                        
                        

    Generic Raw Types

    • You can use a generic class without specifying a concrete type.
    • When generics were introduced in JDK 1.5, raw types were retained only to maintain backwards compatibility with older versions of Java. Although using raw types is still possible, they should be avoided:
    • A generic class such as ArrayList used without parameter type is known as raw type.
    • Rawtype is unsafe.
    • When to Use Raw Types Raw types should only be used in rare cases, such as: Interacting with legacy code that does not use generics.Working with libraries or frameworks that require raw types. In all other cases, always use generics with type parameters to ensure type safety and avoid runtime errors.
    • Github code Generic Raw Type
    
                        

    Bounded Type Parameters

    • For e.g. you want to find an average of two or more numbers using array, the complier does not know whether you are entering string or number. This could cause compile-time error
    • if a generic class is having operations of numeric type only then there is no need to have any other type except numeric type as the type argument
    • To declare a bounded type parameter, list the type parameter’s name, followed by the extends keyword, followed by its upper bound, similar like below method. Extends keyword for both Classes and Interfaces.
    • Bounded type parameters can be used with methods as well as classes and interfaces
    • < T extends A1 & A2 & A3 > No matter whether it is class or interface.
    • Java Generics supports multiple bounds .
    • Github code BoundedType Class and Method
    • Github code BoundedType Class and Interface
    
                        

    Subtyping

    Sub-Typing

    Github code Sub Typing
    
                        

    Wildcard Generic Types

    • In generic code, the question mark (?), called the wildcard, represents an unknown type.
    • You can use unbounded wildcards,
    • Unbound Wildcards or Unknown type
    • Upper-bound wildcards
    • Lower-bound wildcards
    • List< Integer > is NOT a subtype of List < Number > this is the reason we need to use wildcards

    Unbound

    • The unbounded wildcard type is specified using the wildcard character (?)—for example, (List of ?). This is called a list of unknown type.
    • Java generics unbounded wildcards : Unbounded wildcard is used for list of unknown types using '?'(Type is not bounded)
    • Github code Unbound Wildcards
    
                        

    Upper-Bounded Wildcards extends

    • To declare an upper-bounded wildcard, use the wildcard character (?), followed by the extends keyword, followed by its upper bound.
    • Upper-bound is when you specify (? extends Field) means argument can be any Field or subclass of Field.
    • List (extends Number) represent a list of Number or its sub-types such as Integer
    • For example, < ? extends Number> means the type can be Number, Integer, Double, Float, etc
    • Upper Bound: Used when you want to relax the restrictions on a variable, i.e. you want to write a method that works on List
    • Github code Upper-Bounded Wildcards
                        
                        

    Lower-Bounded Wildcards super

    • Suppose we want to add Integers to a list of integers in a method, we can keep the argument type as List (Integer)
    • It will be tied up with Integers whereas List Number and List Object can also hold integers, so we can use lower bound wildcard
    • We use generics wildcard (?) with super keyword and lower bound class to achieve this.
    • super is used to specify an lower bound wildcard.
    • Lower Bounded Wildcards: List(? super Integer) represents a list of Integer or its super-types Number and Object
    • Github code Lower-Bounded Wildcards
    
                        

    When to use super and extends

    • use Upper bounded wildcard ( extends) when you only get values out of the collection
    •                         
                                  List numbers = new ArrayList< Integer>();
                                  Number num = numbers.get(0); // Valid: Reading is allowed
                                  // numbers.add(10); // Invalid: Cannot add to a list of unknown subtype
                              
                              
    • user lower bounded wildcard ( super ) when you only put values into the collection
    •                         
                                  List numbers = new ArrayList< Number>();
                                  numbers.add(10); // Valid: Adding is allowed
                                  // Integer num = numbers.get(0); // Invalid: Reading returns Object
                                  Object obj = numbers.get(0); // Valid: Reading as Object
                              
                              
    • Use Case: Do not use wildcards if you need to both read from and write to a collection.
    • Reason: Wildcards (?) make the type unknown, so you cannot safely add or read specific types.
    •                         
                                  List < Integer> numbers = new ArrayList<>();
                                  numbers.add(10); // Valid: Adding is allowed
                                  Integer num = numbers.get(0); // Valid: Reading is allowed
                              
                              

    Erasure and Restrictions on Generics

    Generics in Java are implemented using a mechanism called type erasure. This approach ensures that generic code is backward-compatible with legacy code that uses raw types. However, it also imposes certain restrictions on how generics can be used

    1. At Compile Time:

      • The compiler uses generic type information to enforce type safety.

      • It checks that the code adheres to the generic type constraints (e.g., you can’t add a String to a List<Integer>).

    2. At Runtime:

      • The generic type information is erased (removed).

      • All generic types are replaced with their raw types (e.g., List<T> becomes List).

      • Type parameters are replaced with their upper bound (e.g., T becomes Object if no explicit bound is specified).

    Cannot Instantiate Generic Types with Primitive Types

    • Generic type parameters must be reference types (e.g., Integer, String).

    • You cannot use primitive types (e.g., int, char) as type arguments.

    
                            // List< int> list = new ArrayList <>(); // Invalid
                            List< Integer> list = new ArrayList<>(); // Valid  
    
                            class Box< T> {
                                T createInstance() {
                                    // return new T(); // Invalid: Cannot instantiate type parameter
                                }
                            }
                            List< String> strings = new ArrayList<>();
                            // if (strings instanceof List< String>) { } // Invalid
                            if (strings instanceof List) { } // Valid: Raw type check
    
                            // List< String>[] array = new List< String>[10]; // Invalid
                            List[] array = new List[10]; // Valid: Unbounded wildcard
    
                            class Example {
                                void print(List< String> list) { }
                                // void print(List< Integer> list) { } // Invalid: Same erased signature
                            }
                        

    Thank you