Detailed analysis of generics in Java, in-depth analysis of the use of generics

Posted by jennatar77 on Mon, 24 Jan 2022 23:44:55 +0100

Basic concepts of generics

  • Generics: parameterized types
    • Parameters:
      • There are formal parameters when defining a method
      • Pass arguments when calling a method
    • Parameterized type: parameterizes the type from the original specific type, similar to the variable parameter in the method
      • A type is defined as a parameter, which can be called a type parameter
      • When using or calling, you pass in a specific type, which can be called a type argument
  • The essence of generics is to parameterize types
    • Without creating a new type, the type of formal parameter specific restriction is controlled by different types specified by generics
    • During the use of generics, the data type of the operation is specified as a parameter, which can be used in:
      • Class - generic class
      • Interface - Generic interface
      • Method - generic method
  • Generic example:
 List arrayList = new ArrayList(); arrayList.add("aaaa"); arrayList.add(100);   arrayList.forEach(i -> { 	String item = (String) arrayList.get(i);  Log.d("generic paradigm", "item = " + item); });
  • Such writing will lead to abnormal crash and end of the program:
 	java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
  • The ArrayList here can store any type. A String type and an Integer type are added. When they are reused, they are used in the form of String, so the program crashes
  • Generics solve this problem
  • In another case, if you modify the initial code declared in the first line, you can find the problem at the compilation stage:
 List arrayList = new ArrayList<String>(); arrayList.add("aaaa"); arrayList.add(100); // In this step, the compiler will report an error ArrayList forEach(i -> {   	 String item = (String) arrayList.get(i);   	 Log.d("generic", "item =" + item);});
  • Generics are only valid at compile time:
List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();

Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();

if (classStringArrayList.equals(classIntegerArrayList)) {
	Log.d("generic paradigm", "Same type");
}

It can be found that after compilation, the program will take de genericization measures In other words, generics in Java are only valid at the compilation stage In the compilation process, after the generic results are correctly verified, the relevant information of the generic will be erased, and type checking and type conversion methods will be added at the boundary where the object enters and leaves the method

  • A generic type can logically be regarded as multiple different types, which are actually the same basic types

Use of generics

  • Generics can be used in three ways:
    • Generic class
    • generic interface
    • generic method

Generic class

  • Generic class: generic types are used in class definitions
    • Through generic classes, you can complete the operation of a group of classes and develop the same interface externally
    • The most typical are various container classes:
      • List
      • Set
      • Map
  • The most basic way to write a generic class:
class Class name <Generic identity: Identification number,Identifies the type of the specified generic> {
	private Generic identity member variable type member variable name;
}
  • Example:
/*
 * Here, t can be any identifier. Generally, parameters in the form of T,E,K,V, etc. are used to represent generics
 * When instantiating a generic type, you must specify the specific type of T
 */
 public class Generic<T> {
 	// key the type of this member variable is t, and the type of T is specified externally
 	private T key;
	
	// The type of generic constructor parameter key is also t, and the type of T is specified externally
	public Generic(T key) {
		this.key = key;
	}

	// The return value type of the generic constructor getKey is t, and the type of T is specified externally
	public T getKey() {
	}
 }
/*
 * Generic type parameters can only be class types, including custom classes Cannot be a simple type
 */
 // The passed in argument type needs to be the same as the parameter type of the generic type, that is, Integer
 Generic<Integer> genericInteger = new Generic<Integer>(123456);
 // The input argument type needs to be the same as the parameter type of generic type, that is, String
 Generic<String> genericString = new Generic<String>("key_value");

 Log.d("Generic testing", "key is" + genericInteger.getKey());
 Log.d("Generic testing", "key is" + genericString.getKey());
Generic testing: key is 123456
 Generic testing: key is key_value

Arguments of generic types do not have to be passed in generic classes:

  • If a generic argument is passed in, corresponding restrictions will be made according to the passed in generic argument. At this time, the generic will play the role of restriction that it should play
  • If you do not pass arguments such as generic types, the definition of methods or member variables that use generic types in generic classes can be any type
Generic genericString = new Generic("1111");
Generic genericInteger = new Generic(5555);
Generic genericBigDecimal = new Generic(66.66);
Generic genericBoolean = new Generic(true);

Log.d("Generic testing", "key is" + genericString.getKey());
Log.d("Generic testing", "key is" + genericInteger.getKey());
Log.d("Generic testing", "key is" + genericBigDecimal.getKey());
Log.d("Generic testing", "key is" + genericBoolean.getKey());
D/Generic testing: key is 1111
D/Generic testing: key is 5555
D/Generic testing: key is 66.66
D/Generic testing: key is true

The type parameter of a generic type can only be a class type, not a simple type

The instanceof operation cannot be used on the exact generic type, and an error will occur during compilation

generic interface

  • The definition and use of generic interfaces and generic classes are basically the same
  • Generic interfaces are often used in various kinds of producers
  • Example:
// Define a generic interface
public interface Generator<T> {
	public T next();
}
  • When a class implementing a generic interface does not pass in a generic argument:
/**
 * When the generic argument is not passed in, the definition is the same as that of the generic class. When declaring the class, the declaration of the generic class should also be added to the class:
 * 		That is, class fruitgenerator < T > implements generator < T > {}
 * 		If you do not declare generics, for example: class fruitgenerator implements generator < T > The compiler will report an error - Unknown class
 */
 class FruitGenerator<T> implements Generator<T> {
 	@Override
 	public T next() {
 		return null;
 	}
 }
  • When a class implementing a generic interface passes in a generic argument:
/**
 * When passing in generic arguments:
 * 		Define a producer to implement this interface
 * 		Although only one generic interface Generator < T > is created, countless arguments can be passed in for t to form countless types of Generator interfaces
 * 		When the implementation class implements the generic interface, if the generic type has been passed into the argument type, all places where generics are used will be replaced with the passed in argument type
 * 			Namely: generator < T >, public t next(); The t here should be replaced with the incoming String type
 */
 public class FruitGenerator implements Generator<String> {
 	private String[] fruits = new String[]{"Apple", "Banana", "Pear"};

	@Override
	public String next() {
		Random rand = new Random();
		return fruits[rand.nextInt(3)];
	}
 }

Generic wildcard

  • Integer is a subclass of number. Generic < integer > and generic < number > are actually the same type
  • As a result, the following problems arise:
    • In the method using generic < number > as a formal parameter, can I use an instance of generic < integer >?
    • Logically, can generic < number > and generic < integer > be regarded as generic types with parent-child relationship?
  • Generic < T > generic class example:
public void showKeyValue1(Generic<Number> obj) {
	Log.d("Generic testing", "key value is" + obj.getKey());
}
Generic<Integer> gInteger = new Generic<Integer>(123);
Generic<Number> gNumber = new Generic<Number>(456);

showKeyValue(gNumber);
showKeyValue The compiler will report an error for this method:
	Generic<java.lang.Integer> cannot be applied to Generic<java.lang.Number> showKeyValue(gInteger);

It can be seen that generic < integer > cannot be regarded as a subclass of generic < number >

  • thus it can be seen:
    • The same generic type can correspond to multiple versions because the parameter type is uncertain
    • Different versions of generic type instances are incompatible
  • In order to solve this problem, we can't define a new method to deal with generic < integer >, which is contrary to the concept of polymorphism in Java Therefore, you need a reference type that can logically represent both generic < integer > and generic < number > parent classes Such types are type wildcards:
  • Use wildcards to represent generics:
public void showKeyValueWildcard(Generic<?> obj) {
	Log.d("Generic testing", "key value is" + obj.getKey());
}
  • Type wildcards are generally used? Instead of specific type arguments:
    • Here? Is a type argument, not a type parameter
    • Like number, string and integer, it is an actual type
    • Can you put it? As the parent of all types, it is a real type
  • Usage scenario of type wildcard:
    • When the specific type is uncertain, this wildcard is?
    • When operating a type, you do not need to use the specific functions of the type, but only the functions in the Object class? Wildcards to represent unknown types

generic method

  • Generic class: indicates the specific type of the generic class when instantiating the class
  • Generic method: specify the specific type of the generic when calling the method
/**
 * Generic method:
 * 		1. public The < T > between and the return value is very important, which can be understood as declaring this method as a generic method
 * 		2. Only methods that declare < T > are generic methods. Member methods that use generics in generic classes are not generic methods
 * 		3. <T> Indicates that the method will use the generic type T. only then can the generic type t be used in the method
 * 		4. Like the definition of generic classes, T here can be any identifier. Common parameters such as T, E, K, V, etc. are often used to represent generic classes
 * 
 * @param tClass Generic argument passed in
 * @return T The return value is of type T
 */
 public <T> T genericMethod(Class<T> tClass) throws InstanttiationException, IllegalAccessException {
 	T instance = tClass.newInstance();
 	return instance;
 }
Object obj = genericMethod(Class.forName("com.oxford.test"));
Basic usage of generic methods
  • Generic method usage example:
public class GenericTest {
	/* 
	 * The following class is a generic class
	 */
	 public class Generic<T> {
	 	private T key;

		public Generic(T key) {
			this.key = key;
		}

		/*
		 * Although this method uses generics in the method, it is not a generic method
		 * This is just an ordinary member method in the class, but the return value is the generic declared in the declaration generic class
		 * Therefore, we can continue to use the generic type T in this method
		 */
		 public T getKey() {
		 	return key;
		 }

		/*
		 * There is obviously a problem with the following method. The compiler will prompt the error "cannot resolve symbol E"
		 * Because generic e is not declared in the class declaration, the compiler will not recognize it when using E as formal parameter and return value type
		 *  
		 * public E setKey(E key) {
		 * 	this.key = key
		 * }
		 */
	 } 
	  
	 /*
	  * The following method is a generic method:
	  * 	First, the < T > between public and the return value is essential, which indicates that this is a generic method and declares a generic t
	  * 	This T can appear anywhere in the generic method
	  * 	The number of generics can also be any number
	  */
	  public <T> T showKeyName(Generic<T> container) {
	  	System.out.println("container key:" + container.getKey());
	  	T test = container.getKey();
	  	return test;
	  }
	  
	  /*
	   * The following method is not a generic method either
	   * This is an ordinary method, which only uses the generic class generic < number > as a formal parameter
	   */
	   public void showKeyValue1(Generic<Number> obj) {
	   	Log.d("Generic testing", "key value is " + obj.getKey());
	   }

	  /*
	   * The following method is not a generic method either
	   * This is also a common method, only using generic wildcards?
	   * From here you can verify: generic wildcards? Is a type argument and can be regarded as the parent of all classes
	   */
	   public void showKeyValue2(Generic<?> obj) {
	   	Log.d("Generic testing", "key value is" + obj.getKey());
	   }

	  /*
	   * If there is a problem with the following method, an error message will be prompted in the compiler: "unknown class' e '"
	   * 	Although < T > is declared, it also indicates that this is a generic method that can handle generic types
	   * 	However, only the generic type T is declared, and the generic type E is not declared, so the compiler does not know how to deal with the type E
	   * 
	   * public <T> T showKeyName(Generic<E> container) {
	   * 	...
	   * }
	   */	

	  /*
	   * The following method is also problematic. An error message will be prompted in the compiler: "unknown class't '"
	   * 	For the compiler, T is not declared in the project, so the compiler does not know how to compile this class
	   * 	So this is not a correct generic method declaration
	   *  
	   * public void showKey(T genericObj) {
	   * 	...
	   * }
	   */

		public void main(String[] args) {
		}	 
}
Generic methods in classes
  • Generic methods can appear anywhere and be used in any scenario
  • However, when a generic method appears in a generic class, the situation is special:
public class GenericFruit {
	class Fruit {
		@Override
		public String toString() {
			return "fruit";
		}
	}

	class Apple extends Fruit {
		@Override
		public String toString() {
			retrun "apple";
		}
	}

	class Person {
		@Override
		public String toString() {
			return "Person";
		}
	}

	class GenerateTest<T> {
		public void show_1(T t) {
			System.out.println(t.toString());
		}

		/*
		 * Declare a generic method in a generic class, using generic T
		 * Note that this t is a new type and can not be the same type as the T declared in the generic class
		 */
		 public <T> void show_2(T t) {
		 	System.out.println(t.toString());
		 }
		 
		/* 
		 * Declare a generic method in a generic class and use generic E. this generic e can be of any type and can be the same as type T
		 * Since generic methods declare generics < E > when they are declared, the compiler can correctly recognize generics recognized in generic methods even if generics are not declared in generic classes
		 */
		 public <E> void show_3(E t) {
		 	System.out.println(t.toString());
		 }	
	} 

	public void main(String[] args) {
		Apple apple = new Apple();
		Person person = new Person();

		GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
		// apple is a subclass of Fruit, so you can
		generateTest.show_1(apple);

		/* 
		 * The compiler will report an error because the generic type argument specifies Fruit and the passed in argument class is Person
		 *  
		 * generateTest.show_1(person);
		 */

		/*
		 * You can use both parameters successfully
		 */
		 generateTest.show_2(apple);
		 generateTest.show_2(person);

		/*
		 * Using both parameters can also be successful
		 */
		 generateTest.show_3(apple);
		 generateTest.show_3(person);
	}
}
Generic methods and variable parameters
  • Generic methods and variable parameters:
public <T> void printMsg(T... args) {
	for (T t : args) {
		Log.d("Generic testing", "t is" + t);
	}
}
Static methods and generics
  • Note that static methods in classes use generics:
    • Static methods cannot access generics defined on a class
    • If the reference data type of static method operation is uncertain, the generic type must be defined on the method
  • If a static method wants to use generics, it must be defined as a generic method:
public class StaticGenerator<T> {
	...
	...
	/*
	 * If you define a static method that uses generics in your class, you need to add additional generic declarations - define this method as a generic method
	 * Otherwise, an error will be reported: staticgenerator cannot be referred from static context
	 */
	 public static <T> void show(T t) {
	 }
}
Generic method summary
  • Generic methods can make methods change independently of classes. The usage principles are as follows:
    • Whenever you can, try to use generic methods
    • If you use generic methods to generic the entire class, you should use generic methods
    • For a static method, parameters of generic type cannot be accessed If static methods want to use generics, they must be made generic

Upper and lower boundaries of generics

  • When using generics, you can restrict the upper and lower boundaries of the incoming generic type arguments:
    • For example, an argument of a type can only be passed into a parent class or a child class of a type
  • Add an upper boundary for a generic method, that is, the passed in type argument must be a subtype of the specified type:
public void showKeyValue1(Generic<? extends Number> obj) {
	Log.d("Generic testing", "key value is" + obj.getKey());
}

Generic<String> generic1 = new Generic<String>("11111");
Generic<Integer> generic2 = new Generic<Integer>(2222);
Generic<Float> generic3 = new Generic<Float>(2.4f);
Generic<Double> generic4 = new Generic<Double>(2.56);

/*
 * This line will report an error when compiling, because String type is not a subclass of Number type
 *  
 * showKeyValue1(generic1);
 */
 showKeyValue2(generic2);
 showKeyValue3(generic3);
 showKeyValue4(generic4);
  • Add an upper boundary for the generic class, that is, the generic in the class must be a subtype of the specified type:
public class Generic<T extends Number> {
	private T key;

	public Generic(T key) {
		this.key = key;
	}

	public T getKey() {
		return key;
	}
}

/*
 * This line of code will report an error when compiling, because the type of String is not a subclass of Number
 */
 Generic<String> generic1 = new Generic<String>("1111");
  • When adding an upper and lower boundary restriction in a generic method, you must add an upper and lower boundary on < T > between the permission declaration and the return value:
/*
 * If used:
 * 		public <T> showKeyName(Generic<T extends Number> container);
 * The compiler will report an error
 */
 public <T extends Number> T showKeyName(Generic<T> container) {
 	System.out.println("container key:" + container.getKey());
 	T test = container.getKey();
 	return test;
 }
  • As can be seen from the above, the addition of upper and lower boundaries of generics must be together with the declaration of generics

Generic array

  • In Java, you cannot create an array of exact generic types
/*
 * The way this array is created is not allowed
 * List<String>[] ls = new ArrayList<String>[10];
 */
 
 // It is possible to create generic arrays using wildcards
 List<?>[] ls = new ArrayList<?>[10];

 // The following method is also possible
 List<String> ls = new ArrayList[10];
  • Example:
 List<String>[] lsa = new List<String>[10]; //It is not allowed to define object o = LSA in this way; Object[] oa = (Object) o;  List<Integer> li = new ArrayList<Integer>();  li. add(new Integer(3));   oa[1] = li; //  This is not recommended, but you can check string s = LSA [1] at run time get(0); //  An error is reported during operation, and the type conversion is abnormal
  • Due to the erasure mechanism of the JVM, the JVM does not know the generic information at runtime:
    • All can assign an ArrayList to oa[1] without exception
    • However, ClassCastException will appear if you need to make a type conversion when fetching data
    • If you can declare a generic array, there will be no warnings and errors at compile time, and only errors will be reported at run time
  • By limiting the declaration of generic arrays, you can prompt the code for type safety problems at compile time
  • The type of an array cannot be a type variable unless it is in the form of a wildcard: for the wildcard method, the final fetching of data requires explicit type conversion
List<?>[] lsa= new List<?>[10]; // It can be defined as a generic array
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // You can assign values like this
Integer i = (Integer) lsa[1].get(0); // The data can be retrieved in this way