Since JDK 1.5 introduced the concept of generics, generics allow developers to define safer types, preventing type conversion exceptions during forced type conversions. Before reflection, operations between different types of data could be done through Object, but forced type conversion (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, and T can be represented by other names, but it is generally written as T. The types inside <> can be multiple, separated by commas. Below is a generic class, specifically:
/**
* 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, the concrete type can only be a reference type, not a primitive type 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> according to different needs during use, thus avoiding type conversion operations and preventing ClassCastException. The compiler will check type compatibility in advance. The following will result in 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 a definition of 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);
}
In an interface, generics can only be used in the methods of the interface and cannot be used on the properties of the interface because the properties in Java interfaces 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 a definition of 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);
}
// Generics do not specify their superclass, and since the type cannot be determined, only its information can be accessed
public static <T> void printA(T t) {
System.out.println(t);
}
// Generics specify a superclass, allowing modification of the entity information represented by the generic
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 some modification of the information represented by the generic type. Generics can be defined in methods, and whether there is a generic method is unrelated to whether the class it belongs to 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's property is determined by the subclass
@Override
public void print(T t) {
// Superclass's property is determined by the superclass
T name = this.name;
}
}
/**
* Subclass specifies a concrete type
* @author jzman
*/
class Child2 extends GenercityEClass<String>{
Integer t; // Subclass's property is determined by the subclass
@Override
public void print(String t) {
// Superclass's property is determined by the superclass
String name = this.name;
}
}
/**
* Erasure of generic superclass
* Subclass is a generic class, superclass does not specify type, erasure uses Object as a substitute
* @author jzman
* @param <T>
*/
class Child3<T/*,T1*/> extends GenercityEClass{
T t;
String t1; // Subclass's property is determined by the subclass
@Override
public void print(Object t) {
// Superclass's property is determined by the 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's property is determined by the subclass
@Override
public void print(Object t) {
// Superclass's property is determined by the superclass
Object obj = this.name;
}
}
Some conclusions can be drawn: The properties in the superclass are determined by the superclass, while the properties in the subclass are determined by the subclass. 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 below.
Generic erasure#
The section on generic inheritance involves generic erasure, which is considered important, so 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 generics, superclass generics erased
- Both subclass and superclass have generic erasure
Below is part of the code:
/**
* Erasure of generic superclass
* Subclass is a generic class, superclass does not specify type, erasure uses Object as a substitute
* @author jzman
* @param <T>
*/
class Child3<T/*,T1*/> extends GenercityEClass{
T t;
String t1; // Subclass's property is determined by the subclass
@Override
public void print(Object t) {
// Superclass's property is determined by the 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's property is determined by the subclass
@Override
public void print(Object t) {
// Superclass's property is determined by the 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 the 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 upon.
Below is the specific use of generic erasure:
class Child1<T> extends GenercityEClass<T>{
T t; // Subclass's property is determined by the subclass
@Override
public void print(T t) {
// Superclass's property is determined by the 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 usable types for generics
- Using type wildcards
Restricting usable types for generics#
By default, any type can be used to instantiate a generic class object, but you can also restrict the types of instances of the generic class. The syntax is as follows:
// Restrict usable types for generics
class <T extends AnyClass>{
}
The above AnyClass can be either a class or an interface, meaning that the type of the generic 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 an example:
/**
* Restriction on usable 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 the List interface, so HashMap cannot instantiate the corresponding generic class
// 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 the usage of 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, due to the uncertainty of the specific type, you can only retrieve and remove data, but cannot add data, and 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 types during object creation and method parameter types, such as using the keywords extends
and super
to restrict the specific types of generics upwards or downwards. Lastly, generic arrays can be declared, but instances of generic arrays cannot be created.