Since JDK 1.5 introduced the concept of generics, generics allow developers to define safer types, preventing type conversion exceptions during forced type casts. Before reflection, operations between different types of data could be accomplished using Object, but forced type casting (downcasting) can lead to errors when the specific type is uncertain. The introduction of generics addresses the issue of ambiguous data types.
- Define generic classes
- Define generic interfaces
- Define generic methods
- Generic inheritance
- Generic erasure
- Advanced usage of generics
- The role of generics
Define generic classes#
To define a generic class, the syntax is as follows:
// Define generic class
class ClassName<T>{
}
Here, T represents a type name, which can be represented by other names, but is generally written as T. The types inside <> can be multiple and separated by commas. Below is a generic class example:
/**
* Define generic class
* @author jzman
* @param <T>
*/
public class GenercityClass<T1,T2> {
private T1 score;
private T2 desc;
public GenercityClass() {}
public GenercityClass(T1 score, T2 desc) {
this.score = score;
this.desc = desc;
}
public T1 getScore() {
return score;
}
public void setScore(T1 score) {
this.score = score;
}
public T2 getDesc() {
return desc;
}
public void setDesc(T2 desc) {
this.desc = desc;
}
public static void main(String[] args) {
// Specify concrete types when using, concrete types can only be reference types, not primitive types like int
GenercityClass<Integer, String> genercity = new GenercityClass<>(90,"A");
int score = genercity.getScore();
String desc = genercity.getDesc();
System.out.println("score="+score+",desc="+desc);
}
}
Clearly, a class defined using generics can specify the actual types represented by <T1,T2> based on different needs during use, thus avoiding type conversion operations and preventing ClassCastException. The compiler will check type compatibility in advance. The following will cause an error:
GenercityClass<Integer, String> genercityClass = new GenercityClass<>();
// Matches the specified concrete type
genercityClass.setScore(80);
// Cannot assign a String value to a variable of type Integer
genercityClass.setScore("A");
Define generic interfaces#
To define a generic interface, the syntax is as follows:
// Define generic interface
interface InterfaceName<T>{
ReturnType methodName<T t>;
}
Below is an example of defining a generic interface:
/**
* Generic interface: can only use specified generics in methods
* @author jzman
*/
public interface GenercityInterface<T> {
// Generics cannot be used before static properties
// T a;
// Use generics in methods
void start(T t);
}
Using generics in an interface can only be done in the methods of the interface, not on the properties of the interface, because the properties in a Java interface are modified by public static final, and the specific type represented by generics is determined during use, which is not known at compile time.
Define generic methods#
To define a generic method, the syntax is as follows:
// Define generic method
Modifier <T> ReturnType methodName{
}
Create a class as follows:
package com.manu.genericity;
public class PersonBean {
private String name;
private int age;
// Omitted Getter, Setter, etc.
//...
}
Below is an example of defining a generic method:
/**
* Generic method
* @author jzman
*/
public class GenercityMethod {
public static void main(String[] args) {
PersonBean bean = new PersonBean("tom",10);
printA("string");
printA(bean);
printB(bean);
}
// Generic does not specify its superclass, due to type uncertainty, can only access its information
public static <T> void printA(T t) {
System.out.println(t);
}
// Generic specifies a superclass, can modify its generic representation of entity information
public static <T extends PersonBean> void printB(T t) {
t.setName("haha");
System.out.println(t);
}
}
The output is as follows:
string
PersonBean [name=tom, age=10]
PersonBean [name=haha, age=10]
Since the specific type of the generic method printA is uncertain, it cannot modify its generic information. printB specifies its superclass, allowing it to modify the information represented by the generic type to some extent. Generics can be defined in methods, and whether there are generic methods has no relation to whether the class it resides in has generics.
Generic inheritance#
When a subclass inherits from a superclass, how should generics be handled? Below are several situations where a subclass inherits a generic superclass:
/**
* Generic superclass
* @author jzman
*/
public abstract class GenercityEClass<T> {
T name;
public abstract void print(T t);
}
/**
* Subclass is a generic class, type determined during use
* @author jzman
* @param <T>
*/
class Child1<T> extends GenercityEClass<T>{
T t; // Subclass properties determined by subclass
@Override
public void print(T t) {
// Superclass properties determined by superclass
T name = this.name;
}
}
/**
* Subclass specifies a concrete type
* @author jzman
*/
class Child2 extends GenercityEClass<String>{
Integer t; // Subclass properties determined by subclass
@Override
public void print(String t) {
// Superclass properties determined by superclass
String name = this.name;
}
}
/**
* Superclass generic erasure
* Subclass is a generic class, superclass does not specify type, generic erasure uses Object as a substitute
* @author jzman
* @param <T>
*/
class Child3<T/*,T1*/> extends GenercityEClass{
T t;
String t1; // Subclass properties determined by subclass
@Override
public void print(Object t) {
// Superclass properties determined by superclass
Object obj = this.name;
}
}
/**
* Both subclass and superclass have generic erasure
* @author jzman
*/
class Child4 extends GenercityEClass{
// Can only use concrete types
String str; // Subclass properties determined by subclass
@Override
public void print(Object t) {
// Superclass properties determined by superclass
Object obj = this.name;
}
}
Some conclusions can be drawn: Properties in the superclass are determined by the superclass, properties in the subclass are determined by the subclass, and when the subclass overrides methods from the superclass, the related types are determined by the superclass. This operation involving generics during inheritance relates to generic erasure, which will be explained in the following section.
Generic erasure#
Since generic erasure was mentioned in the Generic Inheritance section and is considered important, it is recorded separately. There are two situations of generic erasure, as follows:
- Generic erasure during inheritance or implementation (interface);
- Generic erasure during specific use.
When a subclass inherits from a superclass, there are two situations of generic erasure:
- Subclass generic, superclass generic erasure
- Both subclass and superclass have generic erasure
Below is part of the code:
/**
* Superclass generic erasure
* Subclass is a generic class, superclass does not specify type, generic erasure uses Object as a substitute
* @author jzman
* @param <T>
*/
class Child3<T/*,T1*/> extends GenercityEClass{
T t;
String t1; // Subclass properties determined by subclass
@Override
public void print(Object t) {
// Superclass properties determined by superclass
Object obj = this.name;
}
}
/**
* Both subclass and superclass have generic erasure
* @author jzman
*/
class Child4 extends GenercityEClass{
// Can only use concrete types
String str; // Subclass properties determined by subclass
@Override
public void print(Object t) {
// Superclass properties determined by superclass
Object obj = this.name;
}
}
/**
* Subclass erasure, superclass uses generics (error)
* @author jzman
*
class Child5 extends GenercityEClass<T>{
@Override
public void print(T t) {
}
}
*/
Note that a superclass cannot have generics while the subclass has generic erasure; only when the number of generics in the subclass is greater than or equal to that in the superclass can generic erasure occur. Implementing generic interfaces is similar to class inheritance and will not be elaborated on further.
Below is the specific use of generic erasure:
class Child1<T> extends GenercityEClass<T>{
T t; // Subclass properties determined by subclass
@Override
public void print(T t) {
// Superclass properties determined by superclass
T name = this.name;
}
public static void main(String[] args) {
// 1. Generic erasure during use
Child1 child1 = new Child1();
Object obj = child1.name; // Property name is of the erased type Object
// 2. Specify type during use
Child1<String> child2 = new Child1();
String name = child2.name; // Property name is of the specified type String
}
}
That concludes the discussion on generic erasure.
Advanced usage of generics#
The advanced usage of generics is as follows:
- Restricting the available types for generics
- Using type wildcards
Restricting the available types for generics#
By default, any type can be used to instantiate a generic class object, but you can restrict the types of instances for a generic class. The syntax is as follows:
// Restricting the available types for generics
class <T extends AnyClass>{
}
The above AnyClass can be either a class or an interface, meaning that the type for generics must inherit from the AnyClass class or implement the AnyClass interface. Whether it is a class or an interface, the type restriction is done using the extends keyword. Below is a case example:
/**
* Restricting available types for generics
* @author jzman
*/
public class LimitGenercityClass<T extends List> {
public static void main(String[] args) {
// T extends: <upper bound, T can be a subclass of the upper bound or itself
LimitGenercityClass<ArrayList<String>> limitClass1 = new LimitGenercityClass<>();
LimitGenercityClass<LinkedList<String>> limitClass2 = new LimitGenercityClass<>();
// HashMap does not implement List interface, so HashMap cannot instantiate the generic class instance
// LimitGenercityClass<HashMap<String,String>> limitClass3 = new LimitGenercityClass<>();
}
}
In the above code, the generic T is restricted, and the specific generic type must implement the List interface. ArrayList and LinkedList implement the List interface, while HashMap does not, so HashMap cannot instantiate the corresponding generic type.
Using type wildcards#
The generic mechanism provides type wildcards, mainly used to restrict the type of a generic class when creating an object to inherit or implement a certain class or interface. You can use the ? wildcard to achieve this, and you can also use the extends and super keywords to restrict generics. The syntax for using type wildcards is as follows:
// Using type wildcards
GenericClassName<? extends AnyClass> a ;
Below is an example of using type wildcards:
/**
* Using wildcards
* ? extends AnyClass: restricts the specific type of generics to be a subclass of AnyClass
* ? super AnyClass: restricts the specific type of generics to be a superclass of AnyClass
* @author jzman
*/
public class CommonCharGenercityClass<T> {
public static void main(String[] args) {
// 1. Wildcard used in type declaration
CommonCharGenercityClass<? extends List> commA = null;
// The wildcard uses the extends keyword to restrict the generic to be a subclass of List
commA = new CommonCharGenercityClass<ArrayList<String>>();
commA = new CommonCharGenercityClass<LinkedList<String>>();
// commA = new CommonCharGenercityClass<HashMap<String>>();
CommonCharGenercityClass<? super List> commB = null;
// Error, the wildcard uses the super keyword to restrict the generic to be a superclass of List, such as Object
// commB = new CommonCharGenercityClass<ArrayList<String>>();
commB = new CommonCharGenercityClass<Object>();
List<String> listA = new ArrayList<>();
listA.add("tom");
List<?> listB = listA;
// When using wildcards, since the specific type is uncertain, you can only retrieve and remove data, but cannot add data, cannot be called
// listB.add("new");
listB.remove(0);
System.out.println(listB);
}
// 2. Wildcard used in method parameters
public static void test(CommonCharGenercityClass<? extends List> list) {
//...
}
}
The type wildcard ? can be combined with the keywords extends and super to restrict the specific types of generics. Extends restricts the specific type of generics to be a subclass of the target type, while super restricts the specific type of generics to be a superclass of the target type. Additionally, type wildcards cannot be used in class declarations.
The role of generics#
- Check type safety at compile time, discovering errors in advance.
- Type conversions in generics are automatic and implicit, improving code reusability.
Summary: The type of generics must be a reference type and cannot be a primitive type. The number of generics can be multiple, and you can use ? to restrict the generic type during object creation and method parameter types, such as using the keywords extends
and super
to restrict the specific types of generics upward or downward. Lastly, generic arrays can be declared, but instances of generic arrays cannot be created.