编程知识 cdmana.com

Revisit Java generics, take you deeper understanding of it, better use it!

1. introduction

jdk5.0 Introduced in Java Generic , The goal is to reduce mistakes , And add an extra layer of abstraction to the type .
This article will briefly introduce Java The generics in 、 The goal behind generics and how to use generics to improve code quality .

2. Why use generics ?

Imagine a scenario , We hope to use Java Create a list to store Integer; The code might be written like this :

List list = new LinkedList();
list.add(new Integer(1)); 
Integer i = list.iterator().next();

It's amazing , The compiler will prompt for the last line . It doesn't know what type of data is returned . therefore , The compiler prompts for an explicit conversion :

Integer i = (Integer) list.iterator.next();

There is no convention to guarantee that the return type of a list is Integers . The defined list can contain any object . We only know that we retrieve the list by checking the context . When viewing types , It can only guarantee that it is a Object, Therefore, explicit conversion is needed to ensure that the type is safe .

This conversion can be noisy , We know that the data type in this list is Integers . If you switch , It's also messing up our code . If the programmer makes a mistake in an explicit transformation , It may result in throwing and Type related runtime error .

If programmers can express their intention to use a particular type , And the compiler can ensure the correctness of this type , So it's going to be easier .

This is the core idea behind generics .

We change the first line of the previous code snippet to read :

List<Integer> list = new LinkedList<>();

By adding the diamond operator containing the type <>, Let's narrow the list down to Integer type , The specified type is saved in the list . The compiler can enforce the type at compile time .

In smaller programs , It looks like a trivial addition . But in larger programs , This can increase significant robustness and make the program easier to read .

3. Generic methods

Generic methods are methods written with a single method declaration , You can call... With different types of parameters . The compiler will ensure that the type used is correct . Here are some properties of generic methods :

  • A generic method has a type parameter before the return type of the method declaration ( Diamond operator for parcel type )
  • Type parameters can be bounded ( The boundary will be explained later in this article )
  • Generic methods can have different type parameters , These parameters are separated by commas in the method signature
  • The method body of a generic method is the same as that of a normal method

An example of defining a generic method to convert an array to a list :

public <T> List<T> fromArrayToList(T[] a) {   
    return Arrays.stream(a).collect(Collectors.toList());
}

In the previous example , In the method declaration <T> Indicates that the method will handle generic types T. Even if the method returns void, It needs to be done as well .
As mentioned above , Method can handle multiple generic types , under these circumstances , All generic types must be added to the method declaration , for example , If we want to modify the above method to handle types T And type G , It should be written like this :

public static <T, G> List<G> fromArrayToList(T[] a, Function<T, G> mapperFunction) {
    return Arrays.stream(a)
      .map(mapperFunction)
      .collect(Collectors.toList());
}

We're passing a function , The function will have T An array of type elements is converted to contain G List of type elements . for example , take Integer Convert to String Representation form :

@Test
public void givenArrayOfIntegers_thanListOfStringReturnedOK() {
    Integer[] intArray = {1, 2, 3, 4, 5};
    List<String> stringList
      = Generics.fromArrayToList(intArray, Object::toString);
 
    assertThat(stringList, hasItems("1", "2", "3", "4", "5"));
}

Oracle It is recommended to use uppercase letters for generic types , And choose more descriptive letters to represent the type of form , For example, in Java Collection ,T Used of type ,K The key ,V Indicated value .

3.1. Generic boundaries

As mentioned earlier , Type parameters can be bounded . Bounded means “ Limit ”, We can limit the types of methods that can be accepted .

for example , You can specify a method to accept a type and all its subclasses ( ceiling ) Or a type, all its superclasses ( Lower limit ).

To declare the upper bound type , We use keywords after types extends, Follow the upper limit to use . for example :

public <T extends Number> List<T> fromArrayToList(T[] a) {
    ...
}

The keyword is used here extends Indicates the type T Extend the upper limit of the class , Or implement the upper limit of the interface .

3.2. Multiple boundaries

Types can also have multiple upper bounds , As shown below :

<T extends Number & Comparable>

If T One of the types of extensions is class ( namely Number), It must be placed first in the list of boundaries . otherwise , Will cause compile time errors .

4. Use wildcards

Wildcard in Java Chinese question mark ““ Express , They are used to refer to an unknown type . Wildcards are especially useful when using generics , Can be used as a parameter type , But the first thing to consider is an important comment .

as everyone knows ,Object It's all Java The supertype of a class , however ,Object Is not a supertype of any set .( It may be a little winding , Let's have a good taste )

for example ,List<Object> No List<String> The supertype of , take List<Object> A variable of type is assigned to List<String> Variables of type will cause compiler errors .

This is to prevent possible conflicts when adding heterogeneous types to the same collection .

The same rules apply to any set of types and their subtypes . Take a look at this example :

public static void paintAllBuildings(List<Building> buildings) {
    buildings.forEach(Building::paint);
}

If we imagine a subtype Building, example House, So we can't combine this method with House Lists are used together , Even if House yes Building Subtypes of . If you need to use this method with a type build and all its subtypes , The bounded wildcard can achieve the following functions :

public static void paintAllBuildings(List<? extends Building> buildings) {
    ...
}

Now? , This method can deal with Building Type and all its subtypes . This is called the upper bound wildcard , The type Building It's the upper bound .

Wildcards can also be specified with a lower bound , Where the unknown type must be a supertype of the specified type . have access to super Keyword followed by a specific type to specify the lower bound , for example ,<? super T> Indicates an unknown type , It is T(=T And all of its parents ) Superclass of .

5. Type Erasure

Generics are added to Java To ensure type safety , And make sure that generics don't cause overhead at run time , At compile time, the compiler applies a name called type erasure The process of .

Type erase removes all type parameters , And replace them with their boundaries , If the type parameter is unbounded , Is replaced by Object. therefore , The compiled bytecode contains only ordinary classes 、 Interfaces and methods , To ensure that no new types are generated . At compile time Object The correct type is also applied .
Here is an example of type erasure :

public <T> List<T> genericMethod(List<T> list) {
    return list.stream().collect(Collectors.toList());
}

Use type erase , Unbounded type T Replace with Object, As shown below :

// for illustration
public List<Object> withErasure(List<Object> list) {
    return list.stream().collect(Collectors.toList());
}
 
// which in practice results in
public List withErasure(List list) {
    return list.stream().collect(Collectors.toList());
}

If the type is bounded , At compile time, the type is replaced with binding :

public <T extends Building> void genericMethod(T t) {
    ...
}

Changes occur after compilation :

public void genericMethod(Building t) {
    ...
}

6. Generics and raw data types

Java One limitation of generics in is that type parameters cannot be primitive types

for example , The following cannot be compiled :

List<int> list = new ArrayList<>();
list.add(17);

To understand why raw data types don't work , Just remember Generics are compile time features , This means that the type will be erased , All generic types are implemented as Object class .
For example , Let's look at the list of add Method :

List<Integer> list = new ArrayList<>();
list.add(17);

add The method is declared as follows :

boolean add(E e);

And will be compiled as :

boolean add(Object e);

therefore , Type parameters must be convertible to Object. Because the base type does not inherit from Object, So you can't use them as type parameters
however ,Java The type of packing is provided for them , And automatic packing and unpacking :

Integer a = 17;
int b = a;

therefore , If we want to create a list that can hold integers , We can use wrappers :

List<Integer> list = new ArrayList<>();
list.add(17);
int first = list.get(0);

The compiled code is equivalent to :

List list = new ArrayList<>();
list.add(Integer.valueOf(17));
int first = ((Integer) list.get(0)).intValue();

Java Future versions of may allow generics to use raw data types .Valhalla Engineering aims to improve the way generics are handled . The idea is to realize JEP 218 Generic specialization described in .

7. summary

Java Generics are right for Java A powerful complement to language , Because it makes the programmer's job easier , It's also less likely to make mistakes . Generics enforce type correctness at compile time , also , most important of all , Can implement generic algorithms , It doesn't add any extra overhead to our application .

If you think the article is good , Remember to pay attention to official account. : Big guy out of the pot
Liu Yishou's blog

版权声明
本文为[soluble]所创,转载请带上原文链接,感谢

Scroll to Top