preface
Believe that everyone is no stranger to Java generics, both in open source framework and the JDK source code to see it, literally, generics are universal design on essential elements, so the real understanding and proper use of generics, is a compulsory subject, this article will solve all of the confusion of the generic type, and through a lot of practice, let you get to the use of generic correct posture, Let’s get down to business.
The outline
basis
Because this article focuses on practice, and is facing the Java developer community, everyone has the foundation of generics, so the generics foundation section will be quick to help you remember, the main focus is on wildcards
Compile time vs. run time
Compile time is the process by which source code is handed over to a compiler to be compiled into a computer executable, and run time is by which the compiled file is handed over to the computer for execution until the end of the program.
In Java, a. Java file is compiled into a. Class file, and then the compiled file is handed to J V M for loading and execution, as shown in the following figure
The generic
Generics are also called “parameterized types”. Such abstract technical terms are not easy to understand, so A xing uses plain English to explain.
The person is iron, rice is just, have a meal is just need, want to have a meal naturally little bowl chopsticks, but bowl can fill rice only without regulation, besides filling rice it still can fill soup, fill dish, maker makes this bowl only, do not care what the bowl is filled, specific what should be filled will decide by the user, this is the concept of extensive model.
Generics specify a particular type (bowl) when defining a class, interface, or method, and let the user of the class, interface, or method decide which type of argument (container) to use.
Java generics were introduced in 1.5. Only generics are checked at compile time, and at run time generics disappear. We call this “generic erasers”, and eventually all types become Object.
Before no generics, from the collection to read each object must be type conversion, if not carefully insert the wrong type of object, the conversion processing at runtime will go wrong, after a generic, can you tell the compiler what each set to receive the object types, the compiler will do at compile-time type checking, inform whether to insert the wrong type of object, It makes the program safer and clearer.
Finally interrupt, generic erasure and ecological types (the List is the original, the List < T > not original) is for the sake of taking care of defects in the design of the 1.5 before, to compatible with the generic code, to compromise strategy, so don’t recommend using original ecological type, if you use the original ecological type, lose the generics in security and the descriptive advantage.
A generic class
Class defines generics on class member variables and functions, code example below
public class GenericClass<T>{
// Member variables
private T t;
public void function(T t){}public T functionTwo(T t){
// Note that this is not a generic method!
returnt; }}Copy the code
A generic interface
Interface to define generics, acting on functions, code example below
public interface GenericInterface<T> {
public T get(a);
public void set(T t);
public T delete(T t);
default T defaultFunction(T t){
returnt; }}Copy the code
Generic function
Function return type next to the generic, on the function, code example is as follows
public class GenericFunction {
public <T> void function(T t) {}public <T> T functionTwo(T t) {
return t;
}
public <T> String functionThree(T t) {
return ""; }}Copy the code
The wildcard
Wildcards are designed to allow Java generics to support scoping, which makes generics more flexible and allows more room for generality design.
<? >
: Unbounded wildcard, that is, the type is indeterminate, any type<? extends T>
: Wildcard of the upper boundary, that is?
Is inherited fromT
Any subtype of the<? super T>
: Wildcard of the lower boundary, that is?
isT
Any parent type of the
Wildcards are defined when the parameterized type is validated, not when the parameterized type is populated
* new ArrayList<>() = new ArrayList
() */
List<Number> numberList = new ArrayList<Number>();
/** * 2. Add different subclasses */
numberList.add(1);// Add the Integer type
numberList.add(0.5);// Add type Double
numberList.add(10000L);// Add the Long type
/** * 3. Create a List class whose generic type is Number. Integer, Double, Long, etc. are subclasses of Number
List<Number> numberListTwo = new ArrayList<Integer>();// Err exception failed to compile
/** * 4. Create a List class whose generic type is Integer and reference the address of this object to List */ whose generic type is Number
List<Integer> integerList = new ArrayList<Integer>();
List<Number> numberListThree = integerList;// Err exception failed to compile
Copy the code
- Step 1: We create a generic type for
Number
theList
, the compiler checks whether the generic class is consistent, and compiles consistently (verify parameterized types). - Step 2: Generics
Number
Having filled in, calladd
Delta function, at this pointadd
The generic refsT
Has been filled withNumber
.add
Accessible to participateNumber
Or its subclasses - Step 3: We create another generic type for
Number
theList
The compiler checks whether the generic class is consistent. If the generic class is inconsistent, the compiler fails to compile. - Step 4: As in step 3, only an indirect reference is made (validate the parameterized type)
If you want to solve the compile failure problem above, you need to use wildcards, as shown below
/** * 1. Upper boundary wildcard, Number and Number subclass */
List<? extends Number> numberListFour = new ArrayList<Number>();
numberListFour = new ArrayList<Integer>();
numberListFour = new ArrayList<Double>();
numberListFour = new ArrayList<Long>();
/** * 2. Lower boundary wildcard,Integer and its parent */
List<? super Integer> integerList = new ArrayList<Integer>();
integerList = new ArrayList<Number>();
integerList = new ArrayList<Object>();
/**
* 3. 无界通配符,类型不确定,任意类型
*/List<? > list =new ArrayList<Integer>();
list = new ArrayList<Number>();
list = new ArrayList<Object>();
list = new ArrayList<String>();
Copy the code
And finally the upper wildcard read only and not write, the lower wildcard write only and not read what does that mean, in the simplest terms
<? extends T>
The upper boundary wildcard is not used as a function entry parameter, only as a function return type, such asList<? extends T>
The use ofadd
The function will compile and fail,get
Functions are fine<? super T>
The lower-bound wildcard is not used as a function return type, but as a function entry parameter, for exampleList<? super T>
theadd
The function is called normally,get
The function is fine, but only returnsObject
So it doesn’t mean much
Just remember the rules above. If you want to know why it’s done this way, you can look at the producer-extends,consumer-super principle. Right
Best practices
I believe that after the basic theory we have a lot of things recalled, don’t worry, now start to get into the topic, the content behind there will be a lot of code practice, so we want to sit firmly, don’t get carsick, carsick words read several times, or put forward your questions in the comment area ~
Infinite wildcard scenarios
With generics, where type parameters are uncertain and you don’t care about the actual type parameters, you can use
, as in the following code
/** * get the set length */
public static <T> int size(Collection<T> list){
return list.size();
}
/** * get set length -2 */
public static int sizeTwo(Collection<?> list){
return list.size();
}
/** * Get the intersection number of any Set */
public static <T,T2> int beMixedSum(Set<T> s1,Set<T2> s2){
int i = 0;
for (T t : s1) {
if(s2.contains(t)) { i++; }}return i;
}
/** * get the intersection number of any two sets -2 */
public static int beMixedSumTwo(Set
s1,Set
s2){
int i = 0;
for (Object o : s1) {
if(s2.contains(o)) { i++; }}return i;
}
Copy the code
Size and sizeTwo both work fine, but sizeTwo is more appropriate from a design point of view. The object of the function is to return the length of any set, taking either
or
is accepted, but the function itself doesn’t care what type argument you are, just returns the length, so use
.
BeMixedSum and beMixedSumTwo are compared in the same way as above, beMixedSumTwo is more appropriate. The goal of the function is to return the intersection number of two arbitrary sets. Although beMixedSum is used internally, it has little significance. Because contains is Object, the function itself does not care what type parameter you are, so it uses
.
I forgot to add another scenario, that is, the primitive type, the above code can be used with the primitive type function, but it is strongly not recommended, because using the primitive will lose the security and description of generics!!
Upper and lower boundary wildcard scenarios
First, generics are immutable, in other words List
/** * set tool class */
public class CollectionUtils<T>{
/** * copy collection - generic */
public List<T> listCopy(Collection<T> collection){
List<T> newCollection = new ArrayList<>();
for (T t : collection) {
newCollection.add(t);
}
returnnewCollection; }}Copy the code
It declares a CollectionUtils class that has a listCopy method that passes in any collection and returns a new collection.
public static void main(String[] agrs){
CollectionUtils<Number> collectionUtils = new CollectionUtils<>();
List<Number> list = new ArrayList<>();
//list.add....
List<Integer> listTwo = new ArrayList<>();
//listTwo.add....
List<Double> listThree = new ArrayList<>();
//listThree.add....
List<Number> list1 = collectionUtils.listCopy(list);
list1 = collectionUtils.listCopy(listTwo);// Err compilation error
list1 = collectionUtils.listCopy(listThree);// Err compilation error
}
Copy the code
CollectionUtils (Number); listCopy (Number); CollectionUtils (Number); We need to use the upper boundary wildcard, and we append another method
/** * set tools */
public class CollectionUtils<T>{
/** * copy collection - generic */
public List<T> listCopy(Collection<T> collection){
List<T> newCollection = new ArrayList<>();
for (T t : collection) {
newCollection.add(t);
}
return newCollection;
}
/** * copy set - upper boundary wildcard */
public List<T> listCopyTwo(Collection<? extends T> collection){
List<T> newCollection = new ArrayList<>();
for (T t : collection) {
newCollection.add(t);
}
returnnewCollection; }}public static void main(String[] agrs){
CollectionUtils<Number> collectionUtils = new CollectionUtils<>();
List<Number> list = new ArrayList<>();
//list.add....
List<Integer> listTwo = new ArrayList<>();
//listTwo.add....
List<Double> listThree = new ArrayList<>();
//listThree.add....
List<Number> list1 = collectionUtils.listCopyTwo(list);
list1 = collectionUtils.listCopyTwo(listTwo);
list1 = collectionUtils.listCopyTwo(listThree);
}
Copy the code
Now use listCopyTwo there is no problem, listCopyTwo contrast listCopy it is more widely applicable and more flexible, listCopy can do listCopyTwo can do, listCopyTwo can do listCopy is not necessarily can do, in addition, If you are careful, you must have noticed that the collection with the upper boundary wildcard only uses the read operation in the function, following the read-only, no write principle.
So once you’ve looked at the top wildcard, let’s look at the bottom wildcard, which is still a copy method
/**
* 儿子
*/
public class Son extends Father{}
/** ** father */
public class Father extends Grandpa{}
/** * grandpa */
public class Grandpa {}
/** * set tools */
public class CollectionUtils<T>{
/** * Copy set - generic * target target SRC source */
public void copy(List<T> target,List<T> src){
if (src.size() > target.size()){
for (int i = 0; i < src.size(); i++) { target.set(i,src.get(i)); }}}}Copy the code
(CollectionUtils); (CollectionUtils); (CollectionUtils); (CollectionUtils); (CollectionUtils) Take a look at the following code
public static void main(String[] agrs){
CollectionUtils<Father> collectionUtils = new CollectionUtils<>();
List<Father> fatherTargets = new ArrayList<>();
List<Father> fatherSources = new ArrayList<>();
//fatherSources.add...
collectionUtils.copy(fatherTargets,fatherSources);
// Copy the subclass to the parent class
List<Son> sonSources = new ArrayList<>();
//sonSources.add...
collectionUtils.copy(fatherTargets,sonSources);// Err compilation error
}
Copy the code
CollectionUtils (Father); copy (Father); copy (Father); copy (Father); Copy (SRC) is used as an input parameter to copy a subclass to its parent class. The input parameter of copy (SRC) is used as an input parameter to copy a subclass to its parent class
/** * set tools */
public class CollectionUtils<T>{
/** * Copy set - generic * target target SRC source */
public void copy(List<T> target,List<? extends T> src){
if (src.size() > target.size()){
for (int i = 0; i < src.size(); i++) { target.set(i,src.get(i)); }}}}public static void main(String[] agrs){
CollectionUtils<Father> collectionUtils = new CollectionUtils<>();
List<Father> fatherTargets = new ArrayList<>();
List<Father> fatherSources = new ArrayList<>();
//fatherSources.add...
collectionUtils.copy(fatherTargets,fatherSources);
// Copy the subclass to the parent class
List<Son> sonSources = new ArrayList<>();
//sonSources.add...
collectionUtils.copy(fatherTargets,sonSources);
// Copy the subclass to the parent class
List<Grandpa> grandpaTargets = new ArrayList<>();
collectionUtils.copy(grandpaTargets,sonSources);// Err compilation error
}
Copy the code
SRC = List
sonSources; copy = List
sonSources; copy = List
sonSources Continue to analyze copy function, copy function’s input parameter target in the function only involves the add function, that is, write operation (the generic type is only used as the add function input parameter), in accordance with the write but not read principle, can use the lower boundary wildcard, adjust the code as follows
/** * set tools */
public class CollectionUtils<T>{
/** * Copy set - generic * target target SRC source */
public void copy(List<? super T> target,List<? extends T> src){
if (src.size() > target.size()){
for (int i = 0; i < src.size(); i++) { target.set(i,src.get(i)); }}}}public static void main(String[] agrs){
CollectionUtils<Father> collectionUtils = new CollectionUtils<>();
List<Father> fatherTargets = new ArrayList<>();
List<Father> fatherSources = new ArrayList<>();
//fatherSources.add...
collectionUtils.copy(fatherTargets,fatherSources);
// Copy the subclass to the parent class
List<Son> sonSources = new ArrayList<>();
//sonSources.add...
collectionUtils.copy(fatherTargets,sonSources);
// Copy the subclass to the parent class
List<Grandpa> grandpaTargets = new ArrayList<>();
collectionUtils.copy(grandpaTargets,sonSources);
}
Copy the code
Target is the target set and only writes to it. SRC is the source set and only reads from it. Copy is the source set and only reads from it. In accordance with the principle of read-only not write, the use of the upper boundary wildcard, the final design of copy function, its flexibility and scope of application is far more than the
design.
To summarise, when to use wildcards, if the parameter of a generic class is to read and to write, then it is not recommended to use generics can normally, if the parameters of a generic class only read or write, can according to the principle of adopting corresponding to the upper boundary, is very simple, finally once again the meaning of read and write, this is really easy to dizzy
- Read: The so-called read refers to the parameter generic class. The generic type is only used as the function return type of the parameter class, then the function is read.
List
As a parameter generic class, itsget
The function is read - Write: the so-called write refers to the parameter generic class, the generic only as the parameter class function input parameter, then the function is write,
List
As a parameter generic class, itsadd
The function is read
Finally, we can recommend you to think about the Stream forEach function and map function design, in Java1.8 Stream is a lot of use of wildcard design
-----------------------------------------------------------------
/** * lower wildcard */
void forEach(Consumer<? super T> action);
public interface Consumer<T> {
/ / write method
void accept(T t); } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --/** * upper and lower wildcard */
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
public interface Function<T, R> {
// read-write methods, T only as an input parameter, R only as a return value, read only
R apply(T t); } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --// Code example
public static void main(String[] agrs) {
List<Father> fatherList = new ArrayList<>();
Consumer<? super Father> action = new Consumer<Father>() {
@Override
public void accept(Father father) {
// execute the father logic}};// The lower boundary wildcard is transformed upward
Consumer<? super Father> actionTwo = new Consumer<Grandpa>() {
@Override
public void accept(Grandpa grandpa) {
// Execute the grandpa logic}}; Function<?super Father, ? extends Grandpa> mapper = new Function<Father, Grandpa>() {
@Override
public Grandpa apply(Father father) {
// return Grandpa after father
return newGrandpa(); }};// Lower wildcards are cast up, and upper wildcards are cast down
Function<? super Father, ? extends Grandpa> mapperTwo = new Function<Grandpa, Son>() {
@Override
public Son apply(Grandpa grandpa) {
// after executing the grandpa logic, return Son
return newSon(); }}; fatherList.stream().forEach(action); fatherList.stream().forEach(actionTwo); fatherList.stream().map(mapper); fatherList.stream().map(mapperTwo); } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --Copy the code
There are restricted generic scenarios
Generics are easy to use if you need to restrict the types of their parameters, such as this code
public class GenericClass<T extends Grandpa> {
public void test(T t){
//....}}public static void main(String[] agrs){
GenericClass<Grandpa> grandpaGeneric = new GenericClass<>();
grandpaGeneric.test(new Grandpa());
grandpaGeneric.test(new Father());
grandpaGeneric.test(new Son());
GenericClass<Father> fatherGeneric = new GenericClass<>();
fatherGeneric.test(new Father());
fatherGeneric.test(new Son());
GenericClass<Son> sonGeneric = new GenericClass<>();
sonGeneric.test(new Son());
GenericClass<Object> ObjectGeneric = new GenericClass<>();// Err compilation error
}
Copy the code
GenericClass generic parameterized types are limited to Grandpa or a subclass of it, as simple as that. Do not confuse bounded generics with upper bound wildcards. The two are not the same thing (
! =
),
does not need to follow the rule of upper bounds wildcards. It is simply a generic parameterized type constraint, and there is no super.
Recursive generic scenarios
We can also derive recursive generics that need to be used by ourselves, such as collections for custom element size comparisons, often with the Comparable interface, as shown in the following code
public class Person implements Comparable<Person> {
private int age;
public Person(int age) {
this.age = age;
}
public int getAge(a) {
return age;
}
@Override
public int compareTo(Person o) {
0 equals 1 equals greater than 0 equals less than 0
return this.age - o.age; }}/** * set tools */
public class CollectionUtils{
/** * get the maximum set value */
public static <E extends Comparable<E>> E max(List<E> list){
E result = null;
for (E e : list) {
if (result == null || e.compareTo(result) > 0){ result = e; }}returnresult; }}public static void main(String[] agrs){
List<Person> personList = new ArrayList<>();
personList.add(new Person(12));
personList.add(new Person(19));
personList.add(new Person(20));
personList.add(new Person(5));
personList.add(new Person(18));
Return the oldest Person element
Person max = CollectionUtils.max(personList);
}
Copy the code
> means that the generic type E must be Comparable or its subclass/implementation class. Because the comparison elements are of the same type, the Comparable generic is also E, and the final received List generic parameterized type must implement the Comparable interface, and the generic that the Comparable interface fills is also that parameterized type, as in the code above.
About me
Here is A star, a Java program ape who loves technology. In the public account “program ape APE”, we will regularly share excellent original articles on operating system, computer network, Java, distributed, database and so on. In 2021, we will grow together on the road of Be Better! .
Thank you very much everyone can see here, the original is not easy, the article can help “like” or “share and comment”, are support (don’t want white whoring)!
May you and I both go where we want to go. See you in the next article!