What is a generic?

Consider the following scenario: you want to develop a container for passing objects in your application. But object types are not always the same. Therefore, you need to develop a container that can store objects of various types.

Given this situation, it is obvious that the best way to achieve this goal is to develop a container that can store and retrieve the Object type itself, and then cast the Object as it is used for various types.

The class in Example 1 demonstrates how to develop such a container.

public class ObjectContainer { private Object obj; / * * * @return the obj
     */
    public Object getObj() {
        return obj;
    }

    /**
     * @param obj the obj to set
     */
    public void setObj(Object obj) {
        this.obj = obj;
    }
    
}


ObjectContainer myObj = new ObjectContainer();

// store a string
myObj.setObj("Test");
System.out.println("Value of myObj:" + myObj.getObj());
// store an int (which is autoboxed to an Integer object)
myObj.setObj(3);
System.out.println("Value of myObj:" + myObj.getObj());

List objectList = new ArrayList();
objectList.add(myObj);
// We have to cast and must cast the correct type to avoid ClassCastException!
String myStr = (String) ((ObjectContainer)objectList.get(0)).getObj(); 
System.out.println("myStr: " + myStr);
Copy the code

While this container will do the job, it is not the most appropriate solution for our purposes. It is not type-safe and requires explicit casting when retrieving encapsulated objects, so exceptions can be thrown.

Use genericA better solution could be developed to assign a type, also known as a generic type, to the container used at instantiation time, so that an object can be created to store objects of the allocated type.

A generic type is a type parameterized class or interface, which means that a type can be assigned by performing a generic type call that will replace the generic type with the assigned concrete type. The assigned type is then used to limit the values used within the container, eliminating the need for type conversions and providing stronger type checking at compile time.

Example 2 shows how to create the same container as the one you created earlier, but this time with generic type parameters instead of Object types.

public class GenericContainer<T> {
    private T obj;

    public GenericContainer(){
    }
    
    // Pass type inas parameter to constructor public GenericContainer(T t){ obj = t; } / * * * @return the obj
     */
    public T getObj() {
        return obj;
    }

    /**
     * @param obj the obj to set
     */
    public void setObj(T t) { obj = t; }} // To use generic containers, you must specify the container type at instantiation using Angle bracket notation. // Therefore, the following code instantiates a GenericContainer of type Integer and assigns it to the myInt field. GenericContainer<Integer> myInt = new GenericContainer<>(); // GenericContainer<Integer> myInt = new GenericContainer<Integer>(); Myint.setobj (3) will not compile if we try to store other types of objects in an already instantiated container; // OK myInt.setObj("Int"); // Won't Compile
Copy the code

The most significant difference is that the class definition contains that the class field obj is no longer of type Object, but of generic type T. Between the Angle brackets in the class definition is the type parameters section, which describes the type parameters (or parameters) to be used in the class. T is the parameter associated with a generic type defined in this class.

The benefits of using generics

One of the most important benefits is stronger type checking, because avoiding classcastExceptions that can be raised at runtime saves time.

Another benefit is the elimination of type conversions, which means less code can be used because the compiler knows exactly what types are stored in the collection.

How do I use generics

There are many different use cases for generics. This article introduced the use case for generating generic object types in the previous examples. This is a good starting point for understanding generic syntax at the class and interface level.

Class signatureContains aType parametersPart, included inAngle brackets after the class name(< >)

Such as:

public class GenericContainer<T> {
...
Copy the code

Type parameters (also known as type variables) are used as placeholders to indicate that a type is assigned to a class at run time. Depending on the need, there may be one or more type parameters and they can be used for the entire class. By convention, a type parameter is a single uppercase letter that is used to indicate the type of parameter being defined. The standard type parameters for each use case are listed below:

  • E: the element
  • K: a key
  • N: digital
  • T: type
  • V: value
  • S, U, V, etc. : the second, third, and fourth types in the multi-parameter case

In the example above, T indicates the type to be assigned, so GenericContainer can be assigned any valid type at instantiation time. Note that the T parameter is used for the entire class, indicating the type specified at instantiation time. When we instantiate the object, we replace all T arguments with String:

GenericContainer<String> stringContainer = new GenericContainer<String>();
Copy the code

Generics can also be used in constructors to pass type parameters needed for class domain initialization. GenericContainer’s constructor allows any type to be passed on instantiation:

GenericContainer gc1 = new GenericContainer(3);
GenericContainer gc2 = new GenericContainer("Hello");
Copy the code

Note that a generic type without an assigned type is called a primitive type. For example, to create a GenericContainer of primitive type, use the following code:

GenericContainer rawContainer = new GenericContainer();
Copy the code

Primitive types are sometimes useful for backward compatibility, but not for everyday code. Primitive types do not require type checking at compile time, making code error-prone at run time.

Multiple generic types

Sometimes, it helps to be able to use multiple generic types in a class or interface. You can use multiple type parameters in a class or interface by placing a comma-separated list of types between Angle brackets.

The class in the following example demonstrates this concept using a class that accepts two types: T and S.

public class MultiGenericContainer<T, S> {
    private T firstPosition;
    private S secondPosition;
   
    public MultiGenericContainer(T firstPosition, S secondPosition){
        this.firstPosition = firstPosition;
        this.secondPosition = secondPosition;
    }
    
    public T getFirstPosition() {return firstPosition;
    }
    
    public void setFirstPosition(T firstPosition){
        this.firstPosition = firstPosition;
    }
    
    public S getSecondPosition() {return secondPosition;
    }
    
    public void setSecondPosition(S secondPosition){ this.secondPosition = secondPosition; }}Copy the code

The MultiGenericContainer class can be used to store two different objects, the type of each object can be specified at instantiation time.

The usage of containers is as follows

MultiGenericContainer<String, String> mondayWeather =
        new MultiGenericContainer<String, String>("Monday"."Sunny");
MultiGenericContainer<Integer, Double> dayOfWeekDegrees = 
        new MultiGenericContainer<Integer, Double>(1, 78.0);

String mondayForecast = mondayWeather.getFirstPosition();
// The Double type is unboxed--to double, in this case. More on this in next section!
double sundayDegrees = dayOfWeekDegrees.getSecondPosition();
Copy the code

Bounded type

We often find ourselves in situations where we need to specify generic types, but want to be able to control what type is specified, rather than being unrestricted. Bounded types limit the boundaries of generic types by specifying the extends or super keyword in the type parameter section, limiting the type with an upper or lower limit, respectively.

If you want to restrict a type to a specific type or a subtype of a specific type, use the following notation:

<T extends UpperBoundType>
Copy the code

Similarly, if you want to restrict a type to a specific type or supertype of a specific type, use the following notation:

<T super LowerBoundType>
Copy the code

What is PECS?

PECS refers to Producer Extends, Consumer Super. If you want to iterate through a collection and operate on each element, then the collection is a producer (production element), you should use collection <? Extends Thing >. If you want to add elements to a collection, then the collection is the consumer (the consumer element) and should use collection <? Super Thing >.

Generic method

Sometimes, we may not know the parameter type of the method passed in. Applying generics at the method level can solve such problems. Method parameters can contain generic types, and methods can also contain generic return types.

Suppose we want to develop a calculator class that accepts type Number. Generics can be used to ensure that any Number type can be passed as an argument to this class of computation.

For example, the add() method in the following example demonstrates how to use generics to limit the type of two parameters to ensure that they contain an upper limit on Number:

public static <N extends Number> double add(N a, N b){
    double sum = 0;
    sum = a.doubleValue() + b.doubleValue();
    return sum;
}  
Copy the code

By limiting the type to Number, you can pass any object in the Number subclass as a parameter. Also, by limiting the type to Number, we can ensure that any arguments passed to the method will contain the doubleValue() method. To see this in action, if you want to add an Integer and a Float, you can call this method as follows:

double genericValue1 = Calculator.add(3, 3f);
Copy the code

The wildcard

In some cases, it is useful to write code that specifies an unknown type. The question mark? Wildcards can be used to represent unknown types using generic code. Wildcards can be used for parameters, fields, local variables, and return types. But it’s best not to use wildcards in return types, because it’s safer to know exactly what type a method returns.

Suppose we want to write a method that verifies the existence of a specified object in a specified List. We want this method to take two arguments: a List of unknown type and an object of arbitrary type.

public static <T> void checkList(List<? > myList, T obj){if(myList.contains(obj)){
            System.out.println("The list contains the element: " + obj);
        } else {
            System.out.println("The list does not contain the element: "+ obj); }}Copy the code

Use the sample

// Create List of type Integer
List<Integer> intList = new ArrayList<Integer>();
intList.add(2);
intList.add(4);
intList.add(6);

// Create List of type String
List<String> strList = new ArrayList<String>();
strList.add("two");
strList.add("four");
strList.add("six");

// Create List of type Object
List<Object> objList = new ArrayList<Object>();
objList.add("two");
objList.add("four");
objList.add(strList);

checkList(intList, 3); 
// Output:  The list [2, 4, 6] does not contain the element: 3

checkList(objList, strList); 
/* Output:  The list [two, four, [two, four, six]] contains 
the element: [two, four, six] */

checkList(strList, objList);
/* Output:  The list [two, four, six] does not contain 
the element: [two, four, [two, four, six]] */
Copy the code

Sometimes upper or lower limits are used to limit wildcards. Much like specifying bounded generic types, you can declare bounded wildcard types by specifying the extends or super keyword plus a wildcard, followed by a type for upper or lower limits.

For example, if we wanted to change the checkList method to accept only lists of type extended Number, we could write the code shown in Listing 14.

public static <T> void checkNumber(List<? extends Number> myList, T obj){
    if(myList.contains(obj)){
        System.out.println("The list " + myList + " contains the element: " + obj);
    } else {
        System.out.println("The list " + myList + " does not contain the element: "+ obj); }}Copy the code

conclusion

Generics are basically a compile-time application, a technique for the compiler to use, but at runtime, generics don’t exist. This is because there are no generics in the regenerated class file after the editor checks that they are of the correct type.

Considerations for using generics

  • When instantiating an Object without specifying a generic, the default is: Object.
  • Primitive data types cannot be used in the specification of generics; wrapper classes can be used instead.
  • Class generics cannot be used in static methods
  • Multiple bindings can be bound at the same time, using&The connection
  • A generic class may have multiple parameters, in which case you should put the parameters together in Angle brackets. Such as < E1, E2 and E3 >
  • To subclass from a generic class, the generic type needs to be materialized: After inheriting the generic class, the corresponding type of the subclass type needs to be materialized
  • If the generic class is an interface or abstract class, objects of the generic class cannot be created.