Introduction
Hi Guys! Today I will show you another important feature of the Java programming language called "Generics". Well, generics means defining and using generalized types instead of specific ones. We can find different types in Java such as classes and interfaces. When we define our classes, interface, and methods, we can use the aforementioned types as parameters to use the same code with different inputs. In normal parameters, we pass values as inputs but here in typed parameters, we pass types as inputs, which is the difference between those parameters. We have a few benefits of using generic classes rather than non-generic classes. Some of them are,
- Checking types at compile-time instead of at run-time. Normally it is much easier to check types at compile-time rather than run-time.
- Elimination of casting because the compiler knows the type before starting the application.
- Enabling developers to write their own algorithms with generic types, hence they can increase the re-usability and the readability of the code.
package generics.oracle; /*We can pass any type of object to this class but there is no way to check what has been passed. For example, we may mistakenly pass Integer into the class where it is required a string.*/ public class MyObjectClass { private Object object; public Object getObject() { return object; } public void setObject(Object object) { this.object = object; } }
package generics.oracle; /*We can check the type that we should to pass into the class.*/ public class MyGenericClass<T> { private T t; public T getT() { return t; } public void setT(T t) { this.t = t; } }
- E - Element (in Java Collections Framework)
- K - Key
- N - Number
- T - Type
- V - Value
- S, U, V etc. - 2nd, 3rd, 4th types
package generics.oracle; public class GenericsDemo { public static void demoGenerics() { // Invoking and Instantiating a Generic Type MyGenericClass<String> stringMyGenericClass = new MyGenericClass<>(); stringMyGenericClass.setT("Hello"); System.out.println(stringMyGenericClass.getT()); MyGenericClass<Integer> integerMyGenericClass = new MyGenericClass<>(); integerMyGenericClass.setT(1000); System.out.println(integerMyGenericClass.getT()); } }
TelecomServicePlan.java
package generics.oracle;
public interface TelecomServicePlan<T, S> { T getPlanName(); S getDataLimit(); double getMonthlyCost(); }
TelecomServicePlanImpl.java
package generics.oracle;
public class TelecomServicePlanImpl<T, S> implements TelecomServicePlan<T, S> { private final T planName; private final S dataLimit; private final double monthlyCost; public TelecomServicePlanImpl(T planName, S dataLimit, double monthlyCost) { this.planName = planName; this.dataLimit = dataLimit; this.monthlyCost = monthlyCost; } @Override public T getPlanName() { return planName; } @Override public S getDataLimit() { return dataLimit; } @Override public double getMonthlyCost() { return monthlyCost; } @Override public String toString() { return "TelecomServicePlanImpl{" + "planName=" + planName + ", dataLimit=" + dataLimit + ", monthlyCost=" + monthlyCost + '}'; } }
GenericsDemo.java
package generics.oracle;
public class GenericsDemo { public static void demoGenerics() { // Mobile plan TelecomServicePlan<String, Integer> mobilePlan = new TelecomServicePlanImpl<>("Mobile_Plan_1", 5, 200.00); System.out.println(mobilePlan.getPlanName()); System.out.println(mobilePlan.getDataLimit()); System.out.println(mobilePlan.getMonthlyCost()); // Broadband plan TelecomServicePlan<String, Double> broadbandPlan = new TelecomServicePlanImpl<>("Broadband_Plan_1", 30.50, 2000.00); System.out.println(broadbandPlan.getPlanName()); System.out.println(broadbandPlan.getDataLimit()); System.out.println(broadbandPlan.getMonthlyCost()); } }
Type Inference
Type inference in generics in Java refers to the ability of the compiler to automatically determine the generic type argument based on the context in which the generic method or class is used. It allows you to use generics without explicitly specifying the type argument, making the code more concise and readable.
Type inference was introduced in Java 7, and it simplifies the syntax when working with generics. Prior to Java 7, you had to specify the type argument explicitly, even when it was obvious from the context. With type inference, the compiler can infer the type argument by examining the method's or class's invocation or assignment context.
Here's an example to illustrate type inference:
// Prior to Java 7 List<String> names1 = new ArrayList<String>(); // Java 7 and later (Type Inference) List<String> names2 = new ArrayList<>();
Wildcards
In Java generics, wildcards are used to provide more flexibility when working with generic types. They allow you to define generic classes, methods, or interfaces that can work with different types, including unknown types, without having to specify the exact type parameter.
There are three types of wildcards in Java generics: Unbounded Wildcard (?), Upper Bounded Wildcard (? extends T), and Lower Bounded Wildcard (? super T)
Unbounded Wildcard (?): Represented by a question mark ?, it allows the generic type to match any type. It is useful when you want to work with a collection of unknown types or when the specific type parameter is not important for the operation.
Example of an unbounded wildcard:
List<?> myList; // myList can be a List of any type
Upper Bounded Wildcard (? extends T): The upper bounded wildcard restricts the generic type to be a specific type or any of its subclasses. It is used when you want to work with a collection of objects of a certain type and its subclasses.
Example of an upper bounded wildcard:
List<? extends Number> numbersList; // numbersList can be a List of Number or its subclasses
Lower Bounded Wildcard (? super T): The lower bounded wildcard restricts the generic type to be a specific type or any of its superclasses. It is used when you want to work with a collection of objects of a certain type and its superclasses.
Example of a lower bounded wildcard:
List<? super Integer> integerList; // integerList can be a List of Integer or its superclasses (e.g., Number, Object)
Usage of wildcards in Java generics allows you to create more flexible and reusable code when dealing with unknown or diverse types. Wildcards enable you to work with collections of objects with different types, perform operations that are independent of specific types, and avoid unnecessary casting.
Example of using wildcards in a generic method:
WildcardsExample.java
package generics.oracle;
import java.util.List; public class WildcardsExample { public static double sumOfNumbers(List<? extends Number> numbers) { double sum = 0.0; for (Number number : numbers) { sum += number.doubleValue(); } return sum; } }
GenericsDemo.java
package generics.oracle;
import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class GenericsDemo { public static void demoGenerics() { // Wildcards examples List<Integer> integers = Arrays.asList(1, 2, 3); List<Double> doubles = Arrays.asList(1.5, 2.5, 3.5); System.out.println(WildcardsExample.sumOfNumbers(integers)); System.out.println(WildcardsExample.sumOfNumbers(doubles)); } }
Type Erasure
Type erasure is a process in Java generics where the compiler replaces the generic type parameters with their bounding types or with the raw types. It happens during the compilation process, and the actual generic type information is removed ("erased") at runtime.
The purpose of type erasure is to maintain backward compatibility with older Java versions that do not support generics. Type erasure allows code written with generics to interact seamlessly with non-generic code, ensuring that generic code can be used in environments that do not have knowledge of generics.
Key points about type erasure in Java generics:
- Type erasure applies only to generic types, not to non-generic types.
- During compilation, the generic type parameters are replaced with their upper bounds (in the case of upper-bounded wildcards) or with the raw types.
- For example, if you have a generic class MyClass<T>, all occurrences of T within the class are replaced with its upper bound or with Object if no upper bound is specified.
- Type information is not available at runtime for generic types. For example, List<Integer> and List<String> are both treated as List at runtime.
- The use of type erasure allows generic classes and methods to be compiled into bytecode that is compatible with older non-generic Java versions.
TypeErasureExample.java
package generics.oracle; public class TypeErasureExample<T> { private final T value; public TypeErasureExample(T value) { this.value = value; } public T getValue() { return value; } }
GenericsDemo.java
package generics.oracle; import java.util.Arrays; import java.util.List; public class GenericsDemo { public static void demoGenerics() { // Type erasure examples TypeErasureExample<Integer> integerObject = new TypeErasureExample<>(42); TypeErasureExample<String> stringObject = new TypeErasureExample<>("Hello"); System.out.println(integerObject.getValue()); // Output: 42 System.out.println(stringObject.getValue()); // Output: Hello System.out.println(integerObject.getValue() instanceof Integer); // Output: true System.out.println(stringObject.getValue() instanceof String); // Output: true // At runtime, both classes are treated as Object System.out.println(integerObject.getValue() instanceof Object); // Output: true System.out.println(stringObject.getValue() instanceof Object); // Output: true } }
Restrictions
- Cannot Instantiate Generic Types with Primitive Types: Java generics do not support using primitive types as type arguments. For example, you cannot instantiate a generic class with int, double, or char as type parameters. Instead, you must use their corresponding wrapper classes, such as Integer, Double, or Character.
- Cannot Create Arrays of Generic Types: Java does not allow creating arrays of parameterized types, such as List<String>[] or T[]. You can use collections (e.g., List<List<String>>) instead of arrays to work with generic types.
- Cannot Use Type Parameters in Static Context: Generic type parameters cannot be used in static contexts, such as static fields or static methods, because static members are shared among all instances of the class, and the type parameter's value is not specific to any instance.
- Cannot Catch Generic Types with Catch Clause: Java does not allow catching generic types in catch clauses. For example, you cannot write catch (List<String> e); instead, you must use the raw type catch (Exception e) or catch specific exceptions.
- Cannot Create Instances of Type Parameters: You cannot create new instances of a type parameter directly, such as new T() inside a generic class or method. This limitation is due to type erasure, which erases the generic type information at runtime.
- Cannot Overload Based on Generic Types: Java does not allow overloading methods based solely on generic types. For example, you cannot define two methods void myMethod(List<String> list) and void myMethod(List<Integer> list) because they have the same erasure at runtime.
- Cannot Access Static Members with Parameterized Types: You cannot access static members of a generic class using parameterized types. For example, you cannot use T.myStaticField or T.myStaticMethod() because the type information for T is not available at runtime.
- Cannot Create Instances of Type Parameters in Arrays or Varargs: Java does not allow creating arrays of type parameters, such as T[], or using type parameters in varargs, such as T.... This limitation is due to type erasure.
Post a Comment